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.

5 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

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:

  1. You’re creating an asteroid and a laser, and you’re making sure they have the same position to trigger a collision.
  2. 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 the UnityEngine.Assertions.Assert, not the NUnit Assert.

Return to the Test Runner and run this new test. You’ll see that satisfying green check mark.

LaserDestroysAsteroid

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.

Note: Deciding whether to test only public methods or also private methods is something you need to consider. Some people believe that private methods should only be tested through the public methods that use them. This can make the “unit” of code you need to test quite large, and might not be desirable. On the flip side, testing private methods can be problematic and requires special frameworks or using reflection tools to check things. Each scenario has its pros and cons — and that’s beyond the scope of this tutorial. This tutorial will set all methods to be tested to public to make things easier to follow — so don’t use this project as a best practices reference when it comes to production code!

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:

  1. 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.
  2. 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:

DestroyedAsteroidRaisesScore

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.

Code Coverage

Once the package has installed, open the Code Coverage window by selecting Window ▸ Analysis ▸ Code Coverage.

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.

Switch to debug mode, 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!

Note: This tutorial was created using Unity version 2021.3.18f1. The layout and options of the Code Coverage screen have since changed and Auto Open Report may no longer be available for your version of the editor, in which case you can ignore this.

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.

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.

Ship coverage

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.

Improved coverage