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 expected
NUnit实际上并没有报错,是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();
}
参考资料
学习NUIT学习什么
NUnit是C#语言中最重要的测试框架,对标Java中的JUnit。
- Assert系列函数,有两套Assert体系,一套是NUnit自己的,一套是Unity的。
- case上的注解,SetUp,TearDown,ExpectedException,Timeout等。