Go语言精进之路:从新手到高手的编程思想、方法和技巧(2)
上QQ阅读APP看书,第一时间看更新

第44条
正确运用fake、stub和mock等辅助单元测试

你不需要一个真实的数据库来满足运行单元测试的需求。

——佚名

在对Go代码进行测试的过程中,除了会遇到上一条中所提到的测试代码对外部文件数据的依赖之外,还会经常面对被测代码对外部业务组件或服务的依赖。此外,越是接近业务层,被测代码对外部组件或服务依赖的可能性越大。比如:

  • 被测代码需要连接外部Redis服务;
  • 被测代码依赖一个外部邮件服务器来发送电子邮件;
  • 被测代码需与外部数据库建立连接并进行数据操作;
  • 被测代码使用了某个外部RESTful服务。

在生产环境中为运行的业务代码提供其依赖的真实组件或服务是必不可少的,也是相对容易的。但是在开发测试环境中,我们无法像在生产环境中那样,为测试(尤其是单元测试)提供真实运行的外部依赖。这是因为测试(尤其是单元测试)运行在各类开发环境、持续集成或持续交付环境中,我们很难要求所有环境为运行测试而搭建统一版本、统一访问方式、统一行为控制以及保持返回数据一致的真实外部依赖组件或服务。反过来说,为被测对象建立依赖真实外部组件或服务的测试代码是十分不明智的,因为这种测试(尤指单元测试)运行失败的概率要远大于其运行成功的概率,失去了存在的意义。

为了能让对此类被测代码的测试进行下去,我们需要为这些被测代码提供其依赖的外部组件或服务的替身,如图44-1所示。

015-01

图44-1 生产环境的真实组件或服务与测试环境的组件或服务的替身

显然用于代码测试的“替身”不必与真实组件或服务完全相同,替身只需要提供与真实组件或服务相同的接口,只要被测代码认为它是真实的即可。

替身的概念是在测试驱动编程[1]理论中被提出的。作为测试驱动编程理论的最佳实践,xUnit家族框架将替身的概念在单元测试中应用得淋漓尽致,并总结出多种替身,比如fake、stub、mock等。这些概念及其应用模式被汇集在xUnit Test Patterns[2]一书中,该书已成为测试驱动开发和xUnit框架拥趸人手一册的“圣经”。

在本条中,我们就来一起看一下如何将xUnit最佳实践中的fake、stub和mock等概念应用到Go语言单元测试中以简化测试(区别于直接为被测代码建立其依赖的真实外部组件或服务),以及这些概念是如何促进被测代码重构以提升可测试性的。

不过fake、stub、mock等替身概念之间并非泾渭分明的,理解这些概念并清晰区分它们本身就是一道门槛。本条尽量不涉及这些概念间的交集以避免讲解过于琐碎。想要深入了解这些概念间差别的读者可以自行精读xUnit Test Patterns


[1]https://www.agilealliance.org/glossary/tdd

[2]https://book.douban.com/subject/1859393