Introduction to C# Unit Testing in Unity
Creating reliable, reusable tests to aid rapid development
Recently I completed a challenge to rotate the elements of a matrix clockwise or counter-clockwise by 45 degrees. In other words, I needed to be able to make this:
Turn into this:
Here is a scenario where I know exactly what kind of output I needed from the method before I write it — which makes it the perfect candidate for unit testing.
A unit test is exactly what it sounds like. It’s a test that verifies the operation of a unit of code. You might be familiar with using Unity’s Debug.Log() method to verify the operation of your code as you develop. This is similar, but comes with several added advantages.
- Unit testing happens outside of the method itself. If you change the method, the test will still be valid because it isn’t dependent on the contents of the method. It’s just concerned with the the input/output (if any) or behavior of the code.
- Unit testing can happen outside of play mode and without any input from the user. This makes getting to the point you want to test rapid, and feedback instantaneous.
- You can organize your tests into suites of similar tests working on the same game system. If one of them fails, the whole suite fails.
Let’s take a look at how to use the Test Runner module of Unity to create and organize our unit tests.
The Test Runner
When you first open Window > General > Test Runner you will see something like the above screen. There are two tabs — one for Play Mode Testing and one for Edit Mode. I’ll be focusing on Edit Mode, as I want to run my tests without running the game.
To get started, click the Create EditMode Test Assembly Folder. This will create a new folder and an Assembly Definition Asset (.asmdef) file inside it. This file tracks references to the folders where you are storing the scripts you wish to test. Before we get to that, let’s click Create Test Script in Current Folder. It’s good practice to name this script after the class holding the methods you intend to test. So, in my case, I’ll call it the RotateMatrixTests script.
We also need to create an Assembly Definition Asset in the folder where the RotateMatrix test is found. To do that, right click in the scripts folder and select Create > Assembly Definition.
As you can see above, I named mine RotateMatrix.Scripts. This naming convention is based on the following template:
[Project Title].[First Folder in Tree (excluding Assets)].[…additional folders in tree as necessary]
In my case, the project title is Rotate Matrix, and the script I’m testing is in Assets/Scripts/. So the name of my Assembly Definition for that folder is “RotateMatrix.Scripts”
Once you have the Assembly Definition Asset created, go back to the Tests.asmdef file in the Scripts/Tests/ folder and view it in the inspector. Under Assembly Definition References, click the + sign and drag the asmdef file you just created in the Scripts/ folder into the slot.
Now, if you’ve followed all these steps, you should be ready to open the RotateMatrixTests script and start testing! Clear out the default test methods, and create a new method named after the method you intend to test. I’m testing my RotateClockwise(int[,] matrix) and RotateCounterClockwise(int[,] matrix) methods, so I’ll call these RotateClockwiseTest() and RotateCounterClockwiseTest():
Notice each has the [Test] property. This tells Unity to display these tests in the Test Runner window.
Now, in order to run the rotate clockwise method we need to provide it with a 2D array to fiddle with. So the first thing we need is to define such an array.
Now, the matrix variable is defined as something that looks just like this:
Next, we need to get a reference to the RotateMatrix script instance in the scene so we can access the method to test:
The last ingredient is to call the Assert() class. This is the class that holds the logic for checking our output. And what kind of output do we expect? Well, we can check two corners of the original matrix and compare them to the corresponding destination points in the rotated matrix. If the values have moved to the positions we expect, then the rotation is successful.
So, the value originally contained in matrix[0,0] should equal the value of result[0,1] — that is, one column to the right of where it started. Likewise, the value in the original matrix at the maximum of both axes should equal the value in the result matrix at the maximum of the x axis and the maximum less one of the y axis — one row higher than when it started. Here’s one way to write an assert to test these conditions:
If either of these conditions fail, we’ll get an explanation of which corner doesn’t match in the debug log.
I’m now ready to try and work out the solution to the problem over in my RotateClockwise method. I can try any number of approaches, and instantly test the method to see if it gives the expected output. Iterating on this problem just became 1000% easier.
If you’re curious, this is the method I finally came up with:
When you’re ready to test out your method, all you need to do is double click the test or test suite you wish to run in in the Test Runner (or click “Run All” to test everything in all test suites).
As you have seen, unit testing can be a powerful way to get the most out of your time at the keyboard. I hope this brief introduction to unit testing has been useful to you!