75年生人,程序员,在西安。

测试的模拟方式(Test Doubles)

参见Martin Fowler的文章《Mocks aren’t Stubs

测试的模拟方式(Test Doubles)分类:

  • Dummy 仅仅是“傀儡”,实际啥也不做,类似占位符一般,通常仅当参数传入。

  • Fake “假货”,有可执行的实现,但通常是投机取巧的,所以不适合真实生产环境使用,比如内存数据库就是个很合适的例子。

  • Stubs “存根”、“树桩”,直接给结果的模拟方式,对于当前测试以外的情况都无法正确回应,有时也记录一下请求者的信息,比如一个email网关的Stub就会记录下发送过的邮件或邮件的数量供测试使用。

  • Mocks “模仿”,提前定义或模仿出一系列期待产生的行为结果,用record/replay来模仿,用verify来验证。

以上的这些模拟方式,只有Mocks是针对行为验证(behavior verification)的,而其他几种方式通常是属于状态验证(state verification)的。

BDD(行为驱动开发,算是TDD测试驱动开发的一个变种)更倾向于使用Mocks方式的测试。

Cache 缓存的模拟方式似乎只能用Mocks的方式,你不能用一种状态验证(state verification)的方式来确认缓存是否被使用(hit or missed)。

在选择决策方面考虑以下几点:

  • Fixture Setup 测试夹具设置方面;

    • 状态验证(state verification),可重用Fixture的测试数据,当然同时也会带来数据或情况变化时会影响多处的问题。

    • 行为验证(behavior verification),仅测试主要的类或方法,其他全都用Mock,需要每次都写Mock,但各个测试的隔离性更好。

  • Test Isolation 测试隔离方面;

    • 状态验证(state verification),通常bug会造成多处测试不通过,较粗粒度,好处是可以实现小范围集成测试,通常相关的类不要超过6个,否则较难定位问题。

    • 行为验证(behavior verification),通常bug只会造成一处不通过,容易定位,较细粒度,弊端是可能忽略类之间相互操作的测试,同时如果对于行为的断言有误的话也会造成测试的疏漏。

  • Coupling Tests to Implementations 测试与实现的耦合方面;

    • 状态验证(state verification),只关心结果状态的测试,耦合度低,重构实现时影响相对较小。

    • 行为验证(behavior verification),根据实现的不同,相对耦合度较高,重构时可能更麻烦。

  • Design Style 设计风格决策方面;

    • 状态验证(state verification),更面向领域建模设计的测试,容易引导设计出查询风格的方法。

    • 行为验证(behavior verification),更面向接口设计的测试,更符合“Tell Don’t Ask”原则,避免查询风格方法,而是引导设计出干净的API方法,通常实际情况中,这样后期的变化和修改较少。更喜欢“基于角色的接口(RoleInterface)”,也更符合“接口隔离原则(Interface Segregation Principle)”,而不是“基于页头的接口(HeaderInterface)”。

Martin Fowler 的总结:

  • 状态验证(state verification)

    • 更符合人的自然思路,只关心结果,不关心过程。

    • 测试与代码实现松耦合。

  • 行为验证(behavior verification)

    • 有点反人类,需要时常想测试的过程是怎么执行的。

    • 测试与代码实现息息相关,有紧耦合的问题。

    • 两种情况值得一试:

      • 测试出错定位不清晰时;

      • 对象方法不丰富时;

  • 关键要关注这两种测试实现思路对设计决策的影响,然后权衡使用。


评论

© 世风十三 | Powered by LOFTER