Bat*_*nit 8 .net c# unit-testing task-parallel-library async-await
我有以下代码,我想测试:
private Task _keepAliveTask; // get's assigned by object initializer
public async Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
await _logOutCommand.LogOutIfPossible();
await _keepAliveTask;
}
Run Code Online (Sandbox Code Playgroud)
重要的是,EndSession只有`_keepAliveTask'结束后,Task才会结束.但是,我很难找到一种方法来可靠地测试它.
问题:我如何对EndSession方法进行单元测试并验证等待Task返回的方法.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
}
Run Code Online (Sandbox Code Playgroud)
进一步的标准: - 可靠的测试(总是在实现正确时通过,在实现错误时总是失败) - 不能花费超过几毫秒(毕竟这是一个单元测试).
我已经考虑了几个备选方案,我将在下面记录:
async方法如果没有对_logOutCommand.LogOutIfPossible()的调用,那将非常简单:我只是删除它async而return _keepAliveTask不是await它:
public Task EndSession()
{
_cancellationTokenSource.Cancel();
return _keepAliveTask;
}
Run Code Online (Sandbox Code Playgroud)
单元测试看起来(简化):
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);
}
Run Code Online (Sandbox Code Playgroud)
但是,有两个论点反对这个:
Task.WhenAll进一步下调)EndSession.仍然需要在那里测试它.当然,我可以做类似的事情:
public Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
_logOutCommand.LogOutIfPossible().Wait();
return _keepAliveTask;
}
Run Code Online (Sandbox Code Playgroud)
但这是禁止的(同步异步).此外,它仍然存在先前方法的问题.
async方法使用Task.WhenAll(...)是(有效的)性能改进但是引入了更多的复杂性: - 没有隐藏第二个异常(当两个都失败时)很难正确 - 允许并行执行
由于性能不是关键,我想避免额外的复杂性.此外,前面提到的问题,它只是将(验证)问题移动到EndSession方法的调用者也适用于此.
现在当然不是"单位"测试方法调用等.我总能观察到效果.这是:只要_keepAliveTask还没有结束,也不能结束EndSession Task.但是因为我无法无限期地等待,所以必须要暂停.测试应该很快,所以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();
}
Run Code Online (Sandbox Code Playgroud)
但我真的不喜欢这种方法:
如果您想要的只是验证正在EndSession等待_keepAliveTask(并且您确实可以完全控制),那么您可以在等待时_keepAliveTask创建自己的可等待类型而不是信号并检查:Task
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;
}
}
Run Code Online (Sandbox Code Playgroud)
由于您所需要的await只是有一个GetAwaiter返回带有的内容IsCompleted的方法,OnCompleted并且GetResult您可以使用虚拟可等待来确保_keepAliveTask正在等待:
_keepAliveTask = new MyAwaitable();
EndSession();
_keepAliveTask.IsAwaited.Should().BeTrue();
Run Code Online (Sandbox Code Playgroud)
如果您使用一些模拟框架,您可以改为 makeTask返回GetAwaiter我们的MyAwaiter.
| 归档时间: |
|
| 查看次数: |
1009 次 |
| 最近记录: |