我们知道编写测试能够带来很多好处。那么除了只是停留在编写测试上,我们应该如何编写好的测试呢?
这里翻译了一篇2012年Eric Lewis的一篇文章《How to write good tests》。
提交历史显示,这篇内容还在一直维护。
正文开始
01 保持测试代码的短小可读
要实现这一点就必须产品代码进行重构。否则测试端就会因遗留代码而变得腐败。如果测试代码不能很容易的进行重构,那么产品代码同样不容易重构,从而导致了遗留代码。永远都要勇敢的进行重构。
02 避免编写重复的代码
下面这个场景:
一个将参数放置到模版中的方法,代码如下:
public void format(String dateString) {
return String.formate("Update date is %s", dateString);
}
我们对这个测试的代码不应该是这样
@Test
void should_format_date_label_when_format_given_a_date_string() {
String dateString = "2019/12/20";
String formatedDate = DateUtils.format(dateString);
assertThat(formatedDate).isEquals(String.formate("Update date is %s", dateString))
}
一般而言,不应该在测试和代码之间重复逻辑。因此,上面测试断言中,将业务代码中的实现复制到测试代码中并不可取。在这种情况下,我们应该关注输入/输出,应该使用一个固定期望的结果。
@Test
void should_format_date_label_when_format_given_a_date_string() {
String dateString = "2019/12/20";
String formatedDate = DateUtils.format(dateString);
assertThat(formatedDate).isEquals("Update date is 2019/12/20");
}
ps:当然既然String.formate()方法能够提供实现,没有必要在封装一个方法,所以上面的例子不是一个好例子,但是意图在说明应该如何验证结果。
03 除了覆盖正向测试,测试用例应该尽可能多的错误代码的路径
通常,这也是练习TDD最好的实现方式。使用TDD可以在设计的时候辨别出造成破坏的地方。不要认为为一个小的代码片段编写简单的测试不值得,你永远不知道什么时候、因为什么而修改这段代码。
可与使用PIT(突变检测系统 http://pitest.org/)来对测试代码的有效性进行检测。
04 不要Mock一个你不拥有的类型
TDD既涉及到测试,也涉及到设计。这不是一个硬性要求,但是如果真的这样做了,很可能会带来一些影响。
场景:假设你在使用一个第三方提供的工具API时。如果对API进行了mock,那么会有什么杨的结果?
- 你的测试当时是通过的。
- 如果第三方API进行了升级,会有什么结果?会造成单元测试通过,但是实际调用的时候却失败或者并没有返回自己想要的结果。
- 这样的设计也表明和第三方API的紧密度不够。
- 如果第三方API非常复杂,也会导致当前测试代码必须在大量mock之后才能正常工作。这本身也损害了短小易读的目的。
取而代之,我们应该也是到使用底层这些API的风险,我们应该为第三方库和API编写封装器。为了验证第三方提供API的可用性,我们需要编写集成测试,并让这些测试保持短小、易读。
还有一些人记录了当mock一个不是自己代码库的类型的时候,遇到的痛:
- http://davesquared.net/2011/04/dont-mock-types-you-dont-own.html
- http://www.markhneedham.com/blog/2009/12/13/tdd-only-mock-types-you-own
- http://blog.8thlight.com/eric-smith/2011/10/27/thats-not-yours.html
- http://stackoverflow.com/questions/1906344/should-you-only-mock-types-you-own
05 反模式:Mock一切对象
如果所有的代码都mock了,那么我们怎么测试业务代码?不要害怕不使用Mock的方法。
06 不要Mock值对象
为什么有的人会Mock只值对象呢?
因为实例化对方是一件很痛苦的事情。
如果创建一个对象很复杂,那么很可能意味着我们需要进行认真的重构。不Mock的话应该怎么处理?可以使用一些IDE插件、Lombok等,让构造值对象变的简单,也可以在测试类中创建有意义的工厂方法。
abstract class CustomerCreations {
public static Customer customer_with_a_single_item_in_the_basket() {
// long init sequence
}
}
Mockito更加关注对象之间的交互。
07 读读书籍,做做刻意练习
原文中推荐阅读这本书:"Growing Object Oriented Software Guided by Tests"。
但是这本书现在市场已经无法找到了,留在我么身边的还有Code Dojo或者一些其他TDD实践练习,通过反复练习一些场景,思考分析,能够帮助我们写出好的测试。
原文
How to write good tests--Eric Lewis edited this page on Mar 22 · 11 revisions