测试WPF控件而不将其添加到窗口

本文关键字:添加 窗口 WPF 控件 测试 | 更新日期: 2023-09-27 18:06:23

我有一个UserControl,它在其Loaded事件中发布EventAggregator消息。为了测试这一点(并获得Loaded事件引发),我目前正在创建一个窗口并向其添加控件,然后等待Loaded事件被引发。

是否有任何方法来设置一个测试,使Loaded事件火灾,而不必创建和添加控件到窗口?

例如:

[Test, RequiresSTA]
public void active_thingy_message_is_published_on_loaded()
{
    const string TestMsg = "Active thingy changed";
    using (AutoResetEvent loadedEvent = new AutoResetEvent(false))
    {
        DummyEventService eventService = new DummyEventService();                
        DummyControl control = new DummyControl(eventService, TestMsg);
        control.Loaded += delegate { loadedEvent.Set(); };
        Assert.That(eventService.Message, Is.Null, "Before.");
        Window window = new Window { Content = control };
        window.Show();                
        loadedEvent.WaitOne();
        window.Dispatcher.InvokeShutdown();
        Assert.That(eventService.Message, Is.EqualTo(TestMsg), "After.");
    }
}
private class DummyControl : UserControl
{
    public DummyControl(DummyEventService eventService, string testMsg)
    {
        Loaded += delegate { eventService.Publish(testMsg); };
    }
}
private class DummyEventService
{
    public string Message { get; private set; }
    public void Publish(string msg) { Message = msg; }
}

我将标题从"Unit Testing…"改为"Testing…",并将标签"Unit - Testing"替换为"Testing"。

我不喜欢对这到底是哪类测试吹毛求疵,因为它不是建设性的。是的,可以说这不是一个"单元测试",但这并没有帮助。我想测试一个依赖于控件生命周期的问题,这涉及Loaded事件。这是一个重要的回归测试,因为我无法控制的第三方组件依赖于在Loaded中提出的消息。

可以在不将控件添加到窗口的情况下引发Loaded事件吗?

测试WPF控件而不将其添加到窗口

如果您只对触发目标控件的Loaded事件感兴趣,那么Reflection应该可以解决这个问题。

public static void RaiseLoadedEvent(FrameworkElement element)
{
    MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded",
        BindingFlags.Instance | BindingFlags.NonPublic);
    RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent);
    eventMethod.Invoke(element, new object[] { args });
}

这实际上会触发每个FrameworkElement中存在的OnLoaded方法,所以如果您的测试需要Application状态,这将不起作用。

同样,父节点的Loaded事件和它的子节点之间也没有关系。如果测试要求子元素触发它们的Loaded事件,那么helper方法将需要手动遍历子控件并触发它们。

在过去的两年里,也许事情发生了变化。为了代码覆盖率,我也遇到了这个问题,这是解决方案。

WPF ui元素继承了一个名为RaiseEvent的方法,该方法接受一个RoutedEventArgs参数。这可以使用特定元素的. loadedevent来构建,允许您获得最后的代码覆盖率。

我怀疑你是否还需要我的答案,但也许有人需要。

Loaded事件处理程序中的内容重构为自己的方法,并让Loaded事件处理程序调用它。编写单元测试来测试重构的方法,而不是Loaded事件。

单元测试是验证一个单元是否做了它应该做的事情。测试单元如何与其他单元互操作是集成测试。做集成测试当然是有价值的,并且能够对集成单元进行回归测试,但这与单元测试是不同的任务。

最后:如果你对控件的Loaded事件在加载时被触发没有信心(这是你做这样的集成测试的主要原因),那么有些地方非常错误,你应该调查一下。

Popup for me:

public static void LoadControl(Control cnt)
{
    var popup = new Popup();
    popup.Child = cnt;
    popup.IsOpen = true;
}

它看起来比Window好多了,因为屏幕上没有显示任何东西。在服务器上使用应该是安全的。(我还没有在。net Framework中尝试过,只有在。net 6中)