[英]How to write unit test cases for async methods?
我想通过模拟依赖项来编写单元测试用例。 总体流程如下。
我们有一个WorklistLoader
,它有一个异步方法LoadWorklistItemsAsync()
。 要完成此任务, WorklistLoader
依赖于较低层API(我想模拟) QueryManager.StartQueryTask()
。 StartQueryTask()
也是一个异步方法,它查询文件系统并定期引发ProgressChanged()
,然后在最后引发CompletedEvent
。 StartQueryTask()
返回对TPL Task
的引用。
StartQueryTask
签名是
Task StartQueryTask(
"SomeId",
EventHandler<ProgressChanged> progressChanged,
EventHandler<QueryCompleted> queryCompleted);
一旦WorklistLoader
从QueryManager
接收到ProgressChanged
事件,它就会进行一些处理,然后引发其ProgressChanged
事件( ViewModel
已订阅)。
我想测试LoadWorklistItemsAsync()
的方法WorklistLoader
与嘲笑QueryManager.StartQueryTask()
这是我的问题。
Async()
方法编写单元测试的最佳实践是什么? Task
类型的方法) 另一个问题是
为了模拟某些东西,你需要能够将模拟注入到你正在使用的任何东西中。 有许多方法可以实现这一点,包括Inversion of Control容器,环境上下文引导代码等。最简单的方法是构造函数注入和引导环境上下文,以便在您想要测试时获得所需的模拟。 例如:
WorklistLoader worklistLoader;
[SetUp]
public void Setup()
{
worklistLoader = new WorklistLoader(new MockQueryManager());
}
[Test]
public async Task TestWorklistLoader()
{
await worklistLoader.LoadWorklistItemsAsync();
}
这也意味着,WorklistLoader不依赖于QueryManager
而是依赖于像一个抽象IQueryManager
是MockQueryManager
会实现。
MockQueryManager
可能是这样的:
public class MockQueryManager : IQueryManager
{
public Task StartQueryTask() {/* TODO: */}
}
当然,您的原始QueryManager必须实现IQueryManagear:
public class QueryManager : IQueryManager
{
public Task StartQueryTask() {/* TODO: */}
}
现在,在测试使用TPL的类方面,您会注意到我已经实现了一个返回Task的异步测试方法。 这告诉测试运行者在认为测试方法已执行之前等待结果。 如果您只是写了类似的东西:
[Test]
public async void TestWorklistLoader()
{
await worklistLoader.LoadWorklistItemsAsync();
}
运行器将执行TestWorklistLoader
,它将在LoadWorklistItemsAsync
完成之前立即返回,并可能绕过任何断言。
如果您没有使用C#5,那么我建议您只需等待单元测试中的任务完成。 例如:
[Test]
public void TestWorklistLoader()
{
var task = worklistLoader.LoadWorklistItemsAsync();
if(!task.IsComplete()) task.Wait();
}
这可能看起来很糟糕,但我采用的类似测试构建方案的简单方法是使用这个方便的功能:
/// <summary>
/// Wait no longer than @waitNoLongerThanMillis for @thatWhatWeAreWaitingFor to return true.
/// Tests every second for the
/// </summary>
/// <param name="thatWhatWeAreWaitingFor">Function that when evaluated returns true if the state we are waiting for has been reached.</param>
/// <param name="waitNoLongerThanMillis">Max time to wait in milliseconds</param>
/// <param name="checkEveryMillis">How often to check for @thatWhatWeAreWaitingFor</param>
/// <returns></returns>
private bool WaitFor(Func<bool> thatWhatWeAreWaitingFor, int checkEveryMillis, int waitNoLongerThanMillis)
{
var waitedFor = 0;
while (waitedFor < waitNoLongerThanMillis)
{
if (thatWhatWeAreWaitingFor()) return true;
Console.WriteLine("Waiting another {0}ms for a situation to occur. Giving up in {1}ms ...", checkEveryMillis, (waitNoLongerThanMillis - waitedFor));
Thread.Sleep(checkEveryMillis);
waitedFor += checkEveryMillis;
}
return false;
}
用法:
// WaitFor (transaction to be written to file, checkEverySoOften, waitNoLongerThan)
int wait = (Settings.EventHandlerCoordinatorNoActivitySleepTime + 5) * 1000;
var fileExists = WaitFor(() => File.Exists(handlerConfig["outputPath"]), checkEveryMillis: 1000, waitNoLongerThanMillis: wait);
if(!fileExists)
Assert.Fail("Waited longer than " + wait + " without any evidence of the event having been handled. Expected to see a file appear at " + handlerConfig["outputPath"]);
在我的场景中,我期待写一个文件,这就是我等待的。 在你的情况下,你正在等待progressChanged和queryCompleted被调用,所以你最好注入那些Mocks,你要等待的表达式是:
var eventsCalled = WaitFor(() => progressChanged.Called(Time.Once) && queryCompleted.Called(Times.Once), checkEveryMillis: 1000, waitNoLongerThanMillis: wait);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.