Unity Test Runner

Unity test runner!

As the game grows in size it takes longer to verify that new changes did not break any existing behaviour. Where at some point you stop testing and might even outsource it to a QA person. As a consequence issues will be found later in the development process. Now I will not go into details why this is bad, but you can introduce automated tests to cover basic functionality that gets tested over and over and get your feedback within a minute after a code change.

Test types

There are two types of tests:

  • Editmode - editor only, can be used for testing editor related tasks like building lightmaps, assetbundles and verifying assets integrity
  • Playmode - all platforms, everything that you want to be executed during playmode

Both of them look almost identical. Editmode tests should be placed under Editor folder, otherwise you will not be able to build your project. Tests will be playmode or editmode depending on whether they are in Editor folder or not. Test methods should be marked with [Test] for single frame tests and [UnityTest] for tests over period of time where you can use yield to wait or skip a frame.

Project setup

In the examples I am using a scene with nothing but a default unity cube in it.

Editmode test example

Verifies that a GameObject called “cube” exists in the scene.

1
2
3
4
5
6
7
[Test]
public void VerifySceneContents()
{
EditorSceneManager.OpenScene("Assets/TestScene.unity", OpenSceneMode.Single);
var go = GameObject.Find("cube");
Assert.IsNotNull(go, "No cube object found in scene.");
}

Monobehaviour tests

If you would like to execute your test from a MonoBehaviour, you can do that! On a MonoBehaviour you have to define when the test ends implementing interface called IMonoBehaviourTest. When executed a gameobject will be created and your MonoBehaviourTest is attached to it. To get a reference to the object you have to do a GameObject.Find() which in the future should be accessible from a variable.

Example test using MonoBehaviour:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[UnityTest]
public IEnumerator TestBehaviour()
{
int framesToMeasure = 10;
var test = new MonoBehaviourTest<FramerateTestable>();
var gameObject = GetTestObject<FramerateTestable>();
var framerate = gameObject.GetComponent<FramerateTestable>();
framerate.framesToMeasure = framesToMeasure;

yield return test;

Assert.IsTrue(framerate.frame > framesToMeasure);
}

public static GameObject GetTestObject<T>() where T : IMonoBehaviourTest
{
return GameObject.Find("MonoBehaviourTest: " + typeof(T).FullName);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class FramerateTestable : MonoBehaviour, IMonoBehaviourTest
{
public int framesToMeasure = 5;
public int frame = 0;

void Update()
{
frame++;
if (frame > framesToMeasure) IsTestFinished = true;
}

public bool IsTestFinished { get; private set; }
}

Scene based tests

If scenes are necessary use EditorSceneManager for editmode and SceneManager for playmode tests. Both loading modes are supported.

Example playmode test using scene:

1
2
3
4
5
6
7
8
9
10
11
12
13
[UnityTest]
public IEnumerator Scenebased()
{
SceneManager.LoadScene("TestScene", LoadSceneMode.Single);
yield return null;
var cube = GameObject.Find("cube");
var renderer = cube.GetComponent<Renderer>();

Material mat = new Material(Shader.Find("Specular"));
renderer.sharedMaterial = mat;

Assert.AreSame(renderer.sharedMaterial, mat);
}

Setup

Sometimes you want to do extra work before the test run. In that case you can use IPrebuildSetup interface. As an example test, building assetbundles before a test which loads them.

Excluding platforms

In case your test is not supposed to run on all platforms use a UnityPlatform attribute. Usage goes like this:

1
[UnityPlatform(exclude = new[] {RuntimePlatform.WindowsEditor })]