如何测试使用DispatcherTimer的类?

6

我在Stack Overflow上找到了几个问题以及几篇博客文章,它们都涉及到这个主题,但不幸的是,它们都不能满足我的需求。我将从一些示例代码开始展示我想要完成的内容。

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,那么这段代码可以正常工作。但是DispatcherTimer从未触发。我错过了什么?我需要做什么才能让它触发?


我认为您需要将SynchronizationContext设置为DispatcherSynchronizationContext的实例。否则,在等待的另一侧,您将处于一个新线程上,该线程将具有新的调度程序,这不是您想要处理事件的调度程序。 - Mike Zboray
1个回答

10

最好能避免在系统测试中使用DispatcherTimer,而是使用一个抽象(Rx有一个很好的叫做IScheduler)。这种抽象允许您明确地控制单元测试中的时间流逝,而不是使测试依赖于CPU时间。

但如果您现在只对单元测试感兴趣,那么您需要创建一个执行消息泵操作并安装适当的Dispatcher的STA线程。所有“在调度程序上运行此代码”的操作仅将委托封装在Win32消息中,如果您没有在Dispatcher中(在创建计时器之前)具有Win32消息泵循环,则这些消息将无法被处理。

最简单的方法是使用WpfContext

[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 这样的抽象可以保证单元测试的可靠性。


2
非常好的回答。非常感谢您提供的所有详细信息。 - soapergem
1
我不得不采用这种方法,因为我正在处理一个庞大的遗留代码库,重构意味着巨大的回归风险。 - datchung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接