通常如何滥用模拟对象?


15

我最近读过一篇文章,其中说到模拟对象经常被误解和滥用。我可以寻找任何清晰的模拟反模式吗?



不...不记得确切的消息来源,但是如果我愿意,将会在这里发布
Armand

我因模拟mongodb API而承担了stackoverflow的任务。我被指到一篇博客文章中,该文章声称嘲笑您自己未编写的任何类都是错误的。我实际上不同意这一点,但意见是存在的。
凯文

Answers:


13

我讨厌看到简单的具体类被嘲笑。例如,使用下面的简单类,该类不依赖于其他任何东西:

public class Person
{
    private readonly string _firstName;
    private readonly string _surname;

    public Person(string firstName, string surname)
    {
        if (String.IsNullOrEmpty(firstName))
        {
            throw new ArgumentException("Must have first name");
        }

        if (String.IsNullOrEmpty(surname))
        {
            throw new ArgumentException("Must have a surname");
        }

        _firstName = firstName;
        _surname = surname;
    }

    public string Name 
    {
        get
        {
            return _firstName + " " + _surname;
        }
    }
}

在涉及该类的任何测试中,我宁愿实例化并使用一个真实的类,而不是拔出诸如“ IPerson”之类的接口,一个已使用的模拟类并在其上设置期望。通过使用真实的测试,您的测试将更加真实(您已经进行了参数检查,并真正实现了“ Name”属性)。对于像这样的简单类,您不会使测试变慢,确定性降低或使逻辑混乱(您无需在测试其他类时就知道Name被调用)-这是嘲笑/存根。

作为对此的扩展,我还看到人们编写了带有期望值的模拟程序的测试,然后在测试中直接调用该模拟程序。毫无疑问,测试将始终通过...嗯...


值得庆幸的是,我使用的模拟框架已经能够模拟具体的类,因此在不方便的地方退出接口不是问题。
Armand

5
但这并不会改变问题-即使您使用的东西可以放宽对可模拟对象的技术限制(例如,像TypeMock这样的模拟框架或动态语言),也不应嘲笑这种简单的事情。
FinnNk 2011年

我的经验法则一直是嘲笑行为,而不是数据。
ardave

10

听起来似乎很明显,但是:不要在生产代码中使用模拟对象!我已经看到了不止一个示例,其中生产代码取决于某些模拟对象的特性(MockHttpServletRequest例如,来自Springframework)。


14
我希望您能履行自己的职责,并将代码提交给DailyWTF吗?
keppla 2011年

1
在我以前的工作中,我们明确禁止将任何代码从我们的代码库提交到DWTF。
quant_dev

9
@quant_dev:他们制定了这样的政策这一事实意味着他们的开发人员感到非常恐怖……
John Fisher

1
并不是的。这是一家初创公司,必须迅速开发代码库以销售产品,然后开始合并和重构它以偿还技术债务,因为产品已经成熟,并且(缺乏)初始设计阻碍了进一步的开发。经理们知道旧的代码库很烂,并花了时间和资源进行重构,但不想冒任何不利宣传的风险。
quant_dev

仅仅采取捷径还不足以让您日复一日地生活……
poolie 2012年

9

我认为这是对模拟的过多方法调用检查。我觉得这是由EasyMock之类的一些模拟框架强制实施的一种做法,其中默认的模拟行为是每当存在以前未完全指定的其他方法调用时都会失败。这种严格的模拟方法检查可能导致脆弱的设计,即使核心功能仍然相同,对代码进行的最小更改也可能导致整套测试失败。

解决此问题的方法是开始使用存根而不是模拟。我发现特别启发该主题的一篇文章是在Mockito的Javadoc中找到的:http ://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html (请参阅“ 2.存根怎么样?”。 ),链接到:http : //monkeyisland.pl/2008/07/12/should-i-worry-about-the-unexpected/

到目前为止,我很喜欢与Mockito一起工作,因为它不执行这种严格的模拟行为,而是使用存根。它还对特定对象而不是整个模拟对象强制执行方法检查。因此,您最终只能检查在您的测试方案中真正重要的方法。

这里有几本书,我建议您去摸一下这个主题以及嘲讽和一般:

xUnit模式

单元测试的技巧:.Net中的示例

下一代Java测试:TestNG和高级概念(本书主要是关于testNG的,但是有一章很好地介绍了模拟)


+1表示过多的方法调用检查。但是,总会有问题的另一面是,意外的方法调用会导致您的方法失败。值得庆幸的是,Mockito具有模拟的Answer.RETURNS_SMART_NULLS设置,可帮助诊断此问题。
Bringer128

4

我的经验中很少观察到反模式。

  • 在可能发生状态更改并且需要进行验证的地方对类进行模拟/存根。
  • 集成测试与模拟和具体类的混合进行交互,这破坏了集成测试的目的。
  • 在生产代码中无意中使用了模拟(永远不会发生)

否则,我对模拟尤其是Mockito的体验将变得轻而易举。他们使测试非常容易编写和维护。使用GWT进行视图/演示者交互测试比使用GWTTestCase更加容易。


2和3是确定的问题!您有(1)的简单例子吗?
Armand

2

我发现使用跨应用程序多层模拟的测试特别难以解密和更改。但是,我认为近年来已通过改进的模拟框架API(在方便的地方使用JMock)缓解了这种情况。

5或6年前,像EasyMock这样的API很强大,但非常麻烦。通常,使用它的测试代码要比要测试的代码复杂几个数量级。那时,我试图影响那些我很少使用的团队,并使用简单的手工模拟进行弥补,这些模拟只是专门用于测试的接口的替代实现。

最近,随着模拟API使使用它们的测试更加可读,我对此的坚决看法变得更加柔和。本质上,我希望其他开发人员可以更改我的代码(包括测试),而又不让他们觉得自己正在泥泞的API调用中筛选。

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.