如何对包含Task.Delay的代码进行单元测试?

Mis*_*hex 7 tdd unit-testing task-parallel-library system.reactive async-await

如何单元测试具有等待Task.Delay的组件,而不必等待它.

例如,

public void Retry()
{
    // doSomething();
    if(fail)
       await Task.Delay(5000);
}
Run Code Online (Sandbox Code Playgroud)

我需要测试失败分支,但我不希望我的测试等待5秒.

在任务并行库中是否有类似rx虚拟时间调度的功能

Joh*_*ers 8

计时器只是外部依赖的另一种形式.唯一的区别是它取决于时钟而不是数据库.和任何其他外部依赖一样,它使测试变得困难; 因此,答案是改进设计,直到它易于测试.

遵循依赖注入原则建议您应该在对象的构造函数中传递计时器,这使您能够注入测试存根而不是依赖于实时计时器.

请注意这可以改善您的设计.在许多情况下,超时时间根据谁的呼叫而变化.这将建立超时期限的责任转移到等待它的应用程序层.这层应该了解它愿意等多久.


Ste*_*ary 5

任务并行库中是否有类似 rx 虚拟基于时间的调度之类的东西?

不,那里没有。您的选择是定义可以使用测试存根实现的“计时器服务”,或者使用 Microsoft Fakes 拦截对Task.Delay. 我更喜欢后者,但它只是 VS Ultimate 上的一个选项。


ale*_*xey 5

1) 定义您自己的 Task.Delay 实现。

public static class TaskEx
{
    private static bool _shouldSkipDelays;

    public static Task Delay(TimeSpan delay)
    {
        return _shouldSkipDelays ? Task.FromResult(0) : Task.Delay(delay);
    }

    public static IDisposable SkipDelays()
    {
        return new SkipDelaysHandle();
    }

    private class SkipDelaysHandle : IDisposable
    {
        private readonly bool _previousState;

        public SkipDelaysHandle()
        {
            _previousState = _shouldSkipDelays;
            _shouldSkipDelays = true;
        }

        public void Dispose()
        {
            _shouldSkipDelays = _previousState;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2) 在代码中的任何地方都使用 TaskEx.Delay 而不是 Task.Delay。

3) 在您的测试中使用 TaskEx.SkipDelays:

[Test]
public async Task MyTest()
{
    using (TaskEx.SkipDelays())
    {
        // your code that will ignore delays
    }
}
Run Code Online (Sandbox Code Playgroud)