Unity TestRunner批量运行测试失败,单独运行正常问题求助
问题:Unity测试用例单独运行正常,批量运行失败
使用Unity 2020.3.11f1版本,SpawnMultipleBlocks测试用例单独运行时可正常通过,但通过TestRunner点击“Run All”批量运行时始终失败。该测试负责在场景中按随机间隔生成方块。
报错信息
SpawnMultipleBlocks (0.020s) --- Unhandled log message: '[Exception] NullReferenceException: Object reference not set to an instance of an object'. Use UnityEngine.TestTools.LogAssert.Expect --- BlockSpawner+<SpawnedBlocks>d__14.MoveNext () (at Assets/Scripts/BlockSpawner.cs:69) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <127e81e1cb3441cc97d26b1910daae77>:0) UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator) BlockSpawner:Start() (at Assets/Scripts/BlockSpawner.cs:24) --- topPos is null Generate at top : 0 NullReferenceException: Object reference not set to an instance of an object topPos is null Generate at top : 0 NullReferenceException: Object reference not set to an instance of an object topPos is null Generate at top : 0 NullReferenceException: Object reference not set to an instance of an object
测试类代码(BlockSpawnerTests.cs)
using System.Collections; using NUnit.Framework; using UnityEditor; using System.Collections.Generic; using UnityEngine; using UnityEngine.TestTools; namespace Tests { public class BlockSpawnerTests : MonoBehaviour { private static string resourceDirPath = "Tests/Block"; [Test] public void SpawnBlock() { // Arrange var spawner = new GameObject().AddComponent<BlockSpawner>(); spawner.setBlockReference(Resources.Load<GameObject>(resourceDirPath)); // Act spawner.SpawnBlock(Vector3.zero); // Assert Assert.IsNotNull(spawner.getSpawnedBlock()); } [Test] public void SpawnBlockAtPosition() { // Arrange var spawner = new GameObject().AddComponent<BlockSpawner>(); spawner.setBlockReference(Resources.Load<GameObject>("Tests/Block")); // Act spawner.SpawnBlock(Vector3.one); // Assert Assert.AreEqual(spawner.getSpawnedBlock().transform.position, Vector3.one); } [Test] public void SpawnBlockSpeed() { // Arrange var spawner = new GameObject().AddComponent<BlockSpawner>(); spawner.setBlockReference(Resources.Load<GameObject>("Tests/Block")); // Act spawner.SpawnBlock(Vector3.zero); // Assert Assert.IsTrue(spawner.getSpawnedBlock().GetComponent<Block>().GetSpeed() >= -10 && spawner.getSpawnedBlock().GetComponent<Block>().GetSpeed() <= -4); } [Test] public void WhenBlockComponentIsNullShouldLog() { // Arrange var spawner = new GameObject().AddComponent<BlockSpawner>(); spawner.setBlockReference(new GameObject("EmptyObject")); // Act spawner.SpawnBlock(Vector3.zero); // Assert LogAssert.Expect(LogType.Log, "Spawned block doesn't have a Block component"); LogAssert.NoUnexpectedReceived(); Destroy(spawner); } [UnityTest] public IEnumerator SpawnMultipleBlocks() { Debug.Log("block spawner create"); var spawner = new GameObject(); var blockSpawner = spawner.AddComponent<BlockSpawner>(); Debug.Log("set top bottom middle"); blockSpawner.SetTopPos(new GameObject().transform); blockSpawner.SetBottomPos(new GameObject().transform); blockSpawner.SetMiddlePos(new GameObject().transform); blockSpawner.SetWaitForSpawnLow(1); blockSpawner.SetWaitForSpawnHigh(2); blockSpawner.setBlockReference(Resources.Load<GameObject>("Tests/Block")); yield return new WaitForSeconds(1); blockSpawner.Start(); // wait for at least 1 block to be spawned yield return new WaitForSeconds(1); Assert.GreaterOrEqual(blockSpawner.GetSpawnedBlocks().Count, 1); // wait for at least 2 block to be spawned yield return new WaitForSeconds(1); Assert.GreaterOrEqual(blockSpawner.GetSpawnedBlocks().Count, 2); } } }
问题原因与解决方案
核心原因
- 临时对象被提前回收:批量运行时,Unity测试的场景清理机制会自动回收未被持有引用的临时GameObject,
SpawnMultipleBlocks中创建的top/bottom/middle定位对象没有被保存引用,导致topPos等变量变为null,触发空引用异常。单独运行时测试环境的生命周期管理更宽松,临时对象能保持到测试结束。 - 测试类不必要的继承:测试类继承
MonoBehaviour是多余的,NUnit测试类不需要该继承,反而可能干扰测试的生命周期管理逻辑。 - 测试间状态污染:没有统一的测试清理逻辑,前序测试生成的对象可能影响后续测试的运行环境。
修复步骤
移除测试类的MonoBehaviour继承
修改测试类定义,去掉不必要的继承:public class BlockSpawnerTests // 移除: MonoBehaviour保留临时定位对象的引用
在SpawnMultipleBlocks中保存定位对象的引用,防止被自动回收,并在测试结束后手动清理:[UnityTest] public IEnumerator SpawnMultipleBlocks() { Debug.Log("block spawner create"); var spawner = new GameObject(); var blockSpawner = spawner.AddComponent<BlockSpawner>(); Debug.Log("set top bottom middle"); // 保存临时对象引用,避免被自动回收 var topObj = new GameObject(); var bottomObj = new GameObject(); var middleObj = new GameObject(); blockSpawner.SetTopPos(topObj.transform); blockSpawner.SetBottomPos(bottomObj.transform); blockSpawner.SetMiddlePos(middleObj.transform); blockSpawner.SetWaitForSpawnLow(1); blockSpawner.SetWaitForSpawnHigh(2); blockSpawner.setBlockReference(Resources.Load<GameObject>("Tests/Block")); yield return new WaitForSeconds(1); blockSpawner.Start(); // 等待至少1个方块生成 yield return new WaitForSeconds(1); Assert.GreaterOrEqual(blockSpawner.GetSpawnedBlocks().Count, 1); // 等待至少2个方块生成 yield return new WaitForSeconds(1); Assert.GreaterOrEqual(blockSpawner.GetSpawnedBlocks().Count, 2); // 测试结束后手动清理所有对象 Destroy(spawner); Destroy(topObj); Destroy(bottomObj); Destroy(middleObj); }添加统一测试清理逻辑
新增TearDown方法,确保每个测试结束后清理场景中的测试对象,避免状态污染:[TearDown] public void TearDown() { // 销毁所有测试生成的BlockSpawner foreach (var spawner in Object.FindObjectsOfType<BlockSpawner>()) { Destroy(spawner.gameObject); } // 销毁所有生成的方块 foreach (var block in Object.FindObjectsOfType<Block>()) { Destroy(block.gameObject); } }优化BlockSpawner的启动逻辑
若BlockSpawner的Start方法是默认MonoBehaviour生命周期方法,建议将生成逻辑移到单独的公共方法(如StartSpawning),避免测试初始化完成前自动执行导致空引用:// 在BlockSpawner中新增方法 public void StartSpawning() { StartCoroutine(SpawnedBlocks()); } // 测试中调用该方法代替Start() blockSpawner.StartSpawning();
内容的提问来源于stack exchange,提问作者Chathuranga Jayanath
相关产品推荐
相关产品推荐

