Signup/Sign In

Test Driven Development (TDD) in Unity 3d

Test-driven development (TDD) is the practice of writing automated tests for a piece of code before writing the code itself. This basically means that we create and refactor (changing structure without changing behaviour) our project code on the basis of tests. The process of writing the code, testing the code and refactoring it all follow each other in a loop, until a satisfactory state is reached.

The following sequence of steps is generally followed:

  • Add a test (that will initially fail)
  • Run all tests and see if the new one fails
  • Write some code
  • Run tests
  • Refactor code
  • Repeat

Following this workflow speeds up the process of refactoring code and making changes, because you can see straight away what has broken and why.

You may wonder why we write the test before writing the code itself. This is because writing tests after writing the code can often lead to developers writing tests to make them pass. When you write a failing test first, you’re making sure that it fails for a good reason (such as not implementing the required functionality correctly), as well as ruling out false positives.


Red-Green Refactor Loop

Red-green-refactor-loop

The most important thing to keep in mind is the red-green-refactor loop. It is the heart and soul of the TDD process. Here each step has a meaning:

  1. RED: It refers to writing a test case that will definitely fail.
  2. GREEN: It refers to changing/writing code that will make the test created in the “RED” phase pass in such a way that all the tests that passed previously will still pass. This means that the developer must not break the working of the project to just pass one test.
  3. REFACTOR: It refers to eliminating redundancy, increasing readability, etc of the code written.

These steps are repeated over and over again until all tests pass or encountering a failing test is impossible/improbable.


Unit Testing

UNIT TESTING is a level of software testing where individual units/ components of a software are tested. The purpose is to validate that each unit of the software performs as designed. A unit is the smallest testable part of any software. It usually has one or a few inputs and usually a single output.

Time for an Example!

Now what we believe is that action speaks louder than words. Hence, performing a task is the best form of learning. Hence what we will aim for is to set up a basic unit testing scenario were we can test out whether a function of a particular class performs as expected.

We will create a quadratic value solver to which when given an input as ‘x’, we receive the value of f(x).

For eg. Let f(x) = x2 -4x+4 now for a value of x=2, we get f(2)=0.

We aim to create this using a TDD process.

Now in Unity, after opening a new project, click on Window > Test Runner in the menu bar.

Now a window like this appears.

Red-green-refactor-loop

This is the inbuilt Unit Tester Module of Unity. We will avail of its services for TDD.

Attach this tab by dragging it next to the inspector (do it yourself).

Now we have to know that the testing scripts are not same as our regular scripts. To make the unity editor treat a particular script as a Test Script, the first step is to save the script into a particular folder named “Editor” (without quotes).

Thus create an “Editor” folder in the Assets and inside it create a script called “FunctionTester”

Editor

Assets Folder

Now that the setup is complete, we will start with the development process. Here we will be utilizing the Red-Green-Refactor loop. Thus first we will write a test that fails (it is essential that we make the test fail). After that we will try to correct the code so that the test gives a positive result. Then finally we will try to make our code as clean and as readable as possible.

Open the FunctionTester script and type the following code in it.

using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
[TestFixture]
public class FunctionTester
{
    [Test]
    public void T00_PassingTest () 
    {
        Assert.AreEqual (1, 0);
    }
}

Now in the Test Runner tab, click on Run All.

Run all Image

This is the completion of our first Red state. The place where the test failed is mention at the bottom of the tab.

Now for the Green state we need to correct the code so that the case tests true. To do this, change line 11 of the code to

Assert.AreEqual (1, 1);

Now on clicking on Run All all the tests return true.

We do not need a refactoring state in this iteration as the code is in its simplest form. Only the refactoring state can be skipped if the developer feels the code is satisfactory.

Now you must be thinking what does this code do. s

Here in line 5, we describe the class as a [TestFixture]. This tells Unity that this class is our Tester class and will be used for Unit Testing.

Now we will be performing each of our tests as functions. Before the definition of the function we mention that it is a [Test]. This allows the Test Runner to access it and show us the result.

The first test, as you can see is pretty trivial, but it is a good practice to write the first test as trivial as it makes sure that the testing system is functioning properly.

[Test]
public void T00_PassingTest () 
{
    Assert.AreEqual (1, 0);
}

Assert.AreEqual is a function of the Assert class from the Nunit library. It checks wheter the 2 objects passed into it as equal or not. An AssertionException is thrown if the objects are unequal.

Image of Edit Mode Options

This ends our first iteration.

Next we will test that when x=2, we should get a value of 0 from the quadratic equation.

Now in the second iteration we will modify the code as follows:

using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
[TestFixture]
public class FunctionTester
{
    public Function function = new Function();
    [Test]
    public void T00_PassingTest () 
    {
        Assert.AreEqual (1, 1);
    }
    [Test]
    public void T01_X2Y0()
    {
        Assert.AreEqual(function.Value(2f) ,0f);
    }
}  

Here we’ve started the second RGR(Red-Green-Refactor) loop. On running this test, we will get a fail on the test T01.

Now you will see that this does not compile. This is because we do not have a Function class. This is where our real testing begins.

Now you will see that this does not compile. This is because we do not have a Function class. This is where our real testing begins.

Inside it type code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Function 
{
    public float Value (float x)
    {
        return 8f;
    }
} 

Now on running the test we get a failure.

Image of Edit Mode Options

Moving toward the green state, we will change line 9 of the Function script to

return 0f;

Now on running the test again we will get a positive result

You must be thinking now that why didn’t we just compute anything in the function. You have to realise that the one and only objective that we have in TDD is to make all the tests pass and this method passes the tests just fine.

In our next iteration we will test for when x = 0, f(0) = 4. Implement the test in the FunctionTester for this test iteration (Challenge!). Try not to copy the code and write it yourself. Remember, mistakes are the best teachers.

View the code below and compare for errors:

using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
[TestFixture]
public class FunctionTester
{
    public Function function = new Function();
    [Test]
    public void T00_PassingTest () 
    {
        Assert.AreEqual (1, 1);
    }
    [Test]
    public void T01_X2Y0()
    {
        Assert.AreEqual(function.Value(2f) ,0f);
    }
    [Test]
    public void T02_X0Y4()
    {
        Assert.AreEqual (function.Value (0f), 4f);
    }
}  

This test will fail as our Value function returns 0f irrespective of input.

Now for the green state. What we want is the value function to compute the quadratic equation value of x where f(x) = x2-4x+4.

Thus we will change our Value function to:

public float Value (float x)
{
    return (Mathf.Pow (x,2) - (4f*x) + 4f);
}

Now we reach the refactor state. Looking at our function, we can see that its pretty small so refactoring doesn’t effect our code much. Refactoring becomes important in larger scripts.

Now as a task, try and write as many tests as possible in the FunctionTester class and follow the RGR loop to make the tests pass. The more the number of tests, the higher is the accuracy of the module.

Image of Edit Mode Options

Image of Edit Mode Options


What to do if a Previous Test fails during Green State?

Consider the possibility that to make a particular test pass, you make changes to the script. This change causes one of the previous tests, which was passing before, to fail. If this occurs, rather than moving on to the next text, we will again enter the RED state for THAT test case. We will move on to new test cases only if all previous test cases are passing.

Image of Edit Mode Options


Conclusion

This was an example of creating an extremely basic and trivial projects by TDD. This may seem like a pain in the example, but when scaled up (for larger projects) TDD is a boon. It helps to identify and correct errors in a fast and effective manner. So try to use it in projects where lots of scenarios can occur. This also helps to keep track of all scenarios that have been tested.