A Uniject example

Let’s take a look at one of the example scenes that comes with Uniject – the bleeping, bouncing spheres!

We’re going to see how we can:

  • Write a TestableComponent that drives our bouncing lights
  • Have Uniject create the bouncing light GameObject hierarchy for us
  • Load and verify an audio clip and physic material
  • Test all of our code
  • Do all of the above without opening the Unity editor!

Here’s a functional spec for the scene:

  • It has a thin box on the floor
  • It features random, bouncing, light emitting spheres
  • When the spheres collide, they beep and change their light colour

The lights are derived from Uniject’s ‘TestableComponent’ class, which is semantically equivalent to MonoBehaviour:

[GameObjectBoundary]
public class BouncingLight : TestableComponent {

    private ILight light;
    private Random rand;
    private IAudioSource source;
    private UnityEngine.AudioClip beep;

    public const float killThresholdY = -5.0f;

    public BouncingLight(TestableGameObject obj, Sphere sphere,
                         IAudioSource source,
                         ILight light, Random rand,

                         [Resource("physic/bouncy")]
                         IPhysicMaterial material,

                         [Resource("audio/beep")]
                         AudioClip beep) : base(obj) {
        this.light = light;
        this.rand = rand;
        this.beep = beep;
        this.source = source;
        sphere.collider.material = material;
        light.intensity = 2.0f;
        light.range = 5;
    }

    public override void Update() {
        if (this.Obj.transform.Position.y < killThresholdY) {
            Obj.Destroy();
        }
    }

    public override void OnCollisionEnter(Collision collision) {
        light.color = new Color((float) rand.NextDouble(),
                                (float) rand.NextDouble(),
                                (float) rand.NextDouble());
        source.playOneShot(beep);
    }
}

This code is all that is required for Uniject to instantiate a complete, working GameObject hierarchy including referenced audio clip and physic material.

Now for the biggest advantage; we can execute every line of this code in a fraction of a second, without opening Unity at all!

Here is a test that instantiates our bouncing light, simulates a frame and ensures it has not destroyed itself:

    [TestFixture]
    public class TestCollisions : BaseInjectedTest {

        [Test]
        public void testBouncingLightDoesNotDestroyAboveThreshold() {
            BouncingLight light = kernel.Get<BouncingLight>();
            step();
            Assert.IsFalse(light.Obj.destroyed);
        }
}

And here’s the test runner in MonoDevelop 3.0:

So we’ve tested our code, how do we instantiate a TestableComponent in Unity?

Enter Ninject:

BouncingLight light = UnityInjector.get().Get<BouncingLight>();

And our Bouncing Light is created for us:

It’s worth pointing out that this GameObject hierarchy has been created for us automatically by Uniject; it has a light, sphere collider, rigid body and audio source, and  it has the audio clip of our beep sound and the bouncy physic material.

There is no prefab required and no object construction code.

There is one further class involved that sets up the scene and randomly spawns the bouncing lights. It, too, is testable:

public class TestableCollisions {
    public TestableCollisions(IntervalBasedCallback caller,
                              [GameObjectBoundary] Box box,
                              Factory factory,
                              Random rand) {

        caller.callback = () => {
            BouncingLight light = factory.create();
            light.Obj.transform.Translate(
                new Vector3((float) rand.NextDouble(),
                4 + (float) rand.NextDouble() * 10.0f,
                (float) rand.NextDouble()));
        };

        caller.interval = TimeSpan.FromSeconds(2);
        box.Obj.transform.localScale = new Vector3(50, 1, 50);
    }
}

Here is an integration test that instantiates the entire scene and simulates a few seconds worth of frames, to verify that it is spawning bouncing spheres:

[Test]
public void testExampleSpawnsObjects() {
    kernel.Get();
    step(2);
    int count = objectCount;
    step(4);
    Assert.Greater(objectCount, count);
}

But you’re referencing resources with strings!

It’s true, we’ve totally subverted Unity’s convention of editor assigned references; if we change the location of our resources won’t we break our code?

The difference is we’ll know about it immediately if we break a referenced resource; Uniject verifies that referenced resources actually exist. In a fraction of a second a test can verify every referenced resource in our entire project, and we don’t even need to fire up Unity to do so.

I wouldn’t care to know how much time I’ve wasted due to the brittleness of editor assigned references; Uniject has the potential to eliminate a swathe of runtime errors.

Note that we have a separate test solution in a sibling folder to the Assets folder. This solution contains the test project where the unit tests are actually written, and it references the .csproj files that are automatically created and managed for us by Unity.

We also use the latest version of MonoDevelop, 3.0, rather than the borked version that ships with Unity – it has a broken unit test runner.

Browse Uniject on Github.

Advertisements

17 Responses to A Uniject example

  1. sshumakov says:

    It seems that workflow where you reference resources from code is well suited for teams where only developers work on project. What about the case when team has level designer that has no C# language knowledge?

    • outlinegames says:

      Uniject doesn’t change that; you can still design your levels visually as you normally would, and lay out your actors within it as you normally would. The difference is in the way you build those actors.

  2. pSupaNova says:

    Tested your framework, using Unity3D 4 , Visual Studio 2010 & Nunit.

    It worked :). so now I have an already made up testing framework thanks.

    You use Moq, Ninject, Nunit. have you tried setting up a BDD framwork like SpecFlow?

  3. Is it possible to run those tests from CI server such as Jenkins or Bamboo? (i.e can we run them from the command line .. ?)

    • outlinegames says:

      Yes, it’s certainly possible; I have run all the Uniject tests from a Linux VM. With Uniject your code is plain old C sharp, so you can use mono in all its glory.

      Another possibility, which I’ve yet to get working, is to have your tests run with full Ahead Of Time (AOT) complication, thus letting you find any dynamic code generation problems without having to deploy to ah iDevice.

  4. Just curious — are you still using Uniject for your projects? (i.e. has it proven out?)

    Also, can you explain what the step() method does?

    And finally, the Uniject documentation seems to contradict itself in that it claims to require MonoDevelop but also claims to allow the IDE of your choice. Can Uniject easily be used with Visual Studio (which I much prefer) instead of MonoDevelop?

    • outlinegames says:

      I used Uniject for one release game, the Clones of Corpus.

      I use core Uniject interfaces in Unibill to let me unit test it properly, so from that point of view it’s indispensable. However Unibill is a great use case since it’s all code and no design.

    • outlinegames says:

      The step() method simulates an Update() call, so all your testable components will have their update method called.

      You can use visual studio just fine, you don’t need monodevelop.

  5. Torch says:

    Hello,
    i always get an ECall Error whenever i call Quaternion.Euler
    Has this problem ever occured to you?
    I guess i have to work around it and convert it myself, right?

  6. Jason says:

    Is this still under active development?

  7. PinoEire says:

    Hi, I’m trying to use the Hosted Config feature but it doesn’t seem to work: can you please elaborate on its implementation?
    Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: