简体   繁体   English

验证正在等待任务

[英]Verify that task is being awaited

I have the following code which i'd like to test: 我有以下代码要测试:

private Task _keepAliveTask; // get's assigned by object initializer

public async Task EndSession()
{
    _cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
    await _logOutCommand.LogOutIfPossible();
    await _keepAliveTask;
}

It is important that the EndSession Task only ends once the `_keepAliveTask' ended. 重要的是EndSession Task仅在`_keepAliveTask'结束后才结束。 However, I'm struggling to find a way to test it reliably. 但是,我正在努力寻找一种可靠地对其进行测试的方法。

Question: How do i unit test the EndSession method and verify that the Task returned by EndSession awaits the _keepAliveTask . 问:我如何单元测试的EndSession方法和验证Task返回的EndSession等待_keepAliveTask

For demonstration purposes, the unit test could look like that: 出于演示目的,单元测试可能如下所示:

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
}

Further criterias: - reliable test (always passes when implementation is correct, always fails when implementation is wrong) - cannot take longer than a few milliseconds (it's a unit test, after all). 进一步的标准:-可靠的测试(正确的实现总是通过,错误的实现总是失败)-不能超过几毫秒(毕竟这是单元测试)。


I have already taken several alternatives into considerations which i'm documenting below: 我已经在以下文档中考虑了多种选择:

non- async method async方法

If there wouldn't be the call to _logOutCommand.LogOutIfPossible() it would be quite simple: i'd just remove the async and return _keepAliveTask instead of await ing it: 如果没有对_logOutCommand.LogOutIfPossible()的调用,它将非常简单:我只需删除asyncreturn _keepAliveTask而不是await它:

public Task EndSession()
{
    _cancellationTokenSource.Cancel();
    return _keepAliveTask;
}

The unit test would look (simplified): 单元测试看起来(简化):

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);
}

However, there's two arguments against this: 但是,有两个反对意见:

  • i have multiple task which need awaiting (i'm considering Task.WhenAll further down) 我有多个任务需要等待(我正在考虑Task.WhenAll进一步下降)
  • doing so only moves the responsibility to await the task to the caller of EndSession . 这样做只会将等待任务的责任移到EndSession的调用方。 Still will have to test it there. 仍然必须在那里进行测试。

non-async method, sync over async 非异步方法,通过异步同步

Of course, I could do something similar: 当然,我可以做类似的事情:

public Task EndSession()
{
    _cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
    _logOutCommand.LogOutIfPossible().Wait();
    return _keepAliveTask;
}

But that is a no-go (sync over async). 但这是不可行的(通过异步进行同步)。 Plus it still has the problems of the previous approach. 另外,它仍然存在以前方法的问题。

non- async method using Task.WhenAll(...) 使用Task.WhenAll(...)async方法

Is a (valid) performance improvement but introduces more complexity: - difficult to get right without hiding a second exception (when both fail) - allows parallel execution 是一项(有效的)性能改进,但引入了更多的复杂性:-不隐藏第二个异常(两个都失败时)就很难纠正-允许并行执行

Since performance isn't key here i'd like to avoid the extra complexity. 由于性能不是关键,因此我想避免额外的复杂性。 Also, previously mentioned issue that it just moves the (verification) problem to the caller of the EndSession method applies here, too. 同样,前面提到的只是将(验证)问题移到EndSession方法的调用方的问题也适用于此。

observing effects instead of verifying calls 观察效果而不是验证呼叫

Now of course instead of "unit" testing method calls etc. I could always observe effects. 当然,现在不用“单元”测试方法调用等了。我总是可以观察到效果。 Which is: As long as _keepAliveTask hasn't ended the EndSession Task mustn't end either. 这是:只要_keepAliveTask尚未结束, EndSession Task EndSession不会结束。 But since I can't wait indefinite one has to settle for a timeout. 但是由于我等不及了,所以必须解决超时问题。 The tests should be fast so a timeout like 5 seconds is a no go. 测试应该很快,所以5秒钟之类的超时是不可行的。 So what I've done is: 所以我要做的是:

[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();
}

But I really really dislike this approach: 但是我真的很讨厌这种方法:

  • hard to understand 很难明白
  • unreliable 不可靠
  • or - when it's quite reliable - it takes a long time (which unit tests shouldn't). 或者-当它非常可靠时-会花费很长时间(不应该进行单元测试)。

If all you want is to verify that EndSession is awaiting _keepAliveTask (and you really have full control over _keepAliveTask ) then you can create your own awaitable type instead of Task the signals when it's awaited and check that: 如果您只想验证EndSession正在等待_keepAliveTask (并且您确实对_keepAliveTask拥有完全控制权),则可以创建自己的等待类型,而不是在等待Task时创建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;
    }
}

Since all you need to await something is that has a GetAwaiter method that returns something with IsCompleted , OnCompleted and GetResult you can use the dummy awaitable to make sure _keepAliveTask is being awaited: 由于您需要await事情就是拥有一个GetAwaiter方法,该方法返回带有IsCompletedOnCompletedGetResult 东西 ,因此您可以使用哑元awaitable来确保正在等待_keepAliveTask

_keepAliveTask = new MyAwaitable();
EndSession();
_keepAliveTask.IsAwaited.Should().BeTrue();

If you use some mocking framework you can instead make Task 's GetAwaiter return our MyAwaiter . 如果使用某种GetAwaiter框架,则可以使TaskGetAwaiter返回我们的MyAwaiter

  1. Use TaskCompletionSource and set its result at a known time. 使用TaskCompletionSource并在已知时间设置其结果。
  2. Verify that before setting the result, the await on EndSession hasn't completed. 确认在设置结果之前,尚未完成对EndSession的等待。
  3. Verify that after setting the result, the await on EndSession has completed. 确认设置结果后,对EndSession的等待已完成。

A simplified version could look like the following (using nunit): 简化版本可能如下所示(使用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
}

You can also add a short delay at the waitTask to ensure any synchronous execution would be faster, something like: 您还可以在waitTask上添加短暂的延迟,以确保任何同步执行都更快,例如:

var waitTask = Task.Run(async () =>
{
    await Task.Delay(1);
    await keepAliveTask;
 });

And if you don't trust your unit test framework to deal correctly with async, you can set a completed flag as part of the waitTask, and check for that in the end. 而且,如果您不信任单元测试框架正确处理异步,则可以将完成的标志设置为waitTask的一部分,并在最后进行检查。 Something like: 就像是:

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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM