Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

.Net Unity Unit Test Framework - NUUnit - C#

Discussion in 'Scripting' started by tgraupmann, Mar 13, 2009.

  1. tgraupmann

    tgraupmann

    Joined:
    Sep 14, 2007
    Posts:
    828
    I've used http://nunit.org extensively for Unit Testing C# apps, however it's not compatible with Unity.

    I ran across a Unity forum post for UUnit http://forum.unity3d.com/viewtopic.php?t=6515&highlight=uunit, however it was written in BOO script.

    I adapted part of the NUnit source code to work with Unity (as a C# script) and attached an example project that shows all the same attributes work.

    A typical test class looks similar to the following.

    Code (csharp):
    1. using System;
    2.  
    3. [TestFixture]
    4. public class MyTestClass
    5. {
    6.     [TestFixtureSetUpAttribute]
    7.     public void MyTestFixtureSetUpAttribute()
    8.     {
    9.         TestRunner.ReportLog("Call setup once before all [Test] and [SetUp]");
    10.     }
    11.  
    12.     [SetUp]
    13.     public void MySetUp()
    14.     {
    15.         TestRunner.ReportLog("Call setup before each [Test]");
    16.     }
    17.  
    18.     [Test]
    19.     public void MyGoodTest()
    20.     {
    21.         TestRunner.ReportLog("Do a good test");
    22.     }
    23.  
    24.     [Test]
    25.     public void MyBadTest()
    26.     {
    27.         TestRunner.ReportLog("Do a bad test");
    28.         throw new System.Exception("I did a bad thing");
    29.     }
    30.  
    31.     [TearDown]
    32.     public void MyTearDown()
    33.     {
    34.         TestRunner.ReportLog("Call after each [Test]");
    35.     }
    36.  
    37.     [TestFixtureTearDown]
    38.     public void MyTestFixtureTearDown()
    39.     {
    40.         TestRunner.ReportLog("Call setup once after all [Test] and [TearDown]");
    41.     }
    42. }
    To run these tests, you don't need to instantiate anything. Simply run

    Code (csharp):
    1. TestRunner.Execute();
    Using reflection, any class that you've added [TestFixture] will be found.

    I used the same attributes that you'd be familar with if you've used Visual Studio or NUnit for unit testing.

    There's an button in the example that runs the unit tests.

    Output is written to the console output.


    If you aren't already familar with NUnit, take a look at their site and read the documentation.

    Only use [TestFixture] on public classes that have a valid constructor. I say this because you might try to use [TestFixture] on a MonoBehaviour script, so don't do that. Instead create a new test class and use a public reference to your MonoBehavior script. If you need to, you could get away with using a ScriptableObject.


    I didn't port the Asserts from NUnit.

    Code (csharp):
    1. Assert.Fail(); Assert.AreEqual();
    Instead if a [Test] method should throw a System.Exception, the test failed. If an exception wasn't thrown it will pass.


    The test results for a run would look like the following:
    Code (csharp):
    1. [TestRunner] **** Summary ****
    2. [PASS] MyTestClass.MyTestFixtureSetUpAttribute
    3. [PASS] MyTestClass.MySetUp
    4. [PASS] MyTestClass.MyGoodTest
    5. [PASS] MyTestClass.MyTearDown
    6. [PASS] MyTestClass.MySetUp
    7. [FAIL] MyTestClass.MyBadTest
    8. [PASS] MyTestClass.MyTearDown
    9. [PASS] MyTestClass.MyTestFixtureTearDown

    To use:

    import the package NUUnit.unitypackage


    Open:

    Tests\TheScene
     

    Attached Files:

  2. Thomas-Lund

    Thomas-Lund

    Joined:
    Jan 18, 2008
    Posts:
    465
    Cool - thanks for sharing
     
  3. Scrat

    Scrat

    Joined:
    Apr 20, 2008
    Posts:
    316
    Ah, I hate doing tests but it's so useful!

    Thanks a lot for converting it in C#. I'll sure use it in a near future (or at least if if I'm not too lazy :oops:)
     
  4. AngryAnt

    AngryAnt

    Keyboard Operator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    Bookmarked. Thanks.
     
  5. MrJoy

    MrJoy

    Joined:
    Oct 31, 2007
    Posts:
    60
    Haven't had a chance to look at this yet, but you can more or less expect a patch to show up on this thread and/or the wiki once I do.

    Thoughts:

    1) The point of actually having explicit asserts is to add clarity. With throw statements, I'm invariably going to wind up having to visually scan for throw, then expand my focus upward to find the conditional statement wrapping the throw to understand the root cause. Things get trickier if I fall into the habit of letting "hidden" (not in the actual code of the test) throws crop up and relying on those as my assert conditions. Perfectly reasonably to implement Assert using exceptions of course, but wrapping it up in some syntactic sugar and treating those assertions preferentially / differently than assertions from "deeper" in the code is going to turn the tests into actual *documentation*.

    2) Printing error messages with the failure is usually really handy.

    3) One major thing this could use is the ability for a test method to be a coroutine. Especially in light of the fact that things like Update() aren't parametric (I.E. don't receive Time.* as explicit parameters, making it harder to set up tests for them) I don't know how much effort that would be, but it could be worthwhile. On the flipside, my worry is that might introduce a tendency towards tests that over-reach and are not well scoped. I'm considering just making some base classes to facilitate this -- an abstract class such as:

    public abstract class UpdateBehaviour : MonoBehaviour {
    void Update() { OnUpdate(Time.time, Time.deltaTime, Time.frameCount); }
    public abstract void OnUpdate(float t, float dT, int frame);
    }

    In such a case you could write tests that directly called OnUpdate with explicit values, and be able to test a considerable percentage of the typical use-cases, but of course you'd be hamstrung by things like Unity lifecycle events not happening in proper order if you called OnUpdate multiple times to simulate multiple frames. For example, the code to be tested creates a GO and adds a component to it in frame 1, then expects in frame 2 that Start() has executed properly on the created object. On top of that, such a mechanism would add overhead for iPhone that might add up to be significant.

    -JF
     
  6. tgraupmann

    tgraupmann

    Joined:
    Sep 14, 2007
    Posts:
    828
    Keep in mind what I provided is unit testing in 12 lines or less.

    Of course the Asserts and GUI could be useful.

    There are tons of features in the NUnit source code for TestResults and Assert logic.

    I'm working with a slightly modified version of this package that allows me to select which tests are invoked.

    I do have tests that create game objects, run test methods, and then destroy the game object.

    Spawned game objects can Start and Update.

    If you call a game object that starts a coroutine, it could wait for the game object to awaken and update before doing your validation. You would have to put this code into a class that extends MonoBehaviour.

    Code (csharp):
    1.     IEnumerator WaitForSecondsWrapper(float secs)
    2.     {
    3.         yield return new UnityEngine.WaitForSeconds(secs);
    4.     }
    5.  
    6. StartCoroutine(WaitForSecondsWrapper(1f));