异步任务测试
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>()));
我刚开始使用 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>()));