如何测试使用 DispatcherTimer 的 class?
How can I test a class that uses DispatcherTimer?
我发现了几个 Stack Overflow 问题以及一些已经涉及到这个主题的博客文章,但不幸的是 none 满足了我的需求。我将从一些示例代码开始,以展示我想要完成的工作。
using System;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyApp
{
[TestClass]
public class MyTests
{
private int _value;
[TestMethod]
public async Task TimerTest()
{
_value = 0;
var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
timer.Tick += IncrementValue;
timer.Start();
await Task.Delay(15);
DispatcherUtils.DoEvents();
Assert.AreNotEqual(0, _value);
}
private void IncrementValue(object sender, EventArgs e)
{
_value++;
}
}
internal class DispatcherUtils
{
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object frame)
{
((DispatcherFrame)frame).Continue = false;
return null;
}
}
}
如果我不使用 DispatcherTimer,而是使用普通 Timer,则此代码可以正常工作。但是 DispatcherTimer 永远不会触发。我错过了什么?我需要什么才能启动它?
如果你能避免在你的被测系统中使用 DispatcherTimer
并使用抽象来代替(Rx 有一个很好的抽象,叫做 IScheduler
),那是最好的。这种抽象允许您明确控制单元测试中的时间流,而不是让您的测试以 CPU 时间为条件。
但是如果您现在只对单元测试感兴趣,那么您需要创建一个执行消息泵送的 STA 线程 并且 有一个适当的 Dispatcher
安装。所有 "run this code on the dispatcher" 操作只是将委托包装在 Win32 消息中,如果您没有 Win32 消息泵循环 in a Dispatcher
(在创建计时器之前),那么这些消息将不会被处理。
最简单的方法是使用 WpfContext
from here:
[TestMethod]
public async Task TimerTest()
{
await WpfContext.Run(() =>
{
_value = 0;
var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
timer.Tick += IncrementValue;
timer.Start();
await Task.Delay(15);
Assert.AreNotEqual(0, _value);
});
}
同样,这种方法不合标准,因为它取决于时间安排。因此,如果您的防病毒软件不高兴并决定检查您的单元测试,它可能会虚假地失败。像 IScheduler
这样的抽象可以实现可靠的单元测试。
我发现了几个 Stack Overflow 问题以及一些已经涉及到这个主题的博客文章,但不幸的是 none 满足了我的需求。我将从一些示例代码开始,以展示我想要完成的工作。
using System;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyApp
{
[TestClass]
public class MyTests
{
private int _value;
[TestMethod]
public async Task TimerTest()
{
_value = 0;
var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
timer.Tick += IncrementValue;
timer.Start();
await Task.Delay(15);
DispatcherUtils.DoEvents();
Assert.AreNotEqual(0, _value);
}
private void IncrementValue(object sender, EventArgs e)
{
_value++;
}
}
internal class DispatcherUtils
{
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object frame)
{
((DispatcherFrame)frame).Continue = false;
return null;
}
}
}
如果我不使用 DispatcherTimer,而是使用普通 Timer,则此代码可以正常工作。但是 DispatcherTimer 永远不会触发。我错过了什么?我需要什么才能启动它?
如果你能避免在你的被测系统中使用 DispatcherTimer
并使用抽象来代替(Rx 有一个很好的抽象,叫做 IScheduler
),那是最好的。这种抽象允许您明确控制单元测试中的时间流,而不是让您的测试以 CPU 时间为条件。
但是如果您现在只对单元测试感兴趣,那么您需要创建一个执行消息泵送的 STA 线程 并且 有一个适当的 Dispatcher
安装。所有 "run this code on the dispatcher" 操作只是将委托包装在 Win32 消息中,如果您没有 Win32 消息泵循环 in a Dispatcher
(在创建计时器之前),那么这些消息将不会被处理。
最简单的方法是使用 WpfContext
from here:
[TestMethod]
public async Task TimerTest()
{
await WpfContext.Run(() =>
{
_value = 0;
var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
timer.Tick += IncrementValue;
timer.Start();
await Task.Delay(15);
Assert.AreNotEqual(0, _value);
});
}
同样,这种方法不合标准,因为它取决于时间安排。因此,如果您的防病毒软件不高兴并决定检查您的单元测试,它可能会虚假地失败。像 IScheduler
这样的抽象可以实现可靠的单元测试。