我有以下代码,我想测试一下:
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
方法进行单元测试,并验证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
}
进一步的标准: - 可靠的测试(总是在实现正确时通过,在实现错误时总是失败) - 不能超过几毫秒(毕竟这是一个单元测试)。
我已经考虑了几个备选方案,我将在下面记录:
async
方法如果没有对_logOutCommand.LogOutIfPossible()的调用,那将非常简单:我只需删除async
和return _keepAliveTask
而不是await
:
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();
}
但我真的不喜欢这种方法:
答案 0 :(得分:3)
如果您只想确认EndSession
正在等待_keepAliveTask
(并且您真的可以完全控制_keepAliveTask
),那么您可以创建自己的等待类型而不是{{1等待时的信号并检查:
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;
}
}
所有内容,因此await
方法会使用GetAwaiter
,IsCompleted
和{{1}返回某些内容您可以使用虚拟等待来确保等待OnCompleted
:
GetResult
如果你使用一些模拟框架,你可以让_keepAliveTask
的{{1}}返回我们的_keepAliveTask = new MyAwaitable();
EndSession();
_keepAliveTask.IsAwaited.Should().BeTrue();
。
答案 1 :(得分:0)
TaskCompletionSource
并在已知时间设置其结果。简化版本可能如下所示(使用nunit):
[Test]
public async Task VerifyTask()
{
var tcs = new TaskCompletionSource<bool>();
var keepAliveTask = tcs.Task;
// verify pre-condition
Assert.IsFalse(keepAliveTask.IsCompleted);
var waitTask = Task.Run(async () => await keepAliveTask);
tcs.SetResult(true);
await waitTask;
// verify keepAliveTask has finished, and as such has been awaited
Assert.IsTrue(keepAliveTask.IsCompleted);
Assert.IsTrue(waitTask.IsCompleted); // not needed, but to make a point
}
您还可以在waitTask中添加一个短延迟,以确保任何同步执行更快,如:
var waitTask = Task.Run(async () =>
{
await Task.Delay(1);
await keepAliveTask;
});
如果您不相信您的单元测试框架正确处理异步,您可以将完成标志设置为waitTask的一部分,并在最后检查它。类似的东西:
bool completed = false;
var waitTask = Task.Run(async () =>
{
await Task.Delay(1);
await keepAliveTask;
completed = true;
});
// { .... }
// at the end of the method
Assert.IsTrue(completed);