5

I'm currently developing a game in Unity 2019 and C# and I use a lot of events and coroutines for it. The coroutines are necessary because many calculations are done in them and I don't want the game to freeze during that time. Now I started to write unit tests using the Test Runner (Play Mode) and the coroutines are just not executed. Since I really need to be sure that they work as expected, it's not possible to just test "on the higher level".

I already tried the normal StartCoroutine() method, which doesn't work in the test file ("method name not found"). Moreover, I refactored one of my coroutines to be a normal void method and everything worked well and it passed the test. I used the Visual Studio debugger to see if it jumps into the coroutine execution and it does not. So the problem clearly is, that the coroutines are not executed. I thought about moving the calculation logic to another void function and test this function (and leave out the coroutine), but I must be sure that the iterations done in the coroutine work as well (they usually are more complicated than in the example).

This is a minimal example showing the general structure (there are usually many more computations done).

public class MeasuredValues
{
    List<Vector3> slidingWindow; // this is already filled when Normalize() is executed

    public IEnumerator Normalize()
    {
        //find coordinate system origin
        Vector3 originPosition = GetOrigin(); // returns a Vector3
        //normalization
        for (int i = 0; i < slidingWindow.Count; i++)
        {
            yield return null;
            //reset point to be at (0,y,0)
            slidingWindow[i] -= originPosition;
        }
    }
}

In the test file I want to:

[Test]
public void TestNormalization()
{
    MeasuredValues myMeasuredValues = new MeasuredValues();
    // add many Vector3 to slidingWindow
    // call coroutine
    // assert, that the values are now as expected
}

I tried myMeasuredValues.Normalize() (didn't work, the debugger just jumped over it) and StartCoroutine(myMeasuredValues.Normalize) (didn't work, the StartCoroutine is not available in that context). Finally, I tried

    while (test.Normalize().MoveNext())
    {
        yield return null;
    }

but this never ends since the MoveNext() is never set to false. At least the debugger jumped into the coroutine method.

Is there any easily applicable solution to test my coroutines with Visual Studio or Unity Test Runner without the need to refactor the whole project structure?

Nalara
  • 101
  • 1
  • 7

2 Answers2

1

I tried myMeasuredValues.Normalize() (didn't work, the debugger just jumped over it)

that one is of course since this is not how you run a coroutine

and StartCoroutine(myMeasuredValues.Normalize) (didn't work, the StartCoroutine is not available in that context).

that is due to StartCoroutine is a member of MonoBehaviour.


UPDATE

Actually using [UnityTest] instead of [Test] you can simply use

[UnityTest]
public IEnumerator TestNormalization()
{
    MeasuredValues myMeasuredValues = new MeasuredValues();

    // add many Vector3 to slidingWindow
    
    yield return myMeasuredValues.Normalize();

    // assert, that the values are now as expected
}

since this way the test is executed by the framework as a Coroutine itself.

derHugo
  • 83,094
  • 9
  • 75
  • 115
0

It seems to me that you can call a coroutine in your tests like this:

yield return myMeasuredValues.Normalize();
// Assert values are as expected

At least I managed to test coroutines in my Playmode tests this way (using Unity 2021.3.8), without the need of a TestRoutineRunner as derHugo suggested in 2019.

It seems not to be necessary to use MonoBehavior.StartCoroutine() to start the coroutine, as long as you use the return value (in this case with yield return). I'm not at all a Unity expert, so I'm only guessing, but perhaps this behavior is due to the fact that a [UnityTest] itself is called as a coroutine by Unity's TestRunner.

Unfortunately I did not find any proper documentation for this behavior. I'm just happy that after days of trial and error I got my own tests working. Please add a comment if you know more details, caveats, and/or have a link to documentation or helpful background info.

Goodsquirrel
  • 1,476
  • 1
  • 15
  • 29
  • Hey indeed back then I either didn't know `[UnityTest]` or it didn't exist yet. But yes, this is THE way to go and my original solution was quite hacky ;) – derHugo Apr 26 '23 at 17:51
  • As to why the `yield return` works this way - This is nothing Unity specific. Unity actually "ab"uses the `IEnumerator` interface for their Coroutines. A Coroutine basically just means it is registered somewhere to receive a `MoveNext` call each frame until it reaches the end. So just the same you do `yield return null;` or `yield return new WaitForSeconds();` you can `yield` anything that is an `IEnumerator` or implements `GetEnumerator` - or some other types handled especially via extensions etc – derHugo Apr 26 '23 at 17:53