Introduction To Unity Unit Testing
Learn all about the Unity Test Framework and how to set up Unit Tests in your Unity projects. By Ben MacKinnon.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction To Unity Unit Testing
35 mins
- What is a Unit Test?
- Looking at Example Unit Tests
- Getting Started
- Getting Started with the Unity Test Framework
- Setting Up Test Folders
- What’s in a Test Suite?
- Setting Up the Test Assembly and the Test Suite
- Writing Your First Unit Test
- Passing Tests
- Adding Tests to the Test Suite
- Setting Up and Tearing Down Phases
- Testing Game Over and Laser Fire
- Asserting Laser Movement
- Ensuring Lasers Destroy Asteroids
- To Test or Not To Test
- Unit Testing Pros
- Unit Testing Cons
- Testing that Destroying Asteroids Raises the Score
- Code Coverage
- Where to Go From Here?
Passing Tests
Great job! You’ve written your first unit test, but how do you know if it works? The Test Runner of course! In the Test Runner window, expand all the arrows. You’ll see your AsteroidsMoveDown
test in the list with a gray circle:
The gray circle means the test hasn’t yet been run. When a test is run and passes, it’ll show a green arrow. If a test fails, it’ll show a red X. Run the test by clicking the RunAll button.
This will create a temporary scene and run the test. When it’s done, you’ll see that the test passed.
Congratulations! You successfully created your first passing unit test, and it verifies that spawned asteroids move down.
Adding Tests to the Test Suite
The next test will test the game-over logic when the ship crashes into an asteroid. With TestSuite.cs open in the code editor, add the following test below the first unit test and save:
[UnityTest]
public IEnumerator GameOverOccursOnAsteroidCollision()
{
GameObject gameGameObject =
Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
//1
asteroid.transform.position = game.GetShip().transform.position;
//2
yield return new WaitForSeconds(0.1f);
//3
Assert.True(game.isGameOver);
Object.Destroy(game.gameObject);
}
You saw most of this code in the last test, but there are a few different things here:
- You’re forcing an asteroid and ship to crash by explicitly setting the asteroid to have the same position as the ship. This will force their hitboxes to collide and cause game over. If you’re curious how that code works, look at the Ship, Game and Asteroid files in the Scripts folder.
- A time-step is needed to ensure the Physics engine collision event fires so a 0.1 second wait is returned.
- This is a truth assertion, and it checks that the
gameOver
Boolean in the Game script has been set to true. The game code works with this flag being set to true when the ship is destroyed, so you’re testing to make sure this is set to true after the ship has been destroyed.
Go back to the Test Runner window, and you’ll now see this new unit test list there.
This time, you’ll only run this one test instead of the whole test suite. Click GameOverOccursOnAsteroidCollision, then click the Run Selected button.
And there you go — another test has passed. :]
Setting Up and Tearing Down Phases
You might have noticed there’s some repeated code between the two tests where the Game’s GameObject is created and a reference to where the Game script is set:
GameObject gameGameObject =
Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
You’ll also notice it when the Game’s GameObject is destroyed:
Object.Destroy(game.gameObject);
It’s very common in testing to have this type of code — where you create the test environment and then clean it up at the end. But, it’s also good practice to keep your code DRY!
The Unity Test Framework provides two more attributes to help when it comes to running a unit test: the Setup phase and the Tear Down phase.
Any code inside of a Setup method will run before any unit test in that suite, and any code in the Tear Down method will run after every unit test in that suite.
It’s time to move this setup and tear down code into special methods. Open the code editor and add the following code to the top of the TestSuite file, just above the first [UnityTest] attribute:
[SetUp]
public void Setup()
{
GameObject gameGameObject =
Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
}
The SetUp
attribute specifies that this method is called before each test is run.
Next, add the following method and save:
[TearDown]
public void Teardown()
{
Object.Destroy(game.gameObject);
}
The TearDown
attribute specifies that this method is called after each test is run.
With the setup and tear down code prepared, remove the lines of code that appear in these methods and replace them with the corresponding method calls. Your code will look like this:
public class TestSuite
{
private Game game;
[SetUp]
public void Setup()
{
GameObject gameGameObject =
Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
}
[TearDown]
public void TearDown()
{
Object.Destroy(game.gameObject);
}
[UnityTest]
public IEnumerator AsteroidsMoveDown()
{
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
float initialYPos = asteroid.transform.position.y;
yield return new WaitForSeconds(0.1f);
Assert.Less(asteroid.transform.position.y, initialYPos);
}
[UnityTest]
public IEnumerator GameOverOccursOnAsteroidCollision()
{
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
asteroid.transform.position = game.GetShip().transform.position;
yield return new WaitForSeconds(0.1f);
Assert.True(game.isGameOver);
}
}
Testing Game Over and Laser Fire
With the setup and tear down methods ready, it’s the perfect time to add more tests using them. The next test will verify that when the player clicks New Game, the gameOver bool is not true. Add the following test to the bottom of the file and save:
//1
[Test]
public void NewGameRestartsGame()
{
//2
game.isGameOver = true;
game.NewGame();
//3
Assert.False(game.isGameOver);
}
This will look familiar, but here are a few things to notice:
- This test won’t require any time to pass, so it uses the standard [Test] attribute, and the method type is just
void
. - This part of the code sets up this test to have the
gameOver
bool set to true. When theNewGame
method is called, it will set this flag back tofalse
. - Here, you assert that the
isGameOver
bool isfalse
, which should be the case after a new game is called.
Go back to the Test Runner, and you’ll see the new test NewGameRestartsGame is there. Run that test as you’ve done before and see that it passes:
Asserting Laser Movement
The next test you add will test that the laser the ship fires moves up (similar to the first unit test you wrote). Open the TestSuite file in the editor. Add the following method and then save:
[UnityTest]
public IEnumerator LaserMovesUp()
{
// 1
GameObject laser = game.GetShip().SpawnLaser();
// 2
float initialYPos = laser.transform.position.y;
yield return new WaitForSeconds(0.1f);
// 3
Assert.Greater(laser.transform.position.y, initialYPos);
}
Here’s what this code does:
- This gets a reference to a created laser spawned from the ship.
- The initial position is recorded so you can verify that it’s moving up.
- This assertion is just like the one in the
AsteroidsMoveDown
unit test, except now you’re asserting that the value is greater (indicating that the laser is moving up).
Save and go back to the Test Runner. Run the LaserMovesUp test and see that it passes:
Now you’re firing through these test cases, so it’s time to add the last two tests and finish off this tutorial. :]