Tuesday, February 2, 2016

unity - How to make an Assert.IsNull test pass when the value is reported as ?


I've just started using unity tests with the built in editor tests. After making my test and finding out that it constantly failed I debugged the code and found that a static object was set to a new game object when running the test while it is set to null when playing in the editor.


Is there a way to force unity to treat the test the in the same way that it treats scripts at run time?


Edit


After changing some code around i get another strange error: Editor Tests window showing error


does anyone know why "null" isn't "null". The null objects are stored in an array, but that shouldn't make a difference right?



Edit Edit (Edit 2?)


Here is the code that has the issue


public class StatCollector : MonoBehaviour {

public static StatCollector running;

//ensure that there is only one object running at the same time
public void Awake () {
if(running == null)
{

DontDestroyOnLoad(gameObject);
running = this;
}
else if(running != this)
{
DestroyImmediate(gameObject);
}
}
}


and here is the test:


[Test]
public void SingleTest()
{
//create gameobjects
GameObject[] collect = new GameObject[2];
collect[0] = new GameObject("one");
collect[1] = new GameObject("two");
collect[0].AddComponent();


//see if gameobjects were created correctly
if (collect[0] == null || collect[1] == null) Assert.Fail("failed to create game objects");

//test what happens when the second stat collector is added to the scene
collect[1].AddComponent();
collect[0].GetComponent().Awake();
collect[1].GetComponent().Awake();
Assert.IsNull(collect[1]);
}


I'm assuming that there is no way to keep Awake private which is why it is public (just so that the test case has access to it)



Answer



First some background...


There are two flavours of "null" in Unity:




  • Actual null




  • "Pseudo-Null" - this is a non-null Unity object (usually a GameObject or MonoBehaviour) that has been removed from the scene using Destroy or unloading a scene.



    (In-editor, this is also used for reference fields in an Inspector that have been left blank)


    The reason this is called pseudo-null is that for an object in this state, (destroyedObject == null) evaluates to true using an overloaded operator.




Using DestroyImmediate() should put an object into this state immediately, while using Destroy() will do so at the end of the current frame's updates but before rendering, which can often help avoid order-of-execution bugs when multiple scripts are referencing an object.


The thing is that a "pseudo-null" reference isn't actually null, which you can confirm by calling:


System.Object.ReferenceEquals(destroyedObject, null); // returns false

...or by trying to access any of the object's members - you won't get a null reference exception, but a Unity MissingReferenceException (which is handy, because the debugging tools can tell you more about which object got destroyed while a script was still trying to use it - something we don't get from real nulls).


This psuedo-null stub will remain until all references to it are cleared/go out of scope, using normal C# garbage collection rules. Read more about the reasons this exists in this Unity blog post.





How this applies to your test


My guess is that Assert.IsNull() at the end of your test is internally checking for real null instead of comparing with the overloaded operator.


(Especially if you're using Microsoft.VisualStudio.TestTools.UnitTesting.Assert rather than Unity's version - I don't see any using statements in your code so I'm guessing)


Since the destroyed object is only pseudo-null, this check comes back false, tripping the assertion.


We can ensure it uses the overloaded operator to also detect pseudo-null by rephrasing the assert at the end of your test as:


Assert.IsTrue(collect[1] == null);

We know this == will return true for a Destroy()ed object, whether it's recently-destroyed or long-dead, so this phrasing reliably checks for what we want.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...