【问题标题】:Testing WPF Control Without Adding it to a Window测试 WPF 控件而不将其添加到窗口
【发布时间】: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 事件:stackoverflow.com/questions/198543/…
  • @mike 我怀疑 WPF 事件的实现方式不同。我无法使用链接中的方法大纲通过反射看到事件支持字段。已经尝试过这种方法,但会进一步挖掘。谢谢。
  • @Merlyn 我已经更新了示例单元测试。

标签: c# wpf testing user-controls


【解决方案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 事件的方法,但您为使其成为可能所做的事情将破坏可靠性测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-26
    • 2021-03-29
    • 1970-01-01
    • 2016-04-03
    • 1970-01-01
    相关资源
    最近更新 更多