对于部分涉及到 WPF UI 的部分,单元测试一般都难以进行。但是,如果只是使用到其中的 UI 线程调度,那就稍微容易一些。不过为了找到这个方法我做了很多天的尝试。
本文将提供一种在单元测试中运行 Dispatcher
的方法,以便能够在单元测试中测试到 Invoke/InvokeAsync
是否按要求执行。
我第一个想到的是在当前函数中执行 Dispatcher.Run
,但是 Run
之后就阻塞了,我还怎么测试呢?
于是我又想到我上个月写的辅助方法 UIDispatcher.RunNewAsync()
,在后台创建一个运行起来的 Dispatcher
。参见我博客 如何实现一个可以用 await 异步等待的 Awaiter - walterlv 中 UIDispatcher
的实现。
这方法确实可行,可以 await
。然而单元测试中只有一个单元测试可以通过,无论什么测试,只有第一个 Run
起来的可以通过,其它的全部无法完成(已知运行中,无法退出单元测试)。
最后,在 c# - Using the WPF Dispatcher in unit tests - Stack Overflow 发现其实可以先 Invoke
再 Run
,这样,即便是当前的单元测试线程也是可以正常完成的。
1
2
3
4
5
6
7
8
private void RunInDispatcher(Action action)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var frame = new DispatcherFrame();
dispatcher.InvokeAsync(() => action(dispatcher));
dispatcher.InvokeAsync(() => frame.Continue = false, DispatcherPriority.Background);
Dispatcher.PushFrame(frame);
}
这个方法借鉴了此前我和我朋友研究过的 WPF DoEvents(虽然已被弃用):
于是,单元测试可以这样做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[TestMethod]
public void TestSomething_SomethingHappened()
{
RunInDispatcher(async dispatcher =>
{
// 做一些事情。
// 然后……
dispatcher.InvokeAsync(action);
// 然后干些啥……
// 然后等待 Measure/Arrange。
await Dispatcher.Yield();
// 然后再验证值。
Assert.AreEqual(a, b);
});
}
Yield
的意思可以参见我的另一篇博客 出让执行权:Task.Yield, Dispathcer.Yield - walterlv。
以上。
参考资料
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/run-dispatcher-in-unit-test.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。