Unity的TestRunner生成的包名:com.UnityTestRunner.UnityTestRunner

WaitForSeconds

一切yield的东西都继承自:YieldInstruction。 UnityEngine.AsyncOperation (derived) UnityEngine.Coroutine (derived) UnityEngine.Coroutine.Coroutine() UnityEngine.WaitForEndOfFrame (derived) UnityEngine.WaitForFixedUpdate (derived) UnityEngine.WaitForFixedUpdate.WaitForFixedUpdate() UnityEngine.WaitForSeconds (derived) UnityEngine.WaitForSeconds.WaitForSeconds(float seconds)

Unity的自定义YieldInstruction

public sealed class WaitUntil : CustomYieldInstruction { private Func<bool> m_Predicate; public override bool keepWaiting => !this.m_Predicate(); public WaitUntil(Func<bool> predicate) => this.m_Predicate = predicate; } public sealed class WaitWhile : CustomYieldInstruction { private Func<bool> m_Predicate; public override bool keepWaiting => this.m_Predicate(); public WaitWhile(Func<bool> predicate) => this.m_Predicate = predicate; }

一个小问题:下面两个语句等价吗?基本上是等价的,但是第一种写法在超时之后有可能陷入死循环,而第二种方法则不会陷入死循环。 如果改写为while (tasks > 0 && !timeout) yield return null;会更安全一些。

while (tasks > 0) yield return null; yield return new WaitWhile(()=>tasks > 0);

理解UniyTest协程

NUnity默认的Test属性会在一帧渲染里面执行完毕,UnityTest则能够让测试在多帧里面执行。UnityTest函数中的每一次yield return都会立即退出当前帧,等待下一帧再继续执行。

自定义Wait

public class Wait { static public IEnumerator Until(Func<bool> condition, float timeout = 30f) { float timePassed = 0f; while (!condition() && timePassed < timeout) { yield return new WaitForEndOfFrame(); timePassed += Time.deltaTime; } if (timePassed >= timeout) { throw new TimeoutException("Condition was not fulfilled for " + timeout + " seconds."); } } } [UnityTest] public IEnumerator TestSkeletonFollowsPlayer() { Vector3 playerPos = new Vector3(2f, 1f, -5f); Quaternion playerDir = Quaternion.identity; Vector3 skeletonPos = new Vector3(2f, 0f, 5f); Quaternion skeletonDir = Quaternion.LookRotation(new Vector3(0f, 0f, -1f), Vector3.up); GameObject player = GameObject.Instantiate(playerPrefab, playerPos, playerDir); GameObject skeleton = GameObject.Instantiate(skeletonPrefab, skeletonPos, skeletonDir); skeleton.GetComponent<Skeleton>().player = player.GetComponent(); yield return Wait.Until(() => { float distance = Math.Abs((skeleton.transform.position - player.transform.position).magnitude); return distance <= 2f; }, timeout: 10f); }

Unity测试用例的几个阶段

第一阶段:使用yield return new WaitForSeconds 缺点:时长不确定。

[UnityTest] [UnityPlatform(RuntimePlatform.Android)] [Timeout(10000)] public IEnumerator UserGet() { var hasCallback = false; UserService.GetLoggedInUser().OnComplete(u => { Assert.IsFalse(u.IsError); ModelAssert.User(u.Data); UserService.Get(u.Data.ID).OnComplete(m => { hasCallback = true; Assert.IsFalse(m.IsError); ModelAssert.User(m.Data); }); }); yield return new WaitForSeconds(5); Assert.IsTrue(hasCallback); }

第二阶段:使用bool值+for循环 缺点:只能处理一个网络请求的情况,无法处理多个网络请求的情况。

[UnityTest] [UnityPlatform(RuntimePlatform.Android)] [Timeout(10000)] public IEnumerator GetLoggedInUser() { var hasCallback = false; UserService.GetLoggedInUser().OnComplete(m => { hasCallback = true; Assert.IsFalse(m.IsError); ModelAssert.User(m.Data); }); while (!hasCallback) yield return null; }

第三阶段:使用int值 可以处理多个网络请求

Unity如何书写异步测试

Async unit test in Test Runner - Unity Answers

TestRunner会报错:Method has non-void return value, but no result is expectedNUnit实际上并没有报错,是Unity的TestRunner报了一个错,属于Unity TestRunner的bug。

[Test] public async Task 测试异步expect() { Debug.Log("这是异步测试"); return; }

解决方法就是绕过TestRunner,不要使用异步方法测试

public static class UnityTestUtils { public static void RunAsyncMethodSync(t$$anonymous$$s Func < Task > asyncFunc) { Task.Run(async () => await asyncFunc()).GetAwaiter().GetResult(); } } public class AsyncAwaitUnitTests { [Test] public void WithExt([Values(0, 500, 1000)] int delay) { UnityTestUtils.RunAsyncMethodSync(async () => { var sw = Stopwatch.StartNew(); await Task.Delay(delay); Assert.AreEqual(delay, (int) sw.Elapsed.TotalMilliseconds, 300); }); } }

有人提供了另一种工具方法封装:

public static class UnityTestUtils { public static T RunAsyncMethodSync<T>(Func<Task<T>> asyncFunc) { return Task.Run(async () => await asyncFunc()).GetAwaiter().GetResult(); } public static void RunAsyncMethodSync(Func<Task> asyncFunc) { Task.Run(async () => await asyncFunc()).GetAwaiter().GetResult(); } } [Test] public void Test() { var result = RunAsyncMethodSync(() => GetTestTaskAsync(4)); Assert.That(result, Is.EqualTo(4)); } public async Task<int> GetTestTaskAsync(int a) { await Task.Delay(TimeSpan.FromMilliseconds(200)); return a; } [Test] public void Testthrow() { Assert.Throws<InvalidOperationException>( ()=> RunAsyncMethodSync(() => ThrowTaskAsync(4))); } public async Task<int> ThrowTaskAsync(int a) { await Task.Delay(TimeSpan.FromMilliseconds(200)); throw new InvalidOperationException(); }

参考资料

Unity Test Framework NUnit官网

学习NUIT学习什么

NUnit是C#语言中最重要的测试框架,对标Java中的JUnit。

  1. Assert系列函数,有两套Assert体系,一套是NUnit自己的,一套是Unity的。
  2. case上的注解,SetUp,TearDown,ExpectedException,Timeout等。