C# 确认正在等待任务

C# 确认正在等待任务,c#,.net,unit-testing,task-parallel-library,async-await,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 _keepAliveTas

我想测试以下代码:

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
返回的
任务是否等待
\u 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
}
单元测试看起来(简化):

但这是不可能的(异步同步)。此外,它仍然存在以前方法的问题

使用
Task.whalll(…)
是一种(有效的)性能改进,但会带来更大的复杂性: -如果不隐藏第二个异常(当两个异常都失败时),就很难纠正错误 -允许并行执行

由于性能不是这里的关键,我希望避免额外的复杂性。此外,前面提到的问题,即它只是将(验证)问题转移到
EndSession
方法的调用者,也适用于这里

观察效果而不是验证调用 现在当然不用“单元”测试方法调用等,我可以随时观察效果。也就是说:只要
\u keepAliveTask
没有结束
结束会话
任务
也不能结束。但是,由于我不能无限期地等待,一个人不得不满足于超时。测试应该很快,所以像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();
}
[测试]
public void EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAlive=new TaskCompletionSource();
_cancelableLoopingTaskFactory
.Setup(x=>x.Start(It.IsAny(),It.IsAny())
.Returns(keepAlive.Task);
_testee.StartSendingKeepAlive();
_testee.EndSession()
.Wait(时间跨度从毫秒(20))
.Should().BeFalse();
}
但我真的不喜欢这种方法:

  • 难懂
  • 不可靠
  • 或者——当它相当可靠时——它需要很长时间(单元测试不应该)

如果您只想验证
结束会话
是否正在等待
\u keepAliveTask
(并且您确实可以完全控制
\u keepAliveTask
),那么您可以创建自己的等待类型,而不是
任务
等待时的信号,并检查:

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;
    }
}
由于您需要等待的是一个具有
GetAwaiter
方法的
GetAwaiter
方法,该方法返回带有
IsCompleted
OnCompleted
GetResult
的内容,因此您可以使用虚拟等待来确保
\u keepAliveTask
正在等待:

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

  • 使用
    TaskCompletionSource
    并在已知时间设置其结果
  • 确认在设置结果之前,等待结束会话尚未完成
  • 验证设置结果后,等待结束会话是否已完成
  • 简化版本可能如下所示(使用nunit):

    如果您不相信您的单元测试框架能够正确处理异步,那么您可以在waitTask中设置一个completed标志,并在最后进行检查。比如:

    var waitTask = Task.Run(async () =>
    {
        await Task.Delay(1);
        await keepAliveTask;
     });
    
    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);
    

    如果
    \u keepAliveTask
    被取消并等待,它应该抛出一个
    操作取消异常,不是吗?我不太确定你到底想做什么。您想确保等待
    LogOutIfPossible
    ?您实际想要测试什么?
    EndSession
    完成了吗?@l3arnon
    EndSession
    只完成一次
    \u keepAliveTask
    completes@YuvalItzchakov是的,我认为这是一个可能的解决办法。让任务返回结果(或者在没有结果的情况下抛出异常),并验证“包装”任务是否返回相同的结果(抛出异常)。非常感谢。你使用什么样的测试框架?MS Test支持异步测试方法。我将尝试一下。仍然需要确保自定义的
    MyAwaitable
    正确实现,不过……这基本上就是我描述的观察效果,而不是验证调用。这不可靠。为了达到足够的可靠性,测试必须花费很长时间。在单元测试中,等待一秒(或更长)是不可接受的。除非你能将它们并行运行,否则问题就不会那么严重。
    _keepAliveTask = new MyAwaitable();
    EndSession();
    _keepAliveTask.IsAwaited.Should().BeTrue();
    
    [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
    }
    
    var waitTask = Task.Run(async () =>
    {
        await Task.Delay(1);
        await keepAliveTask;
     });
    
    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);