About RMC & Unity3D
Rivello Multimedia Consulting (RMC) provides consulting services for applications and games. RMC specializes in Unity3D development (see our work here). Please contact us today with any questions, comments, and project quotes.
As always, RivelloMultimediaConsulting.com/unity/ will be the central location for deep articles and tutorials, Facebook.com/RivelloMultimediaConsulting (like us!) will engage the growing RMC+Unity community, and for the latest opinions and cool links follow me at Twitter.com/srivello.
Update: December 18, 2013, Unity Technologies releases the official “Unity Test Tools.” Read the full post below, then jump over to “Unity3D Unit Testing 2: Unity Test Tools“.
Intro To Unit Testing
As the author of the Art Of Unit Testing begins, “I used to feel that a ‘unit’ was the smallest possible part of a code base (a method, really). But in the past couple of years I’ve changed my mind”. Unit Testing is a fundamental of Extreme Programming too. Here’s how I define a unit test;
A unit test is an automated piece of code that invokes a unit of work in the system and then checks a single assumption about the behavior of that unit of work.
A unit of work is a single logical functional use case in the system that can be invoked by some public interface (in most cases). A unit of work can span a single method, a whole class or multiple classes working together to achieve one single logical purpose that can be verified.
A good unit test is:
- Fully automated
- Consistently returns the same result
- Tests a single logical concept in the system
Reasons To Write Unit Tests
Development experts urge us all to test. Here are some top reasons;
- Tests Reduce Bugs in New Features
- Tests Reduce Bugs in Existing Features – With well-tested code, introducing new features rarely breaks existing functionality.
- Tests Are Good Documentation – A concise code example is better than many paragraphs of documentation.
- Tests Allow Refactoring \ Reduce the Cost of Change – Be more nimble. Experiment with confidence.
- Testing Forces You to Slow Down and Think
- Tests Reduce Fear – Having a complete test suite allows programmers to remove the fear of making changes or adding new features.
Unit Testing in Unity
So in Unity, we have three levels that need to be continuously tested:
- C# code
- Prefabs and single configured objects
- Scene definitions for the final game – also known as levels.
There are some challenges to each, however the 3rd is probably the most tricky since much of its setup is done in visually through the editor (vs in C#). One tip is to configure all Game-Objects as prefabs, so that the scenes only contain a minimum of manual definitions. Some greater thought on the subject is outlined in this post.
The Unity Technologies team (creators of the Unity IDE) announced in 2013 Unity’s Runtime API Test Framework. While this is a great sign of the teams dedication to TDD (see next section) and to create tested, durable code for us to use, this framework is not available for us developers to use. The team released this chart showing how much of the Unity code-base is currently covered by testing (see Figure 1).
Test Driven Development (TDD)
TDD is the practice of creating your tests while you implement (rather than after). Moreover, TDD developers create the test first (returning the simplest incorrect value), runs it to see the failed test result, then implements the correct, complete implementation. The unit testing leads or ‘drives’ the programming workflow. This requires discipline but leads to a codebase with (good) heavy test coverage.
To perform a test you need 3 things; a testing framework to run the tests, you need the tests, and then you need something to test. Here is an example of a test and something being tested.
Example Test (Pseudocode)
//ASSUMING YOU HAVE A CUSTOM 'DoIntAddition' METHOD TO TEST...
[Test]
public void DoIntAdditionTest() {
//SETUP
int value1_int = 10;
int value2_int = 1;
//TEST
int result_int = DoIntAddition (value1_int, value2_int);
//EVALUATE VIA ‘ASSERTION’
Assert.IsTrue(result_int == (value1_int + value2_int) );
}
Example Test (Summary)
- Most Unit Testing Frameworks have some meta-tags (attributes) such as [test] that is used to indicate which methods are ‘test methods’
- You do any setup needed to cleanly prepare for your test (optional)
- You perform the test (such as calling a custom method to be tested as shown)
- You evaluate using an assertion. An assertion is your way as the tester to suggest the expected behavior. In this example it seems almost too simple, almost silly to test this method. However, once you create the test, you can be forever confident that the ‘DoIntAddition’ method will perform as expected, even if you later change the implementation of the method. This is the confidence you gain from Unit Testing.
Challenges In Unit Testing
General
Unit testing requires discipline from the team. Team leaders and project stakeholders must allow time not only to add the user-facing features, but also to create non-functional features such as under-the-hood unit tests for the codebase. Each team member too must be responsible for his or her own code. Writing tests for your own code (beforehand as in TDD per above) or during development is best.
First and foremost, a game is different to many other types of software in that a good portion of the code is handling input, and visuals/graphics/UI. These are two notoriously “un-unit-testable” parts of a system.
To help these issues, you can start by separating your visuals from your logic. This will make the logic more testable.
Specific Challenges for Unity3D
A staple of Unity development is the heavy use of the MonoBehaviour class. There is always logic in MonoBehaviours that you simply cannot take out and test in isolation. Now if you try to simply instantiate a MonoBehaviour in your script you will see this error in the console: You are trying to create a MonoBehaviour using the ‘new’ keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all. Each Unit3D testing frameworks has a solution to this issue. See the documentation for the framework you choose.
Another problem is the tight coupling of scripts to UnityEngine.dll*, which is not testable; if code depends on UnityEngine directly I can’t give it a mock implementation under my control, ergo it is not testable. This post covers more about the issue.
Links
Unity3D Unit Testing Articles
- Unit Testing In Unity3D (Great Overview)
- Organizing Unity3D for Unit Testing
Unity3D Unit Testing Frameworks (Packages)
- Test Star (Very robust, but costs money)
- NUnitLite
- UUnit
- Sharp Unit
- Autopilot for TestFlight