简体   繁体   English

异步任务测试

[英]async task testing

I just started using Rhino Mocks that test my async methods within the code. 我刚刚开始使用Rhino Mocks在代码中测试我的异步方法。 But while normally the code works just perfect but while mocking tests with Rhino Mocks there are some weird async problems. 虽然通常代码工作得很完美但是在使用Rhino Mocks进行模拟测试时会出现一些奇怪的async问题。

Let's say I want to test the following code which calls multiple numberGenerators that all will run at the same time and wait till they are all done: 假设我想测试以下代码,该代码调用多个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
}

Is tested with: 测试用:

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

I use two of the generators on 'mocked' in the code above. 我在上面的代码中使用了'mocked'中的两个生成器。 Now if I run the code with 'non-mocked' objects numberTasks.Add just adds the Task and moves on. 现在,如果我使用'非模拟'对象运行代码numberTasks.Add只需添加任务并继续。 But when I mock it with the WhenCalled sleep it waites for 10 seconds before going to the next one in the foreach loop. 但是,当我用WhenCalled sleep模拟它时,它等待10秒钟,然后转到foreach循环中的下一个。

How can I change my mock that it acts like a normal async Task that takes 10 seconds to complete? 如何更改我的模拟,它像一个需要10秒才能完成的正常async Task

Bonus question: Can want to be able to do the same with .Throw() so I can wait 10 seconds before throwing an Exception. 奖金问题:可以希望能够使用.Throw()做同样的事情,这样我可以在抛出异常之前等待10秒。

Update: Although I thought the following would fix the problem: 更新:虽然我认为以下将解决问题:

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

This doesn't seem to be the case. 似乎并非如此。 The Task.Delay starts counting down at the moment this mock is created. 在创建此mock时,Task.Delay开始倒计时。 So if I create this mock, wait 10 seconds and then run the test it will be done in 0 seconds. 所以如果我创建这个模拟,等待10秒然后运行测试它将在0秒内完成。

So my question remains, how can I test an async method with a delay before returning the result. 所以我的问题仍然存在,如何在返回结果之前测试一个delay的异步方法。

An async method runs synchronously until an await is reached. 异步方法同步运行,直到达到await That means if you wait before returning a task in the mocked operation this wait will be synchronous. 这意味着如果您在模拟操作中返回任务之前等待,则此等待将是同步的。 What you want to do is return a task immediately that completes after some time. 你想要做的是立即返回一个任务,一段时间后完成。

You can do that with Task.Delay : 你可以使用Task.Delay做到这Task.Delay

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

Task.ContinueWith is used to add the actual return value after the delay is completed. Task.ContinueWith用于在延迟完成后添加实际返回值。

If you would like to throw an exception instead of returning a value, that too can be done inside the continuation. 如果您想抛出异常而不是返回值,那么也可以在continuation中完成。

Since Task.Delay(10000).ContinueWith(t => numbers) is evaluated when you create the mock, each call would return the same task (which will be completed 10 seconds after the mock was created). 由于在创建模拟时会评估Task.Delay(10000).ContinueWith(t => numbers) ,因此每次调用都将返回相同的任务(将在创建模拟后10秒完成)。

If you need to create a new task each time you need to pass a delegate. 如果每次需要传递委托时需要创建新任务。 Return doesn't accept a delegate so you need to use WhenCalled : Return不接受委托,因此您需要使用WhenCalled

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

Now, while WhenCalled is used to set the return value using Return is still required in order to determine the type of the return result . 现在,当使用WhenCalled来设置返回值时,仍然需要使用Return 来确定返回结果的类型 It doesn't matter what value it gets as the return value will still be set by WhenCalled : 它得到的值无关紧要,因为返回值仍将由WhenCalled设置:

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

Personally I haven't worked with rhino mock, so I don't know if it supports async Method calls. 我个人没有使用过rhino mock,所以我不知道它是否支持异步Method调用。

But Thread.Sleep(10000) is a synchronous code. 但是Thread.Sleep(10000)是一个同步代码。 If you want await in asynchronously you have to use Task.Delay(10000) . 如果要异步等待,则必须使用Task.Delay(10000)

Or alternatively use Task.Run( () => Thread.Sleep(10000) ) though, Task.Delay(10000) is preferred way and best practice. 或者使用Task.Run( () => Thread.Sleep(10000) )Task.Delay(10000)是首选方式和最佳实践。

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

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