Contents
  1. 1. 原文:https://martinfowler.com/articles/mocksArentStubs.html
  2. 2. 常规测试
  3. 3. Mock对象的测试
  4. 4. 使用EasyMock
  5. 5. Mocks与Stubs的不同
  6. 6. 差异的选择
  7. 7. Driving TDD
  8. 8. 夹具设置
  9. 9. 测试隔离
  10. 10. 实现与测试耦合
  11. 11. 设计方式
  12. 12. 该选择classicist或mockist ,我发现这是一个难回答的问题。

原文:https://martinfowler.com/articles/mocksArentStubs.html

这篇是Martin Fowler 大神对于单元测试的概念的分析,收获颇多。

该篇文章解释mock对象如何工作,如何鼓励基于行为的测试,社区如何围绕他们发展出不同风格的测试。

第一次听说术语“mock object” 是一些年前在极限编程社区,但人们没有很好的描述它,而且经常与stub混淆,差异主要在两方面,一方面是测试结果的教研不同:状态校验和行为校验。另一方面,与测试和设计共同发挥作用的方式截然不同,这里我描述为classical和mockist TDD风格。

常规测试

订单包含商品和数量,仓库保存不同的商品和数量,然后用仓库填充订单,2种不同的响应,商品足够填充订单,订单填充,仓库商品减少,商品不足,订单失败,仓库不变。XUnit 测试遵循典型4个阶段顺序,setup,exercise,verify,teardown。例子中订单是测试对象,我使用System Under Test,仓库是一个配合对象。这个风格是状态校验(state verification)

Mock对象的测试

setup阶段有些不同,分成2部分,data和expectations
关键不同点是我们通过订单的反馈来校验它是否做了对的事情,状态校验通过仓库的状态判断。Mock使用行为校验。

使用EasyMock

EasyMock使用 记录/回放 (record/replay)隐喻去设置期望。每一个对象创建一个control和mock对象。可以添加额外的特性,可以调用实际的方法,不用传方法名。

Mocks与Stubs的不同

当你专注于一个软件元素时,也就是我们通常的单元测试,做一个单一的单元测试的问题时,我们经常需要其他的单元,因此我们的例子中需要仓库。

在之前我们展示的2种测试风格中,第一个例子使用真实的仓库对象,第二个使用模拟的仓库对象,使用mocks是一种不适用真实仓库的方法,但还有其他形式不使用真实对象的方法。
在讨论这些的时候词汇容易混乱,stub,mock,fake,dummy。这里我们使用Gerard Meszaros的书。Mezaros使用术语Test Double代表任何类型的假装对象,来替换真实对象。
Dummy对象是传递,但不会实际使用。通常他们知识填充参数列表。
Fake对象实际工作与实现,但通常使用一些捷径,不满足生产(比如内存数据库)
Stubs提供一个调用的罐头类回答,通常不回答任何外界的要求。
Spies当他们被调用的记录一些信息,一种形式是email服务记录有多少消息发送
Mocks编程前给予期望,特定调用的响应。

差异的选择

文章中已经讨论了不同:/状态或行为校验 /classic or mockist TDD。选择行为状态 vs 行为呢?
第一个是考虑上下文。如果时间简单的配合,没有什么选择。mockist就是mock对象,行为校验。classicist确实需要做选择,但不是特别大,通常是一个个的解决。
cache是一个比较难classical的方式去解决,mocj是一个好方式。

Driving TDD

Mock对象是从XP社区出来的,而且重点发展TDD是XP的特性原则之一,那里系统设计通过写测试迭代发展。
因此不用惊讶mockists,尤其是mockist testing 在设计上的效果的谈论,尤其他们贡献了一个叫need-driven development 的方式。通过这种方式,你通过写一个测试开始你的用户故事,给你的SUT制定一些接口,通过合作者的一些期望,你开放SUT与关联对象的关系-有效设计SUT的关联接口。

一旦你有了一个测试在运行,在mocks对象上的期望提供下一步的规范和测试的起点。你一个添加测试上的期望在合作者上,然后重复过程在SUT上,这种方式在分层系统中工作得很好。你第一步写UImock,然后写耕更低层次的测试用例,逐渐写每一层,这是一种结构化和可控的。很多人相信对于新的面相对象和TDD新人是有帮助的。

Classic TDD没有提供这种指导,你可以做相似的步骤,使用stub方法替代mocks。当你需要合作者的时候,你写死代码来响应SUT的工作。

但是classic TDD可以做另外的事情,一种通常的风格是middle-out。在这种风格,你做一个特性,决定你需要在domain中。
我应该强调mockists和classicists做了同一件事情,有这么一个学校教大家,一层层去构建系统,不是开始一层指导另外一层完成。classicist和mockist都趋向于敏捷背景和更容易获得的迭代循环,结果他们逐个特性地工作而不是逐层工作。

夹具设置

Classic TDD 你需要创建的不仅仅是SUT,同时也要创建所有的合作者来响应SUT测试,测试通常会调用一大堆合作对象,通常这些对象每次测试被创建和销毁。
Mockistest只要创建SUT和模拟相关的邻居,者可以避免创建复杂的夹具。
实际中,classic测试者趋向于重用复杂的夹具。最简单的方式是将夹具放在xUnit setup方法中,更复杂的夹具,几个测试类都需要用的,需要生成类,我通常称他们是Object Mother
结果我听到双方都控诉对方做了太多的工作,mockists说创建夹具做了太多的努力,但classicist说这个可以重用,但你不得不每次都创建mocks。

测试隔离

如果你通过mockist测试引入一个bug,它通常只引起SUT包含bug的失败。通过classic的方式,很多相关的可能都失败,如果一个重用的对象,那会导致很多的失败。
Mockist测试者因为这是一个主要的问题,它导致很多debugging,为了发现错误的根源与修复它。但classicist看起来不是什么问题,因为一般是最后编辑的代码有问题,你很快会发现。

测试粒度是一个重要的因素。因为classic测试执行多个真实对象,你经常会发现单个测试有一簇对象,很难发现真实bug的源码,测试粒度太粗会发生。
本质上xunit测试不仅仅是单元测试,也包括最小化的集成测试。

实现与测试耦合

当你写mockist测试,你需要调用SUT,确保它提供的正确的,classic测试只关心最终状态,不关心状态如何传递,Mockist测试更多与实现耦合,改变合作方法会导致mockist测试损坏。
这些耦合导致一些关注,最重要的是对TDD的影响。mockist测试,书写测试要确保实现的行为,相反mockist测试者把这个看成优点。Classicists,认为只关心外部的接口,留下所有实现关联直到测试写完。

设计方式

这些测试方式最让我着迷的方面是他们如何影响设计决定。
我已经提到一些不同层面的踪迹。Mockist测试者支持从外到内的实现,classic测试更倾向于domain model的方法。

该选择classicist或mockist ,我发现这是一个难回答的问题。

Contents
  1. 1. 原文:https://martinfowler.com/articles/mocksArentStubs.html
  2. 2. 常规测试
  3. 3. Mock对象的测试
  4. 4. 使用EasyMock
  5. 5. Mocks与Stubs的不同
  6. 6. 差异的选择
  7. 7. Driving TDD
  8. 8. 夹具设置
  9. 9. 测试隔离
  10. 10. 实现与测试耦合
  11. 11. 设计方式
  12. 12. 该选择classicist或mockist ,我发现这是一个难回答的问题。