For reasons of being a responsible developer I decided that because games are no different than other software they need unit tests as well. Unity does not make this easy.
Visual Studio supports unit tests inside the IDE, and they can run whenever you build, which is a mere Ctrl+Shift+B away. It can also use whatever framework I want, so I’m not restricted to just MSTest so can get nice things like Assert.Throws through NUnit. This seems to be the most useful way of having the tests run while you’re actually coding.
The first thing I had to do was switch the editor IDE within Unity and get it to generate some visual studio projects for the game. Double clicking on a CS file kicks it all off, but the first problem is that these projects are re-generated whenever it feels like it. You can’t add a unit test project to the solution and expect it to remain included, so I created a separate unit tests solution and added the Assemby-CSharp-Firstpass-vs and Assembly-CSharp-vs projects to it. While those will still be recreated, the solution and tests themselves will remain stable. Next I created the unit test project using the handy unit test generator extension (available through NuGet) and tried to test some code.
The next problem I faced was actually the biggest of the whole process: MonoBehaviour based classes cannot be instantiated. I took a peek inside of the code and it turns out that the way that they hook into the Unity engine itself is by a mechanism that is totally unsupported by code running under the normal .NET framework.
Several solutions suggested themselves. The first was to put all of the logic into “proper” classes and call them from the MonoBehaviour ones. This design pattern can work well, and does have some major advantages of extracting logic from platform specific implementation if I ever need to ditch Unity in the future, but also has the larger drawback of leaving any code that needs to actually interact with Unity itself, whether by spawning objects, showing the UI or whatever, untested. This was a big enough drawback for me to discard the idea.
The next thing that I tried was Microsoft Fakes, which is a rather nice feature of Visual Studio to mock objects. It can do some very impressive things behind the scenes with member replacement and I’ve used it a lot on other projects, but the linking tricks Unity use again defeated it and no matter how much tweaking I tried I couldn’t get it to work. After a day of getting to know a whole new level of the .NET framework and visual studio I rejected this as well.
This left me with the obvious low tech solution: doing the mocks myself. By replacing any Unity supplied class that I wanted with my own implementation I’m able to add any testing logic that I want, but at the risk of misunderstanding the actual logic of those calls and introducing false logic into the tests. For 90% of tests this won’t matter as it’s at the lowest possible level and each member function of the game only does one task at a time, like all good design should.
Mocking is easy enough to do. I created an empty MonoBehaviour class in the Assembly-CSharp-vs project under the Assets.UnityMocks namespace. Next I needed to find a compile time define that could be used to see if I was compiling from inside Unity or running the unit tests from within Visual Studio. Because Unity uses Mono for its C#/.NET implementation I first tried the __MonoCS__ define that is defined by Mono itself. Unity however doesn’t use this so I looked for a define that Unity does set.
Unity does actually create a series of defines while it compiles, and these are documented at http://docs.unity3d.com/Documentation/Manual/PlatformDependentCompilation.html. None of those quite do the job, but further down the page it explains that if you create a file called smcs.rsp in your Assets folder then you can add command lines for the compilers. In this case it just had to contain -define:_UNITY_RUNTIME for me, to be able to wrap my mocks and the using statements that reference them with some conditional compilation.
The last thing I needed to do was add the following to the top of all .cs files that use any Unity objects:
The resulting compilation errors then told me which Unity classes and members I needed to implement. As a bonus I only ever have to implement those once for every project as I can share code afterwards.
This does have the drawback of divorcing the actual implementation of, say, Resources.Load() with my new implementation, but I’m happy enough that I can catch that through other forms of testing. There will be all manner of graphical glitches that cannot be spotted through unit tests because of this, but those are always going to be hard to code tests for anyway.
Now I can happily go about with test driven development with my Unity projects.