summaryrefslogtreecommitdiff
path: root/tests/logicTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/logicTests.cpp')
-rw-r--r--tests/logicTests.cpp685
1 files changed, 685 insertions, 0 deletions
diff --git a/tests/logicTests.cpp b/tests/logicTests.cpp
new file mode 100644
index 0000000..97d9ed9
--- /dev/null
+++ b/tests/logicTests.cpp
@@ -0,0 +1,685 @@
+/** @file logicTests.cpp
+* @brief Unit tests for the logic layer of a Rally-X game.
+*
+* The classes used to run Allegro's install and uninstall functions were tested indirectly
+* through the use of other classes. If the AllegroWrappers classes are not working, they will
+* result in tests failing, or the program crashing.
+*
+* The Car class's movement is tested through implementing a subclass of Car, PlayerCar, facing
+* in a given direction with and without maze walls in the way, and testing its position after
+* a frame has passed.
+*
+* The counting system with the Checkpoint class is tested through repeated creation and
+* destruction of Checkpoint objects.
+*
+* The CollisionDetecter class is tested by setting up situations with each type of collision,
+* and checking that they have the desired results on the objects involved.
+*
+* The DestroyedObjectPopup class is tested to ensure that it lasts the desired amount of time
+* before being destroyed.
+*
+* The intelligence of the EnemyCar class is not unit tested, as it may change as extra states
+* and AI schemes are implemented, possibly including a random element. Therefore, it should be
+* tested manually by playing example levels and monitoring if the enemies appear to behave in
+* a manner that makes gameplay interesting.
+*
+* The Game class is not tested directly. It pulls all of the units being tested together into
+* a complete game, and so should be tested by running the Game.
+*
+* The GameObject class is tested indirectly through testing its subclasses.
+*
+* The LimitedTimeObject class is tested indirectly through the DestroyedObjectPopup and Smokescreen classes.
+*
+* The Maze class is tested in terms of its ability to construct itself correctly from a list of
+* coordinates. It is also tested with the PlayerCar class where the movement of the PlayerCar is
+* tested in a Maze.
+*
+* The MazeMath class is tested by running each of its functions with expected input and output values.
+*
+* The PlayerCar class is tested on its ability to move correctly as part of the tests for the Car class.
+* The functioning of the PlayerCar's petrol system is also tested. Controlling of the PlayerCar
+* is not tested, and should be tested manually by running the game and verifying that the arrow keys
+* cause the direction of the PlayerCar to change appropriately. Further, the creation of Smokescreens
+* by pressing the spacebar should also be tested manually.
+*
+* The Rock class only has functionality in collisions, and so is tested through the CollisionDetector's
+* tests.
+*
+* The Smokescreen class is tested in that it disappears after a set time. Its affect on the EnemyCar class
+* is tested through the CollisionDetector's tests.
+*
+* @author Justin Wernick
+* @author David Schneider
+*/
+
+#include <vector>
+#include <list>
+#include <utility>
+using namespace std;
+
+#include <gtest/gtest.h>
+
+#include "../source/logic/CollisionDetector.h"
+#include "../source/logic/PlayerCar.h"
+#include "../source/logic/EnemyCar.h"
+#include "../source/logic/Checkpoint.h"
+#include "../source/logic/Rock.h"
+#include "../source/logic/Smokescreen.h"
+#include "../source/logic/Maze.h"
+#include "../source/logic/MazeMath.h"
+#include "../source/logic/DestroyedObjectPopup.h"
+
+/**
+* @brief Tests that PlayerCar moves the correct amount in the up direction on each frame without obstacles.
+*/
+TEST(Car, updateMovesPlayerUpInEmptyMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(2,2,Maze::UP);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 2;
+ double expectY = 2-player.speed();
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that PlayerCar moves the correct amount in the down direction on each frame without obstacles.
+*/
+TEST(Car, updateMovesPlayerDownInEmptyMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(2,2,Maze::DOWN);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 2;
+ double expectY = 2+player.speed();
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that PlayerCar moves the correct amount in the left direction on each frame without obstacles.
+*/
+TEST(Car, updateMovesPlayerLeftInEmptyMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(2,2,Maze::LEFT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 2-player.speed();
+ double expectY = 2;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that PlayerCar moves the correct amount in the right direction on each frame without obstacles.
+*/
+TEST(Car, updateMovesPlayerRightInEmptyMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(2,2,Maze::RIGHT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 2+player.speed();
+ double expectY = 2;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that PlayerCar does not move up if the path is blocked.
+*/
+TEST(Car, carNotMovedWhenPathUpBlocked)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(3,3));
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(3,4,Maze::UP);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 3;
+ double expectY = 4;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move down if the path is blocked.
+*/
+TEST(Car, carNotMovedWhenPathDownBlocked)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(3,3));
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(3,2,Maze::DOWN);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 3;
+ double expectY = 2;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move left if the path is blocked.
+*/
+TEST(Car, carNotMovedWhenPathLeftBlocked)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(3,3));
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(4,3,Maze::LEFT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 4;
+ double expectY = 3;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move right if the path is blocked.
+*/
+TEST(Car, carNotMovedWhenPathRightBlocked)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(3,3));
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(2,3,Maze::RIGHT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 2;
+ double expectY = 3;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that PlayerCar does not move up if currently on the top row of the maze.
+*/
+TEST(Car, carDoesNotMoveUpOutMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(3,0,Maze::UP);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 3;
+ double expectY = 0;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move down if currently on the bottom row of the maze.
+*/
+TEST(Car, carDoesNotMoveDownOutMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(3,5,Maze::DOWN);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 3;
+ double expectY = 5;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move left if currently on the first column of the maze.
+*/
+TEST(Car, carDoesNotMoveLeftOutMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(0,3,Maze::LEFT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 0;
+ double expectY = 3;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+/**
+* @brief Tests that PlayerCar does not move right if currently on the last column of the maze.
+*/
+TEST(Car, carDoesNotMoveRightOutMaze)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(5,3,Maze::RIGHT);
+
+ list<Smokescreen> smokescreens;
+ player.update(testMaze, smokescreens);
+
+ double expectX = 5;
+ double expectY = 3;
+ EXPECT_FLOAT_EQ(expectX, player.x());
+ EXPECT_FLOAT_EQ(expectY, player.y());
+}
+
+/**
+* @brief Tests that counting of the number of checkpoints happens correctly.
+*/
+TEST(Checkpoint, countIncrementsAndDecrements)
+{
+ vector<Checkpoint> checkpoints;
+ EXPECT_EQ(0, Checkpoint::checkpointCount());
+
+ for (int i=0; i<1000; ++i)
+ {
+ checkpoints.push_back(Checkpoint(i,i));
+ }
+ EXPECT_EQ(1000, Checkpoint::checkpointCount());
+
+ //brackets to limit scope
+ {
+ Checkpoint extraCheck1(0,0);
+ Checkpoint extraCheck2 = extraCheck1;
+ EXPECT_EQ(1002, Checkpoint::checkpointCount());
+ extraCheck2 = extraCheck1;
+ EXPECT_EQ(1002, Checkpoint::checkpointCount());
+ }
+ EXPECT_EQ(1000, Checkpoint::checkpointCount());
+
+ while(!checkpoints.empty())
+ {
+ checkpoints.pop_back();
+ }
+ EXPECT_EQ(0, Checkpoint::checkpointCount());
+}
+
+/**
+* @brief Tests that when a player and enemy overlap, both are marked for destruction.
+*/
+TEST(CollisionDetector, playerAndEnemyBothDestroyed)
+{
+ list<PlayerCar> players;
+ list<EnemyCar> enemies;
+ list<Checkpoint> checkpoints;
+ list<Rock> rocks;
+ list<Smokescreen> smokescreens;
+ CollisionDetector detector;
+
+ players.push_back(PlayerCar(5,5));
+ players.push_back(PlayerCar(5,6));
+ players.push_back(PlayerCar(6,5));
+ players.push_back(PlayerCar(7,7.5));
+
+ enemies.push_back(EnemyCar(5,6));
+ enemies.push_back(EnemyCar(4,5));
+ enemies.push_back(EnemyCar(6,6));
+ enemies.push_back(EnemyCar(6.5,8));
+
+ detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens);
+
+ list<PlayerCar>::const_iterator playerIter = players.begin();
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_TRUE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_TRUE(playerIter->destroyed());
+
+ list<EnemyCar>::const_iterator enemyIter = enemies.begin();
+ EXPECT_TRUE(enemyIter->destroyed());
+ ++enemyIter;
+ EXPECT_FALSE(enemyIter->destroyed());
+ ++enemyIter;
+ EXPECT_FALSE(enemyIter->destroyed());
+ ++enemyIter;
+ EXPECT_TRUE(enemyIter->destroyed());
+}
+
+/**
+* @brief Tests that when a player and rock overlap, only the player is marked for destruction.
+*/
+TEST(CollisionDetector, playerDestroyedByRock)
+{
+ list<PlayerCar> players;
+ list<EnemyCar> enemies;
+ list<Checkpoint> checkpoints;
+ list<Rock> rocks;
+ list<Smokescreen> smokescreens;
+ CollisionDetector detector;
+
+ players.push_back(PlayerCar(5,5));
+ players.push_back(PlayerCar(5,6));
+ players.push_back(PlayerCar(6,5));
+ players.push_back(PlayerCar(7,7.5));
+
+ rocks.push_back(Rock(5,6));
+ rocks.push_back(Rock(4,5));
+ rocks.push_back(Rock(6,6));
+ rocks.push_back(Rock(6.5,8));
+
+ detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens);
+
+ list<PlayerCar>::const_iterator playerIter = players.begin();
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_TRUE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_TRUE(playerIter->destroyed());
+
+ list<Rock>::const_iterator rockIter = rocks.begin();
+ EXPECT_FALSE(rockIter->destroyed());
+ ++rockIter;
+ EXPECT_FALSE(rockIter->destroyed());
+ ++rockIter;
+ EXPECT_FALSE(rockIter->destroyed());
+ ++rockIter;
+ EXPECT_FALSE(rockIter->destroyed());
+}
+
+/**
+* @brief Tests that when a player and checkpoint overlap, only the checkpoint is marked for destruction.
+*/
+TEST(CollisionDetector, checkpointDestroyedByPlayer)
+{
+ list<PlayerCar> players;
+ list<EnemyCar> enemies;
+ list<Checkpoint> checkpoints;
+ list<Rock> rocks;
+ list<Smokescreen> smokescreens;
+ CollisionDetector detector;
+
+ players.push_back(PlayerCar(5,5));
+ players.push_back(PlayerCar(5,6));
+ players.push_back(PlayerCar(6,5));
+ players.push_back(PlayerCar(7,7.5));
+
+ checkpoints.push_back(Checkpoint(5,6));
+ checkpoints.push_back(Checkpoint(4,5));
+ checkpoints.push_back(Checkpoint(6,6));
+ checkpoints.push_back(Checkpoint(6.5,8));
+
+ detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens);
+
+ list<PlayerCar>::const_iterator playerIter = players.begin();
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_FALSE(playerIter->destroyed());
+ ++playerIter;
+ EXPECT_FALSE(playerIter->destroyed());
+
+ list<Checkpoint>::const_iterator checkpointIter = checkpoints.begin();
+ EXPECT_TRUE(checkpointIter->destroyed());
+ ++checkpointIter;
+ EXPECT_FALSE(checkpointIter->destroyed());
+ ++checkpointIter;
+ EXPECT_FALSE(checkpointIter->destroyed());
+ ++checkpointIter;
+ EXPECT_TRUE(checkpointIter->destroyed());
+}
+
+/**
+* @brief Tests that when an enemy and smokescreen overlap, the enemy's speed becomes zero.
+*/
+TEST(CollisionDetector, enemyStoppedBySmokescreen)
+{
+ list<PlayerCar> players;
+ list<EnemyCar> enemies;
+ list<Checkpoint> checkpoints;
+ list<Rock> rocks;
+ list<Smokescreen> smokescreens;
+ CollisionDetector detector;
+
+ enemies.push_back(EnemyCar(5,5));
+ enemies.push_back(EnemyCar(5,6));
+ enemies.push_back(EnemyCar(6,5));
+ enemies.push_back(EnemyCar(7,7.5));
+
+ smokescreens.push_back(Smokescreen(5,6));
+ smokescreens.push_back(Smokescreen(4,5));
+ smokescreens.push_back(Smokescreen(6,6));
+ smokescreens.push_back(Smokescreen(6.5,8));
+
+ detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens);
+
+ list<EnemyCar>::const_iterator enemyIter = enemies.begin();
+ EXPECT_GT(enemyIter->speed(),0);
+ ++enemyIter;
+ EXPECT_FLOAT_EQ(0, enemyIter->speed());
+ ++enemyIter;
+ EXPECT_GT(enemyIter->speed(),0);
+ ++enemyIter;
+ EXPECT_FLOAT_EQ(0, enemyIter->speed());
+}
+
+/**
+* @brief Tests that a DestroyedObjectPopup is destroyed 30 frames after it is created.
+*/
+TEST(DestroyedObjectPopup, destroyedAfterSetTime)
+{
+ DestroyedObjectPopup testPopup(3,2,BitmapStore::CRASHED_CAR);
+
+ int i=0;
+ while (!testPopup.destroyed())
+ {
+ testPopup.update();
+ ++i;
+ }
+
+ EXPECT_EQ(30,i);
+}
+
+/**
+* @brief Tests that the maze treats the undefined area outside its bounds as being walls.
+*/
+TEST(Maze, queryOutsideBoundsReturnsSolid)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ EXPECT_FALSE(testMaze.getSolid(5,5));
+ EXPECT_FALSE(testMaze.getSolid(0,0));
+ EXPECT_TRUE(testMaze.getSolid(6,5));
+ EXPECT_TRUE(testMaze.getSolid(5,6));
+ EXPECT_TRUE(testMaze.getSolid(-1,3));
+ EXPECT_TRUE(testMaze.getSolid(3,-1));
+}
+
+/**
+* @brief Tests that a maze can be generated without extending the width and height for objects.
+*/
+TEST(Maze, generationWithWallsWithoutObjectMax)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(1,2));
+ walls.push_back(make_pair(4,3));
+ testMaze.generateMaze(walls);
+
+ //test random empty blocks
+ EXPECT_FALSE(testMaze.getSolid(2,1));
+ EXPECT_FALSE(testMaze.getSolid(0,0));
+
+ //test wall blocks
+ EXPECT_TRUE(testMaze.getSolid(1,2));
+ EXPECT_TRUE(testMaze.getSolid(4,3));
+
+ //test bounds are being set right
+ EXPECT_TRUE(testMaze.getSolid(4,4));
+ EXPECT_TRUE(testMaze.getSolid(5,3));
+}
+
+/**
+* @brief Tests that a maze can be generated with extending the width and height for objects.
+*/
+TEST(Maze, generationWithWallsWithObjectMax)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ walls.push_back(make_pair(1,2));
+ walls.push_back(make_pair(4,3));
+ testMaze.generateMaze(walls,5,6);
+
+ //test random empty blocks
+ EXPECT_FALSE(testMaze.getSolid(2,1));
+ EXPECT_FALSE(testMaze.getSolid(0,0));
+
+ //test wall blocks
+ EXPECT_TRUE(testMaze.getSolid(1,2));
+ EXPECT_TRUE(testMaze.getSolid(4,3));
+
+ //test bounds are being set right
+ EXPECT_FALSE(testMaze.getSolid(4,4));
+ EXPECT_FALSE(testMaze.getSolid(5,6));
+ EXPECT_TRUE(testMaze.getSolid(6,6));
+ EXPECT_TRUE(testMaze.getSolid(5,7));
+}
+
+/**
+* @brief Tests that the formula for a straight line distance works as expected.
+*/
+TEST(MazeMath, distanceGivesExpectedResult)
+{
+ //right angle triange with sides of length 3, 4, and 5
+ double x1 = 1;
+ double y1 = 1;
+ double x2 = 4;
+ double y2 = 5;
+
+ double expectedResult = 5;
+
+ EXPECT_DOUBLE_EQ(expectedResult, MazeMath::distance(x1,y1,x2,y2));
+}
+
+/**
+* @brief Tests that rounding off function works as expected.
+*/
+TEST(MazeMath, roundGivesExpectedResult)
+{
+ //right angle triange with sides of length 3, 4, and 5
+ double roundUp = 5.5;
+ double roundDown = 5.49;
+
+ double expectUp = 6;
+ double expectDown = 5;
+
+ EXPECT_DOUBLE_EQ(expectUp, MazeMath::round(roundUp));
+ EXPECT_DOUBLE_EQ(expectDown, MazeMath::round(roundDown));
+}
+
+/**
+* @brief Tests that a PlayerCar can be created and destroyed repeatedly without incident.
+*
+* This tests that the PlayerCar can handle its own dependencies on Allegro, even when
+* copy constructors are used (through vector).
+*/
+TEST(PlayerCar, creationAndDestructionCanHappen)
+{
+ EXPECT_NO_THROW({
+ vector<PlayerCar> players;
+ for (int i=0; i<10; ++i)
+ {
+ players.push_back(PlayerCar(i,i));
+ }
+ while(!players.empty())
+ {
+ players.pop_back();
+ }
+ });
+
+ EXPECT_FALSE(al_is_system_installed());
+}
+
+/**
+* @brief Tests that the player's petrol runs out, and that the player's speed is halved after that.
+*
+* Also tests that the speed is recovered if petrol is increased again.
+*/
+TEST(PlayerCar, playerSpeedAffectedByPetrol)
+{
+ Maze testMaze;
+ vector<pair<int,int> > walls;
+ testMaze.generateMaze(walls,5,5);
+
+ PlayerCar player(5,3,Maze::RIGHT);
+
+ list<Smokescreen> smokescreens;
+ for (int i=0; i<1429; ++i)
+ {
+ player.update(testMaze, smokescreens);
+ }
+
+ EXPECT_FLOAT_EQ(0, player.petrol());
+ EXPECT_FLOAT_EQ(0.05, player.speed());
+
+ player.gotCheckpoint();
+ EXPECT_GT(player.petrol(), 0);
+ EXPECT_FLOAT_EQ(0.1, player.speed());
+}
+
+/**
+* @brief Tests that a Smokescreen is destroyed 60 frames after it is created.
+*/
+TEST(Smokescreen, destroyedAfterSetTime)
+{
+ Smokescreen testSmokescreen(3,2);
+
+ int i=0;
+ while (!testSmokescreen.destroyed())
+ {
+ testSmokescreen.update();
+ ++i;
+ }
+
+ EXPECT_EQ(60,i);
+}