写点什么

原创 | 使用 JUnit、AssertJ 和 Mockito 编写单元测试和实践 TDD (八)好单元测试的特质

发布于: 2020 年 05 月 16 日
原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (八)好单元测试的特质





上一章讲了“CORRECT边界条件”,这一章我们讲讲“好单元测试的特质”。



好的单元测试应该具备下面的品质:

Automatic(自动化的)

Thorough(彻底的)

Repetable(可重复的)

Independent(独立的)

Professional(专业的)



下面分别论述。

1. Automatic 自动化

单元测试应该能够自动地运行,从准备数据到执行测试到检查结果这一整个过程都不需要人工干预。

首先,调用测试的过程必须是自动化的,不需要任何人工干预步骤,例如人工输入参数值或回答YES/NO。对于测试所需要的任何预设条件(例如创建一个临时文本文件并输入指定内容)等等,都应该成为单元测试自身的一个自动化组成部分。配合Maven这样的自动化构建工具,能够做到一键执行整个项目的全部单元测试并生成测试报告。



@Test
void testConfig() throws Exception {
File file = File.createTempFile("app", ".conf");
file.deleteOnExit();
BufferedWriter out = new BufferedWriter(new FileWriter(file));
out.write("charset=UTF-8");
Configuration instance = Configuration.fromFileSystem(file);
assertThat(instance.getString("charset")).isEqualTo("UTF-8");
}



上面的例子,为了测试用于读取配置文件的内容的Configuration类的getString()方法,必须首先存在一个包含确定内容的配置文件。我们不是在电脑上预先手工创建一个配置文件并填充内容,而是将创建文件和填充内容作为单元测试本身的一部分。这样的测试执行时就不需要人工干预了。



另一方面,检查测试结果也应该是自动化的,测试必须能够自己决定它是通过了还是失败了,而不需要人工确认。例如你要测试一个加密函数是否正确,你不应该让测试打印出加密结果,然后人工核对输出字符串和我们期望的加密后的字符串是否一样,而是由测试本身来断言二者是否相等。



@Test
void testEncrypt() {
String origStr = "xxxx";
String expectedStr = "yyyy";
Encryptor instance = new Encryptor();
assertThat(instance.encrypt(origStr)).isEqualTo(expectedStr);
}

2. Thorough 彻底

好的单元测试应该覆盖了被测工作单元的每一个分支执行路径,每一个可能抛出的异常,甚至每一行代码(除了哪些getXXX()和setXXX()等极端简单,不包含逻辑的方法)。如果代码中有if...else...,你不能只是测试if这个分支,而不测试else这个分支。如果使用了switch语句,同样要测试到每一个case,以及最后的default。



3. Repetable 可重复

测试必须是可重复的,意味着:

  • 无论执行多少遍,结果都是一样的

这意味着测试不会改变会影响它下一次运行的环境。例如一个测试往一个中央数据库插入一行数据,然后断言目前有多少行数据。这样的测试就是不可重复的,因为每次执行测试,记录行数都会加一。另外别人也可能向这个数据插入数据。解决办法是使用本地数据库(不共享),并且在每次测试之前清空数据。



  • 在任何人的电脑上执行,结果都是一样的

这意味着测试不依赖于现有的计算机环境(操作系统,某个文件存在与否,等等)。不能只是“在我的笔记本上能够运通过测试”



  • 在任何时间执行,结果都是一样的

这意味着测试结果不受时间因素影响。如果被测代码的行为依赖于当前日期/时间(例如周末不能取款),那么在工作日和在周末执行取款测试,结果就可能不一样。这种情况下通常要重构产品代码,不要在方法内部通过

Date now = new Date();



的方式获取当前时间,而是将时间作为一个参数传递给方法:



public void withdraw(int amount, Date withdrawTime)

由客户代码负责获取当前时间并传递给Account.withdraw()方法。这样对withdraw()方法的单元测试又是可重复的了。

总之,单元测试的结果不受环境影响,也不受上次测试影响。无论如何,单元测试决不能依赖于共享资源(例如共享数据库或消息中间件),因为别人会操作这个资源,从而导致你的测试结果不确定。



4. Independent 独立

独立有两个含义:

  • 一个测试只测试一件事情。

每个测试都有且只有一个关注点。



  • 测试不依赖于其他测试。也就是说,多个测试之间没有顺序依赖。

一个测试不应该依赖于此前运行的另一个测试来为它设置测试条件(例如,第一个测试创建了文本文件并写入内容,第二个测试读取文件内容)。每个测试所需要的预设条件都应该在测试本身里面设置(第二个测试先创建文件,写入内容,再执行测试)。



5. Professional 专业

要按照编写产品代码的质量标准去编写测试代码。不要认为测试代码是二等公民,不值得为了提高它的质量而花费心力。在测试代码中,针对好设计的所有普遍规则——维护封装、采用DIY原则、降低耦合,等等等等,同样适用。

第一部分“单元测试”就讲到这里,下一章将开始展开讲讲第二部分“测试驱动开发TDD”,敬请关注!





发布于: 2020 年 05 月 16 日阅读数: 98
用户头像

高级架构师,技术顾问,交流公号:编程道与术 2020.04.28 加入

杨宇于2020年创立编程道与术,致力于研究领域分析与建模、测试驱动开发、架构设计、自动化构建和持续集成、敏捷开发方法论、微服务、云计算等顶尖技术领域。 了解更多公众号:编程道与术

评论

发布
暂无评论
原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (八)好单元测试的特质