验证正在等待任务

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

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

  • 很难明白
  • 靠不住
  • 或者 - 当它非常可靠时 - 需要很长时间(单元测试不应该).

i3a*_*non 4

如果您想要的只是验证正在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.