模拟具体课程-不推荐


11

我刚刚读了《成长面向对象的软件》一书的摘录,其中解释了为什么不建议模拟具体类的一些原因。

以下是MusicCentre类的单元测试的一些示例代码:

public class MusicCentreTest {
  @Test public void startsCdPlayerAtTimeRequested() {
    final MutableTime scheduledTime = new MutableTime();
    CdPlayer player = new CdPlayer() { 
      @Override 
      public void scheduleToStartAt(Time startTime) {
        scheduledTime.set(startTime);
      }
    }

    MusicCentre centre = new MusicCentre(player);
    centre.startMediaAt(LATER);

    assertEquals(LATER, scheduledTime.get());
  }
}

和他的第一个解释:

这种方法的问题在于,它使对象之间的关系保持隐式。我希望我们现在已经弄清楚了,使用模拟对象进行测试驱动开发的目的是发现对象之间的关系。如果我是子类,则域代码中没有任何东西可以使这种关系可见,而只是对象上的方法。这使得很难看到支持这种关系的服务是否在其他地方有用,下次我与该类一起工作时,我将不得不再次进行分析。

他说的时候我无法确切知道他的意思:

这使得很难看到支持这种关系的服务是否在其他地方有用,下次我与该类一起工作时,我将不得不再次进行分析。

我了解该服务对应于MusicCentre称为的方法startMediaAt

他在“其他地方”是什么意思?

完整的摘录在这里:http : //www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html


在我的博客上添加了评论,因为我无法从这些引文中弄清楚他的意思。
oligofren

@oligofren这确实是一个伟大的谜:) ...
Mik378

Answers:


6

该文章的作者正在提倡使用接口而不是成员类。

It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.

他担心以后会重新发现的关系是,MediaCentre类不需要所有CdPlayer对象。他的主张是,通过使用接口(可能仅限于开始|停止),可以更轻松地了解交互的真正含义。

“其他位置”仅表示其他对象可能具有类似的受限关系,并且不需要完整的成员对象-通过接口包装的功能的子集就足够了。

当您展开所有潜在功能时,该声明就变得更有意义了:

  • 开始
  • 暂停
  • 记录
  • 随机播放顺序
  • 样本曲目,歌曲开头
  • 样本曲目,歌曲的随机样本
  • 提供媒体信息
  • ...

现在,他所说的“我只需要开始和停止”变得更加合理。使用具体的成员对象而不是接口会使将来的开发人员不清楚真正需要什么。从MediaCentre对CdPlayer中的所有其他功能运行单元测试是浪费测试工作的,因为它们属于“不在乎”状态。如果Record在这种情况下该功能无法正常工作,我们真的不在乎,因为它不是必需的。但是将来的维护者不一定会根据编写的代码知道这一点。

最终,作者的前提是仅使用所需的内容,并向将来的维护者明确说明以前需要的内容。目的是在后续维护期间最大程度地减少返工/重新分析代码模块。


感谢您的精彩回答。但是您说过:“对所有其他功能进行单元测试是浪费测试工作,因为它们属于“无关”状态。” 难道不是:“为其他每个功能创建模拟都是浪费测试工作,因为它们属于“不在乎”状态。”?
Mik378

@ Mik378-是的,这正是我要得到的,我只是用不同的措词了。我更新了答案,使之更加清楚。

但我发现“运行单元测试”一词令人困惑。这意味着MusicCentre将要对其合作者进行单元测试……实际上,它会对其合作者进行MOCKS以便对其OWN服务进行单元测试。顺便说一句,我现在理解的含义了:)
Mik378,2012年

@ Mik378-我们说的是同样的话,为此我可能使用的术语不够精确。抱歉造成混乱。

4

这使得很难看到支持这种关系的服务是否在其他地方有用,下次我与该类一起工作时,我将不得不再次进行分析。

在考虑了很多之后,我得到了对此报价的可能解释:

引用的“服务”对应于“调度事实”。这可以通过名为“ ScheduledDevice”的名称明确的“一个角色集中”的接口来表示,或者通过不依赖于任何接口的具体方法实现来隐式表示。

在上面的示例中,调度由名为的整个功能齐全的对象表示CDPlayer。因此,它仍然导致MusicCentre与“调度事实” 之间的隐式关系。

因此,如果我们开始注入具体的类并将其模拟到高级对象中;当我们要测试这些对象时,我们必须分析每个注入的“具体”对象,以查看它们是否呈现出我们必须嘲弄的特定关系,因为它们是隐藏的(隐式)。相反,始终通过接口编码允许开发人员直接找出高级对象将要服务的那种关系,并因此检测必须模拟的功能以隔离单元测试。


我想你已经知道了。不幸的是,我没有收到您评论的通知。
Steve Freeman

3

我在这里指的服务是CDPlayer.scheduleToStartAt()。这就是MediaCentre所说的-它是运作所需的合作者。MediaCentre是被测试的对象。

这样的想法是,如果我明确说明MediaCentre所依赖的内容而不是实现类,则可以为该依赖项角色命名并进行讨论。MediaCentre所需要知道的只是它与ScheduledDevices通信。随着系统其余部分的更改,除非媒体功能发生变化,否则我无需更改MediaCentre。

有帮助吗?


(这篇伟大文章的作者:))我想解释的是这句话:“这使得支持这种关系的服务很难在其他地方找到相关的信息,下次工作时我将不得不再次进行分析。与班级”。什么样的分析?由于该对象显然被隐藏,因此检测该对象的方法应该实现这种关系的事实?
Mik378
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.