这篇文章是结合"The Art of Unit Testing"一书及业内一些单元测试大牛们的总结整理而成。主要内容是实际工作中如何运用一些技巧和方法编写出可靠、可读、可维护的优秀单元测试用例。
优秀单元测试的定义
单元测试:一段** 自动化** 的代码 ,这段代码调用被测试的** 工作单元 ,之后对这个工作单元的 单个最终结果 的某些假设进行检验。单元测试几乎都是用 单元测试框架进行编写。单元测试容易编写,快速运行,可自动化,可靠,可读,可维护,结果稳定**。
集成测试:对一个工作单元进行的测试,这个测试对被测试的工作单元没有完全的控制,并使用该单元的一个或多个真实依赖物,例如数据库、系统时间、系统文件等
工作单元:从调用系统一个公共方法到产生一个测试可见的最终结果,其间这个系统发生的行为。一个工作单元既可以是小到只包含一个方法,也可以大到包含实现某功能的多个类或方法。
最终结果:是指被调用的公共方法返回一个值;或者在方法调用前后,系统的状态或行为有可见的变化,这种变化不需要查询私有状态即可判断;又或者是** 调用了一个不受测试控制的第三方系统 **,这个第三方系统不返回任何值,或者返回值已被忽略。
编写可靠的测试
所谓可靠性是指单元测试本身是正确的,即该失败的时候失败,该成功的时候成功。只有保证单元测试的可靠性,才能让开发人员信任该单元测试,不会为了以防万一进行调试等其他工作。
在"单元测试的艺术"中,作者给出了一些简单的原则和技术帮助编写可靠的测试。
- 依据实际情况合理地删除或修改单元测试
如果确定是测试缺陷,而不是产品缺陷(被测试代码缺陷)时,需要立刻修改相关单元测试代码;如果被测试的产品代码的语义或者API变更导致测试失败,这时是需要修改测试,使用新的语义;如果看到测试名含义不清或者单元测试的可维护性差就应该在保证单元测试基本功能前提下修改测试名称或者重构测试;如果同一个功能多个单元测试,请删除重复测试。 - 避免在单元测试代码中包含逻辑
包含逻辑的测试是指测试代码中包含switch、if/else、for/while等控制流语句。这样的测试可读性差,代码脆弱,测试代码的复杂度高,容易包含缺陷,测试结果不容易重现。 - 每个单元测试只测试一个关注点
所谓的一个关注点就是指一个工作单元的一个最终结果:一个返回值、系统状态的一个改变、对第三方对象的一个调用。测试多个关注点一方面不利于测试命名,另一方面很多单元测试框架中,一个失败断言就会抛出一个特殊类型的异常,后面代码不会继续执行,这样不利于收集测试失败原因。 - 区分单元测试和集成测试
- 用代码审查确保代码覆盖率
如果你做了代码审查、测试审查、确保测试优秀而且覆盖了所有代码,那么就可以避免犯简单愚蠢的错误,同时也可以从持续的学习中获益。
编写可读的测试
单元测试可以看做是一个婉婉道来的故事,这个故事是讲给下一代开发者听,故事的内容就是这个应用程序的组成及其流程。既然是故事,就一定要形象生动,易于理解。那么可读性就是指如何确保其他开发者能够理解他们要做的工作,以便维护产品代码和测试。
单元测试的可读性其实和代码可读性在很多方面类似,下面就逐一讨论这些方面。
- 单元测试的命名标准
合理地命名测试,主要目的是为了使后来的开发者从为了理解测试而阅读代码的负担中解脱出来。测试名应该包含三部分:被测试方法名、测试场景(即测试使用的条件)、预期行为(即被测试方法的最终结果)。 - 单元测试中的变量命名规范
单元测试除了主要的测试功能之外,它还为API提供某种形式的文档。通过合理命名变量,帮助阅读测试的人可以尽快理解你要验证什么(从而更加理解产品代码中想要实现什么功能)。 - 断言和操作分离
- 避免滥用setup和teardown
比如在setup中准备stub和mock对象,这种情况就会导致阅读测试的人意识不到测试中使用了模拟对象,也不知道这些模拟对象预期是什么。
编写可维护的测试
可维护性是大多数单元测试面临的最大挑战。随着时间累计,单元测试似乎越来越难维护和理解,被测代码一个微小的改动,似乎都会使某个测试失败。那么怎么能够尽量降低可维护性的成本呢?
- 只测试公共契约,避免测试私有或者受保护的方法
私有方法可以看做是系统内部契约,这个内部契约是动态,在系统重构时可能会被随时修改,因此针对这些内部契约的单元测试也很可能会失败。而内部契约最终都会被一个公共契约(公共方法、整体功能)所调用,也就是说任何私有方法通常都是一个更大的工作单元的一部分。 - 去除重复代码
可以使用辅助方法或者setup来去除重复代码的问题 - 实施测试隔离
测试隔离是指每个测试都只生活在自己的小世界中,它与其他测试之间没有任何依赖关系,甚至不知道其他测试存在。
下面列举几种常见的测试隔离的反模式。
1、测试结果依赖测试执行的顺序
2、测试调用其他测试方法
3、测试中使用的共享资源(内存或外部资源)没有得到清理或回滚 - 避免对不同关注点多次断言,尽量使用参数化测试或者对每个关注点设计单独的测试用例
- 避免过度指定
常见的过度指定的例子。
1.对系统内部契约进行断言
2.使用过多的模拟对象
3.精确匹配
网友评论