异步任务测试

async task testing

我刚开始使用 Rhino Mocks 在代码中测试我的异步方法。但是,虽然通常代码工作得非常完美,但在使用 Rhino Mocks 进行模拟测试时,会出现一些奇怪的 async 问题。

假设我想测试以下调用多个 numberGenerators 的代码,它们将同时 运行 并等待它们全部完成:

public async Task MyTask()
{
    List<Task<List<int>>> numberTasks = new List<Task<List<int>>>();

    foreach (var numberGenerator in numberGenerators)
    {
        // GetNumbers is an 'async Task<List<int>>'
        // As you can see I don't use 'await' so it should just add it and go on.
        numberTasks.Add(numberGenerator.GetNumbers()); 
    }

    try
    {
        await Task.WhenAll(numberTasks);
    }
    catch (Exception e)
    {
        // Log exception if something happens at 'GetNumbers' (like time-out or whatever)
    }

    var numbers = new List<int>();
    numbers.AddRange(numberTasks.Where(task => task.Status == TaskStatus.RanToCompletion).SelectMany(task => task.Result));

    // Do something with the numbers
}

测试:

numberGenerator.Expect(x => x.GetNumbers())
    .WhenCalled(x => Thread.Sleep(10000))
    .Return(Task.FromResult(numbers));

我在上面的代码中使用了 'mocked' 上的两个生成器。现在,如果我 运行 带有 'non-mocked' 个对象的代码 numberTasks.Add 只是添加任务并继续。但是当我用 WhenCalled sleep 模拟它时,它会等待 10 秒,然后再进入 foreach 循环中的下一个。

我怎样才能改变我的模拟,让它像正常的 async Task 一样需要 10 秒才能完成?

奖金问题:我希望能够对 .Throw() 做同样的事情,这样我就可以在抛出异常之前等待 10 秒。

更新: 尽管我认为以下内容可以解决问题:

numberGenerator.Expect(x => x.GetNumbers())
    .Return(Task.Delay(10000).ContinueWith(x => numbers));

好像不是这样的。 Task.Delay 在 mock 创建时开始倒计时。因此,如果我创建此模拟,请等待 10 秒,然后 运行 测试将在 0 秒内完成。

所以我的问题仍然存在,如何在返回结果之前使用 delay 测试异步方法。

我个人没有使用过 rhino mock,所以我不知道它是否支持异步方法调用。

但是Thread.Sleep(10000)是同步代码。如果你想异步等待,你必须使用 Task.Delay(10000)

或者也可以使用 Task.Run( () => Thread.Sleep(10000) ),但 Task.Delay(10000) 是首选方式和最佳实践。

异步方法同步运行,直到达到 await。这意味着如果您在模拟操作中 returning 任务之前等待,则此等待将是同步的。您想要做的是 return 一段时间后立即完成的任务。

你可以用 Task.Delay:

numberGenerator.Expect(x => x.GetNumbers()).
        Return(Task.Delay(10000).ContinueWith(t => numbers));

Task.ContinueWith用于延迟完成后加上实际的return值

如果您想抛出异常而不是 returning 一个值,那也可以在延续中完成。

由于 Task.Delay(10000).ContinueWith(t => numbers) 在您创建模拟时进行评估,因此每次调用都会 return 相同的任务(将在创建模拟后 10 秒完成)。

如果需要每次都创建一个新的任务需要传递一个delegate。 Return 不接受委托,因此您需要使用 WhenCalled:

numberGenerator.Expect(x => x.GetNumbers()).
        WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers));

现在,虽然 WhenCalled 用于设置 return 值,但使用 Return 仍然需要 in order to determine the type of the return result。它得到什么值并不重要,因为 return 值仍将由 WhenCalled:

设置
numberGenerator.Expect(x => x.GetNumbers()).
    WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers)).
    Return(Task.FromResult(new List<int>()));