【发布时间】:2015-03-17 11:25:15
【问题描述】:
我想测试以下代码:
private Task _keepAliveTask; // get's assigned by object initializer
public async Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
await _logOutCommand.LogOutIfPossible();
await _keepAliveTask;
}
重要的是EndSession 任务仅在 `_keepAliveTask' 结束后结束。但是,我正在努力寻找一种可靠地对其进行测试的方法。
问题:我如何对EndSession 方法进行单元测试并验证EndSession 返回的Task 是否等待_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
}
进一步的标准: - 可靠的测试(执行正确时总是通过,执行错误时总是失败) - 不能超过几毫秒(毕竟这是一个单元测试)。
我已经考虑了几种替代方案,我将在下面记录:
非async方法
如果不调用 _logOutCommand.LogOutIfPossible(),那将非常简单:我只需删除 async 和 return _keepAliveTask 而不是 awaiting 它:
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进一步向下) - 这样做只会将等待任务的责任转移给
EndSession的调用者。仍然需要在那里进行测试。
非异步方法,通过异步同步
当然,我也可以做类似的事情:
public Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
_logOutCommand.LogOutIfPossible().Wait();
return _keepAliveTask;
}
但这是不行的(同步优于异步)。加上它仍然存在以前方法的问题。
非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();
}
但我真的很不喜欢这种方法:
- 很难理解
- 不可靠
- 或者 - 当它相当可靠时 - 它需要很长时间(单元测试不应该)。
【问题讨论】:
-
如果
_keepAliveTask被取消并等待,它应该抛出一个OperationCancelledException,不是吗?我不太确定您实际上要做什么。您想确保等待LogOutIfPossible? -
您实际上想测试什么?
EndSession完成了吗? -
@l3arnon
EndSession只在_keepAliveTask完成后完成 -
@YuvalItzchakov 是的,我认为这是一个可能的解决方案。让任务返回结果(或在没有结果的情况下抛出异常)并验证“包装”任务是否返回相同的结果(抛出该异常)。谢谢!
-
你使用什么测试框架? MS Test 支持异步测试方法。
标签: c# .net unit-testing task-parallel-library async-await