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?
Ensuring Lasers Destroy Asteroids
Next, you’re going to make sure that a laser will destroy an asteroid if it hits it. Open the editor and add the following test at the bottom of TestSuite and save:
[UnityTest]
public IEnumerator LaserDestroysAsteroid()
{
// 1
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
asteroid.transform.position = Vector3.zero;
GameObject laser = game.GetShip().SpawnLaser();
laser.transform.position = Vector3.zero;
yield return new WaitForSeconds(0.1f);
// 2
UnityEngine.Assertions.Assert.IsNull(asteroid);
}
Here’s how this works:
- You’re creating an asteroid and a laser, and you’re making sure they have the same position to trigger a collision.
- This is a special test with an important distinction. Notice how you’re explicitly using UnityEngine.Assertions for this test? That’s because Unity has a special Null class that’s different from a “normal” Null class. The NUnit framework assertion
Assert.IsNull()
will not work for Unity null checks. When checking for nulls in Unity, you must explicitly use theUnityEngine.Assertions.Assert
, not the NUnitAssert
.
Return to the Test Runner and run this new test. You’ll see that satisfying green check mark.
To Test or Not To Test
Committing to unit tests is a big decision, and it shouldn’t be taken lightly. However, the payoffs can certainly be worth it. There’s even a methodology of development known as Test Driven Development (TDD).
With TDD, you actually write tests before you write your application logic. You make tests first, ensure they fail, and then only write code designed to get the test to pass. This can be a very different approach to coding, but it also ensures you’ve written your code in a testable way.
Testing can be a big commitment, so it’s worth taking a look at the pros and cons of adding unit testing to your project.
Unit Testing Pros
There are a lot of important upsides to unit testing, including:
- Provides confidence that a method behaves as expected.
- Serves as documentation for new people learning the code base (unit tests make for great teaching).
- Forces you to write code in a testable way.
- Helps you isolate bugs faster and fix them quicker.
- Prevents future updates from adding new bugs to old working code (known as regression bugs).
Unit Testing Cons
However, you might not have the time or budget to take on unit testing. These are the downsides you should also consider:
- Writing tests can take longer than writing the code itself.
- Bad or inaccurate tests create false confidence.
- Requires more knowledge to implement correctly.
- Important parts of the code base might not be easily testable.
- Some frameworks don’t easily allow private method testing, which can make unit testing harder.
- If tests are too fragile (fail too easily for the wrong reasons), maintenance can take a lot of time.
- UI is hard to test.
- Inexperienced developers might waste time testing the wrong things.
- Sometimes, testing things with external or runtime dependencies can be very hard.
Testing that Destroying Asteroids Raises the Score
Time to write the last test. With the code editor open, add the following to the bottom of the TestSuite file and save:
[UnityTest]
public IEnumerator DestroyedAsteroidRaisesScore()
{
// 1
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
asteroid.transform.position = Vector3.zero;
GameObject laser = game.GetShip().SpawnLaser();
laser.transform.position = Vector3.zero;
yield return new WaitForSeconds(0.1f);
// 2
Assert.AreEqual(game.score, 1);
}
This is an important test that makes sure that when the player destroys an asteroid, the score goes up. Here’s how it breaks down:
- You’re spawning an asteroid and a laser, and you’re making sure they’re in the same position. This ensures a collision occurs, which will trigger a score increase. This code is the same as the previous test.
- This asserts that the game.score int is now 1 (instead of the 0 that it starts at).
Save your code and go back to the Test Runner to run this last test and see that it passes:
Amazing work! All the tests are passing.
Code Coverage
With six different unit tests covering what is a fairly basic project, you would think that there’s pretty good coverage of the code base. Fortunately, Unity also provides another tool that will show you exactly how much of the code is covered by unit tests. What’s more, it will show you how this coverage changes over time between test runs. So as you add more code, your coverage may go down. And as you add tests, coverage should go up!
Time to take a quick look at the Code Coverage package. Open the Package Manager window once more from Window ▸ Package Manager and select Unity Registry from the drop-down. Scroll down the list to find the Code Coverage package and install it to the project.
Once the package has installed, open the Code Coverage window by selecting Window ▸ Analysis ▸ Code Coverage.
When you open this window for the first time, you may see a warning that Code Coverage requires the Unity Editor to be in Debug mode. If so, click the button to switch to debug mode.
Next, check the box to Enable Code Coverage.
The rest of the settings are fine as they are, but the two important ones here are Auto Generate Report and Auto Open Report. With those two options selected you can go straight into generating your first report!
Head back to the Test Runner window and select Run All to re-run all your tests. Once the Test Runner is complete, a window will open by default in the path that’s set in the Code Coverage window. The file index.html will be selected by default. Open this file in your default browser to view the code coverage report.
You can see in the report that with just six test cases, you have already covered more than 70% of the game’s code base! With a quick glance down the different scripts listed inside the GameAssembly, you can see that most have good coverage. But the Ship class could definitely use some further coverage…
Click on the Ship class in the list, and it will open a new view that shows you all the lines of code and highlights those that are and are not tested. Scroll down to the bottom of the class and you will see SpawnLaser which you called in a few of your tests. Notice it is marked green.
Also near the bottom of the class are Explode and RepairShip. They’re marked green too, but you didn’t write explicit test cases for those! However, Explode is called from GameOver, which you wrote a test for, and RepairShip is called from NewGame, which you also wrote a test case for. These methods have been covered by extension of existing tests.
You can also see from your test report that there are two player movement methods that remain untested. Try writing test cases for these (use the AsteroidsMoveDown test case as a reference), and then check your coverage report again. Note that if you only run the new test, the coverage report will also only cover that test — so you want to Run All to get the full report.
Your new report should show some improved coverage for the Ship class and overall.