这本书教你为什么要关注可测试性, 如何编写可测试的代码, 以及如何推动测试落地.
书中的代码是.NET的, 不太习惯, 后文中我改成用Golang进行举例.
第一部分 入门
第一部分是入门章节, 告诉我们什么是单元测试, 为什么要用单元测试, 如何写好单元测试.
什么是单元测试? 单元测试是一段自动化的代码, 这段代码调用被测试的工作单元, 之后对这个单元的单个最终结果的某些假设进行检验. 这里需要注意一个概念: 单元. 从调用系统的一个公共方法到产生一个测试可见的最终结果, 其间这个系统发生的行为总称为一个工作单元.
一个相关的概念是集成测试. 单元测试和集成测试的最主要区别在于: 是否使用了真实依赖物, 如数据库, 网络连接等等.
什么是好的单元测试? 优秀单元测试应该有如下特性:
- 自动化, 可重复执行
- 运行结果是稳定的
- 容易编写
- 运行快速
- 能够完全控制被测试的单元
在开发过程中应该何时编写单元测试? 这个见仁见智, 可以采用TDD先写测试后写功能, 也可以写完功能再补测试.
单元测试的目的在于使得代码可维护. 如果你的单元测试没有促进这一目标, 就要反思是不是真的写好了单元测试.
第二部分 核心技术
核心技术章节主要介绍了如何使系统与外部依赖项隔离, 从而进行去依赖的单元测试.
fake & stub
通常我们开发的系统都会有各种外部依赖项. 外部依赖项是系统中的一个对象, 被测试代码与这个对象发生交互, 但你不能控制这个对象. 常见外部依赖项包括文件系统, 线程, 内存以及时间等. 注意: 一旦你的系统中引入的真实的外部依赖项, 那么你进行的就是集成测试, 而非单元测试.
显然, 如果没法控制外部依赖项的行为, 就无法保证单元测试运行结果的稳定性. 那么如何使外部依赖项可控? 答案是使用伪对象 (fake) 替代真实的外部依赖对象.
那么接下来的问题是, 如何用伪对象替代外部依赖? 只需要找到被测试单元使用的外部接口, 然后将接口的底层实现替换成你能控制的代码. 如果这个接口与被测试单元直接相连, 就添加一个间接层, 隐藏这个接口. 来看下面这个例子:
// 判断文件是否存在
func IsFileExist(fileName string) bool {
_, err := os.Stat(fileName)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
这段代码引入了文件系统依赖, 需要用一个伪对象替代真实文件系统. 然而, 文件系统有关的代码已经写死在函数里了, 这就需要引入一个中间层, 抽象出文件系统的操作. 这里我们声明一个IFileManager接口: