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

Posted

技术标签:

【中文标题】测试 WPF 控件而不将其添加到窗口【英文标题】:Testing WPF Control Without Adding it to a Window 【发布时间】:2011-10-30 23:44:49 【问题描述】:

我有一个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; 

更新

我已将标题从“单元测试...”更改为“测试...”,并将标签“单元测试”替换为“测试”。

我不希望就这到底是什么类别的测试而分道扬镳,因为它没有建设性。是的,可以说这不是“单元测试”,但这没有帮助。我想测试一个依赖于控件生命周期的问题,这涉及Loaded 事件。这是一个重要的回归测试,因为我无法控制的第 3 方组件取决于在 Loaded 提出的消息。

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

【问题讨论】:

你将不得不伪造/模拟窗口......但这看起来不再像单元测试了。 @Henk 你如何看到一个伪造的窗口导致我的用户控件被加载,从而触发它的Loaded 事件? 您可以尝试通过反射引发 Load 事件:***.com/questions/198543/… @mike 我怀疑 WPF 事件的实现方式不同。我无法使用链接中的方法大纲通过反射看到事件支持字段。已经尝试过这种方法,但会进一步挖掘。谢谢。 @Merlyn 我已经更新了示例单元测试。 【参考方案1】:

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

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 方法,因此如果您的测试需要应用程序状态,这将不起作用。

此外,父级的 Loaded 事件与其子级之间没有关系。如果测试要求子元素触发其 Loaded 事件,则辅助方法将需要手动遍历子控件并触发它们。

【讨论】:

+1 我希望不必求助于反思,但就我而言,这并不复杂。【参考方案2】:

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

WPF UIElements 继承了一个名为 RaiseEvent 的方法,该方法接受一个 RoutedEventArgs。这可以使用特定的 UIElement 的 .LoadedEvent 构建,让您获得最后的代码覆盖率。

我怀疑你仍然需要我的回答,但有人可能会。

【讨论】:

【参考方案3】:

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

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

最后:如果您不确定控件的 Loaded 事件在加载时会被触发(这是您进行此类集成测试的主要原因),那么您应该进行调查.

【讨论】:

+1 表示“如果您没有信心”的评论。有时最好不要教条地坚持一种方法,如果它没有给你带来任何好处。在这种情况下,代码审查可能足以验证事件绑定。 如果不是,则说明某处有问题需要解决。 @Robert @Merlyn 我已经从标题和标签中删除了“Unit”,因此很明显我想专门测试这个问题,而不会陷入它属于哪类测试的困境。我一点也不教条。在Loaded 事件中发布的消息在生命周期方面非常重要,以及消息如何与我没有 控制的系统中的其他第 3 方组件一起播放。尽管挑战和剖析这是什么类别的测试很诱人,但它并不能解决我的问题,这是一个有效且重要的测试用例。 @Robert 我完全有信心Loaded 事件将被触发——WPF 框架就是这样做的!我不确定的是重构、代码更改等会破坏这段代码——我想要一个回归测试。如果此代码被破坏,其他人就很难诊断出问题。失败的测试是解决此问题的绝佳方法。 我想我明白你的意思了。我相信答案是否定的。更准确地说,我相信虽然您最终可能会找到一种在 WPF 应用程序上下文之外创建此控件的实例并触发其Loaded 事件的方法,但您为使其成为可能所做的事情将破坏可靠性测试。

以上是关于测试 WPF 控件而不将其添加到窗口的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:将动画添加到 Picker 而不将其添加到值中

将文件推送到 Github/Gitlab 而不将其添加到 .git?

WPF:Scrollviewer 不将内容限制为窗口大小

我可以在设备上调试我的 iOS 应用程序而不将其添加到 Apple 配置门户吗?

删除一个单词而不将其添加到 Emacs 中的 kill-ring

如何计算GeoJSON的边界而不将其添加到地图中