private Task _keepAliveTask; // get's assigned by object initializer public async Task EndSession() { _cancellationTokenSource.Cancel(); // cancels the _keepAliveTask await _logoutCommand.logoutIfPossible(); await _keepAliveTask; }
重要的是EndSession Task只在`_keepAliveTask’结束后才结束.但是,我很难找到一种方法来可靠地测试它.
问题:如何对EndSession方法进行单元测试并验证EndSession返回的任务是否等待_keepAliveTask.
出于演示目的,单元测试可能如下所示:
public async Task EndSession_MustWaitForKeepAliveTaskToEnd() { var keepAliveTask = new Mock<Task>(); // for simplicity sake i slightly differ from the other examples // by passing the task as method parameter await EndSession(keepAliveTask); keepAliveTask.VerifyAwaited(); // this is what i want to achieve }
进一步的标准:
– 可靠的测试(总是在实现正确时通过,在实现错误时总是失败)
– 不能超过几毫秒(毕竟这是一个单元测试).
我已经考虑了几个备选方案,我将在下面记录:
非异步方法
如果没有对_logoutCommand.logoutIfPossible()的调用,那将非常简单:我只是删除异步并返回_keepAliveTask而不是等待它:
public Task EndSession() { _cancellationTokenSource.Cancel(); return _keepAliveTask; }
单元测试看起来(简化):
public void EndSession_MustWaitForKeepAliveTaskToEnd() { var keepAliveTask = new Mock<Task>(); // for simplicity sake i slightly differ from the other examples // by passing the task as method parameter Task returnedTask = EndSession(keepAliveTask); returnedTask.Should().be(keepAliveTask); }
但是,有两个论点反对这个:
>我有多个需要等待的任务(我正在考虑Task.WhenAll down down)
>这样做只会将责任转移到等待EndSession调用者的任务.仍然需要在那里测试它.
非异步方法,异步同步
当然,我可以做类似的事情:
public Task EndSession() { _cancellationTokenSource.Cancel(); // cancels the _keepAliveTask _logoutCommand.logoutIfPossible().Wait(); return _keepAliveTask; }
使用Task.WhenAll(…)的非异步方法
是(有效的)性能改进,但引入了更多的复杂性:
– 如果不隐藏第二个异常(两个都失败时)很难做到
– 允许并行执行
由于性能不是关键,我想避免额外的复杂性.此外,前面提到的问题,它只是将(验证)问题移动到EndSession方法的调用者也适用于此.
观察效果而不是验证电话
现在当然不是“单位”测试方法调用等.我总能观察到效果.这是:只要_keepAliveTask没有结束,EndSession任务也不能结束.但由于我不能无限期地等待,因此我必须暂时停止.测试应该很快,所以5秒的超时是不行的.所以我做的是:
[Test] public void EndSession_MustWaitForKeepAliveTaskToEnd() { var keepAlive = new TaskCompletionSource<bool>(); _cancelableLoopingTaskFactory .Setup(x => x.Start(It.IsAny<ICancelableLoopStep>(),It.IsAny<CancellationToken>())) .Returns(keepAlive.Task); _testee.StartSendingKeepAlive(); _testee.EndSession() .Wait(TimeSpan.FromMilliseconds(20)) .Should().BeFalse(); }
但我真的不喜欢这种方法:
>难以理解
>不可靠
>或 – 当它非常可靠时 – 需要很长时间(单元测试不应该).
解决方法
public class MyAwaitable { public bool IsAwaited; public MyAwaiter GetAwaiter() { return new MyAwaiter(this); } } public class MyAwaiter { private readonly MyAwaitable _awaitable; public MyAwaiter(MyAwaitable awaitable) { _awaitable = awaitable; } public bool IsCompleted { get { return false; } } public void GetResult() {} public void OnCompleted(Action continuation) { _awaitable.IsAwaited = true; } }
因为所有你需要等待的东西是有一个GetAwaiter方法返回一些IsCompleted,OnCompleted和GetResult,你可以使用虚拟等待来确保正在等待_keepAliveTask:
_keepAliveTask = new MyAwaitable(); EndSession(); _keepAliveTask.IsAwaited.Should().BeTrue();
如果你使用一些模拟框架,你可以使Task的GetAwaiter返回我们的MyAwaiter.