验证正在等待任务

时间:2015-01-19 12:42:02

标签: c# .net 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;
}

重要的是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()的调用,那将非常简单:我只需删除asyncreturn _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();
}

但我真的不喜欢这种方法:

  • 难以理解
  • 不可靠
  • 或 - 当相当可靠时 - 需要很长时间(不应进行单元测试)。

2 个答案:

答案 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方法会使用GetAwaiterIsCompleted和{{1}返回某些内容您可以使用虚拟等待来确保等待OnCompleted

GetResult

如果你使用一些模拟框架,你可以让_keepAliveTask的{​​{1}}返回我们的_keepAliveTask = new MyAwaitable(); EndSession(); _keepAliveTask.IsAwaited.Should().BeTrue();

答案 1 :(得分:0)

  1. 使用TaskCompletionSource并在已知时间设置其结果。
  2. 验证在设置结果之前,EndSession上的await尚未完成。
  3. 验证设置结果后,EndSession上的await已完成。
  4. 简化版本可能如下所示(使用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);