【问题标题】:Unit test that a void method on a class is executed when an event fires事件触发时执行类上的 void 方法的单元测试
【发布时间】:2011-05-06 04:43:53
【问题描述】:

假设您有 2 个类,Listener 和 Talker。 Talker 有一个 Talking 事件,当这个事件被触发时,Listener 应该执行一个 void 方法 HeardTalk,大致如下:

public class Talker
{
   public event EventHandler Talking;

   public void Talk()
   {
      if (Talking != null)
      {
         Talking(this, null);
      }
   }
}

public class Listener
{
   public void StartListening(Talker talker)
   {
      talker.Talking += HeardTalk;
   }

   public void HeardTalk(object sender, EventArgs e)
   {
      // do something private here
   }
}

如果没有反映该方法被调用的公共状态,那么一旦调用了 StartListening,就会调用 HeardTalk,您将如何进行单元测试?为了验证的目的,我可以添加这样的状态,但它看起来很笨拙。理想情况下,我想以类似于 Mocking 框架所做的方式断言调用已进行,但我无法模拟被测类。

有没有一种优雅的方式来断言在 SUT 上调用了一个方法,而不只是为了可测试性而对其进行修改?

【问题讨论】:

  • 我理解当您说没有公共状态可以检查是否调用了 HeardTalk,这是否意味着执行的 HeardTalk 代码不包含可以检查的副作用?
  • 史蒂夫:是的,我就是这个意思。

标签: c# unit-testing events mocking


【解决方案1】:

触发的事件必须至少有一些可衡量的副作用。也许您正在记录事件,将某些内容保存到数据存储中,或者更改类的内部状态。

如果您要调用任何外部依赖项,则应通过构造函数/属性注入传入并进行模拟。在改变内部状态的情况下,即使这样做也必须有一些可以从外部观察到的副作用。否则还有什么意义?

最后,如果您只想检查方法是否被调用,那么您实际测试的是什么?同样,方法被调用一定是有原因的,而且这个原因必须有一些可以在某处观察到的影响......

【讨论】:

  • 总的来说,我同意——不可能有一种绝对没有明显影响的方法;该方法应该做一些事情,这通常是可观察的。当我编写一些人为的代码来说明一个想法时,这个问题出现了。这种情况下的副作用是向用户弹出一个消息框。这个问题主要是理论上的:是否可以检查是否已拨打电话,例如确保接线正确?
  • 三思后,我的 MessageBox 示例非常糟糕 - 我无法提出一个不涉及 UI 的现实示例,所以我认为我必须同意您的回答:要么有方式来验证方法的效果,还是真的不适合单元测试。
  • 当然,最终每个方法都会有一些可衡量的副作用,否则毫无意义。但通常在现实生活中的测试中,这种影响可能在时间和/或空间上很远(想想例如异步 Web 方法调用),因此或由于某些其他原因,这些影响可能并不总是适用于自动化测试。在这种情况下,您进行所谓的“交互测试”:不断言结果,而是验证某个方法是否被调用(或未被调用),可选择检查该方法的参数。这通常是基于事件的案例,例如示例中的案例。
【解决方案2】:

您可以使用商业广告 Typemock Isolator 轻松完成这些事情,它允许您有选择地在“真实”对象上“模拟”单个方法。
测试如下所示:

[Test, Isolated]
public void HeardTalk_GetsCalled()
{
    // --- Arrange ---
    var talker = new Talker();
    var listener = new Listener();
    bool heardTalkWasCalled = false;
    Isolate.WhenCalled(() => listener.HeardTalk(null, null))                     // Selectively 'mock' the call to 'listener.HeardTalk()'
                                     .DoInstead(x => heardTalkWasCalled = true); // on the live object (arguments are ignored by default)

    // --- Act ---
    listener.StartListening(talker);
    talker.Talk();

    // --- Assert ---
    Assert.IsTrue(heardTalkWasCalled);
}

诚然,Typemock Isolator 需要支付一些许可费用。但是,如果您对测试很认真,并且需要测试很多类似上述的东西,那么它的强度和灵活性都值得每一分钱。

注意事项:

  • 您可以使用 free MS Moles framework(适用于 Visual Studio 2010)进行相同操作,它是 Typemock 的合适替代品,如果您只需要偶尔进行此类测试。它通常需要更多的工作/代码,增加了测试的复杂性,并且对性能的要求更高,尤其是在编译期间,但对于较小的测试套件来说已经足够了。
  • 还有一个来自 Telerik 的商业替代方案,称为 JustMock。因为它很新,我只能说它存在……

HTH!
托马斯

【讨论】:

  • 谢谢托马斯,它确实有帮助。我一直在考虑 TypeMock,但因为价格原因推迟了,也许我应该重新考虑 - 只听说过它的好消息。 Moles 也是一个很好的建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 2017-05-11
  • 1970-01-01
  • 2023-03-22
  • 2019-10-27
  • 2015-10-09
  • 1970-01-01
相关资源
最近更新 更多