Unit Tests & TDD
Tests : introduction
Tests are important in software development because they assure a minimal software quality A lot of software are designed without testing and the consequences are:
- a lot of bugs
- bugs in prod and difficult to solve
- software grow with patch and new features is based on dirty code
Most of the time, an untested code leads to technical debt.
The tests pyramid presents different type of tests :
-
HMI (Human-machine interface) tests are expensive because the application must be launched entirely. This tests can be automatized but to long to execute. They cover a few of scenario. Sometimes, we haven’t automatized test and we engage tester.
-
Integration tests are easy. They are close to the code, so refactor is easier. They cover much code and they can impact directly by modification
-
Unit tests are very easy to take in place when they make in parallel of the code. They are the base of testing software and they give feedback quickly. They cover the major part of the code and are cheap. They can be automated and they must be quick to execute.
Warning : Think to separate unit test (quick) to integration test (slower).
We are interested to unit testing and test driven development (TDD)
Write a unit test serves to check the behaviour code and with TDD, write a unit test tell us how code must work.
Unit tests
Unit tests are in a test project. And it must have the same architecture as the software who test. A Software Class = A Test Class.
Why you should write unit tests?
- To be sure not to go back
- Reduce bugs in features (new or old)
- Easy refactoring code and fear about modification or new features in the project.
- Have a quick feedback
- more “robust” application
- minimal documentation
In fact, more quickly you know tests fail, more quickly you can correct the problem and resolve are cheaper.
What is a good unit test?
A good unit test:
- is fully automatized
- return systematically the same result
- test one concept or one logic
- name is clear and easy to understand
How writes a unit test?
Different naming convention exists:
- Should_ExpectedBehavior_When_StateUnderTest
- When_StateUnderTest_Expect_ExpectedBehavior
Be careful to not have the name of the method in the name of the test because when you change the method name, the name of the test does not mean anything anymore. The test can be more difficult to maintain.
A unit test expresses an intention, it tests a feature and no an implementation.
A test has decomposed into three parts :
- Arrange : initialize step
- Act : call of testing method
- Assert : verification step
When you write a test, you can begin by the Assert step. The assertion is the answer of “What do you want to test?”
How to put unit tests into practice with Unity ?
Unity Test Runner
For make unit test, Unity creates a tool call Unity Test Runner.
To display Test Runner window, Windows > Tests Runner.
There are two modes :
-
PlayMode :
- Tests are executed in many frames
- Behaviour Awake(), Start(), … are automatically executed
-
Is used for Integration tests
-
[UnityTest] is executed like a Coroutine
- Open a tests scene for execution of tests ( Save your scene before launch tests)
-
EditMode :
- Tests are executed in one frame
- **Tests must be placed in Editor folder**
- [UnityTest] is executed in editor with **"Editor.Application.Update"**
Select your favorite mode and click on “Create PlayMode/EditMode Test Assembly Folder”.
Unity create Test folder with “Tests.asmdef file” (asmdef =“assembly definition”).
Once the folder has been created, go inside and click on “Create Test Script in current folder”.
A .cs file is created by Unity with the code bellow :
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
public class NewTestScript {
[Test]
public void NewTestScriptSimplePasses() {
// Use the Assert class to test conditions.
}
// A UnityTest behaves like a coroutine in PlayMode
// and allows you to yield null to skip a frame in EditMode
[UnityTest]
public IEnumerator NewTestScriptWithEnumeratorPasses() {
// Use the Assert class to test conditions.
// yield to skip a frame
yield return null;
}
}
Tests must display in the Test Runner:
if you click on “Run All”, tests pass green.
Now, if you replace the first test by :
[Test]
public void NewTestScriptSimplePasses() {
// Use the Assert class to test conditions.
Assert.AreEqual(4, 3);
}
this test must be red because 3 and 4 are not equals.
Today, Unity allow launching tests in command lines.
You can launch tests with your IDE (Visual Studio, Rider , …) too.
Tests Scripts
The Class name must be prefixed or suffixed by “Test”. By default, Unity uses the C# unit test framework NUnit.
Before each method of test, the attribute [Test] must be written :
[Test]
public void Method_Test() {
...
}
With UnityEngine.TestTools; you can write “Unity Tests” :
[UnityTest]
public IEnumerator Methode_Unity_Test() {
...
yield return null;
...
}
A Unity Test is a Coroutine. Usually Coroutines are used for make Asynchronous processing. For example, with a Coroutine, you can wait a frame (yield return null) or wait x seconds (yield return new WaitForSecondes(x)) before or during or after instructions.
To realize an assertion with NUnit, you must use the Assert class, few examples:
Assert.IsNotNull(object);
Assert.IsTrue(boolean);
Assert.AreEqual(value1, value2)
For “Assert.AreEqual”, value1 is the expected value and value2 is the tested value. It’s important for having good error message (“Expected “value1” But was “value2”).
Unity have added their own assertions (with using UnityEngine.Assertions; and UnityEngine.TestTools.Utils;) like :
- “Assert.AreApproximatelyEqual” and “Assert.AreNotApproximatelyEqual” with a default tolerance equals to 0.00001f. They can be used for compare two float like position in x axis.
- “ColorEqualityComparer”
- “QuaternionEqualityComparer”
- “Vector2EqualityComparer” ,”Vector3EqualityComparer” et “Vector4EqualityComparer”
If your tests have the same “Arrange” part, therefore you can use [SetUp] and [TearDown] attributes. the [SetUp] method is executed before all [Test] methods and the [TearDown] method is executed after all [Test] methods.
If we have:
public class MyTestClass {
[Test]
public void Method_Test_0() {
//Arrange
GameManager game = new GameManager();
...
//Act
...
//Assert
...
}
[Test]
public void Method_Test_1() {
//Arrange
GameManager game = new GameManager();
...
//Act
...
//Assert
...
}
}
we can write :
public class MyTestClass {
GameManager game;
[SetUp]
public void Init(){
game = new GameManager();
}
[TearDown]
public void Dispose() {
Destroy(game);
}
[Test]
public void Method_Test_0() {
//Arrange
...
//Act
...
//Assert
...
}
[Test]
public void Method_Test_1() {
//Arrange
...
//Act
...
//Assert
...
}
If you want instantiate a Monobehaviour object, it’s better to write:
MyComponent myComponent = new GameObject.AddComponent<MonComponent>();
then:
MyComponent myComponent = new MonComponent();
Source: Unite Austin 2017 - Testing for Sanity: Using Unity’s Integrated TestRunner, https://www.youtube.com/watch?v=MWS4aSO7HAo
Test-driven developpement (TDD)
TDD or Test-driven development is a method which consists in writing tests before code. With TDD, unit test is not to validate behaviour, but to specify the behaviour of the future code.
TDD can be resumed like this:
In the book “The Clean Coder”, Uncle bob defines the 3 laws of TDD (p.80) :
-
- You are not allowed to write any production code until you have first written a failing test
-
- You are not allowed to write more of a unit test than sufficient to fail – and not compiling is failing
-
- You are not allowed to write more production code that is sufficient to pass the currently failing unit test
For more precision about TDD: