经典学派如何解决隔离问题
首先,再回顾一下单元测试的三个重要特性:
验证一小段代码(或者叫一个单元)
执行速度快
使用隔离的方式进行
针对第一个特性就会引出一个问题:多小的一段代码才足够小?
如果你采用针对每个类进行隔离的话,那么这一小段代码肯定就是这个类了,或者类里面的一个方法。没法比这个范围更大了。
有些情况下,你可能需要同时测试好几个类。但大部分时候,你都应该尽量保证每次只测试一个类。
但是在经典学派的做法里,并不是被测试的代码需要隔离,而是单元测试之间需要互相隔离。这样的话,你可以并行、按顺序或按任意顺序进行单元测试。而且它们的结果不会受到影响。
让测试之间互相隔离意味着可以同时运行多个类,前提是它们都在内存里并且不会接触到共享的状态。
共享状态就是指测试之间可以通信,并且会影响相互的执行上下文。
典型的共享状态例子就是进程外依赖:数据库、文件系统等等。
共享、私有、进程外依赖
共享依赖是指:这种依赖在测试间被共享,并且能够提供影响相互结果的手段。
典型的例子就是静态可变字段
另一个例子就是数据库
私有依赖就是指不共享的依赖。
进程外依赖,这种依赖运行于程序执行进程以外。它是到暂时还未进入内存的数据的代理。
在大多数情况下,进程外依赖就是共享依赖,但也不全是。例如:
数据库既是共享依赖,又是进程外依赖。
但如果每次运行测试都启动一个内涵数据库的 docker 容器,那这是进程外依赖,而不是共享依赖。因为测试运行的不是同一个进程实例。
同理,只读数据库也是进程外依赖,但不是共享依赖。因为其数据不可变,所有测试间的结果不会互相影响。
针对隔离问题,经典学派对于 mock 和测试替身的使用,拥有更加温和的观点:你仍然可以使用它们,但通常只对那些在测试之间引入共享状态的依赖项这样做:
强调一下:共享依赖是指在单元测试间共享的依赖,而不是被测试类(单元)的依赖。
共享依赖与不稳定依赖
另一个意思相近,但不完全一样的术语就是不稳定依赖(volatile dependencies)。
不稳定依赖具有以下属性:
除了默认安装在开发人员的机器上之外,它还引入了设置和配置运行时环境的要求。数据库和API服务就是很好的例子。它们需要额外的设置,默认情况下不会安装在组织中的计算机上。
它包含不确定性行为。例如,随机数生成器或返回当前日期和时间的类。这些依赖关系是不确定的,因为它们在每次调用上提供不同的结果。
不稳定依赖与共享依赖在概念上多少还是有一些重叠的。例如:
数据库既是共享依赖,又是不稳定依赖。
而文件系统则不是,因为每个开发者电脑上都有文件系统。
随机数生成器是不稳定依赖,但它确不是共享的,因为每次测试都会运行它的一个实例。
替换掉共享依赖的另一个原因就是提升测试的速度。
因为共享依赖大多数都在程序执行进程之外,所以例如访问数据库就要比访问私有依赖慢得多。
经典学派对隔离的这种观点也导致了对单元(一小段代码)的不同理解。一个单元不一定要局限于一个类。你也可以对一组类进行单元测试,只要它们都不是共享依赖项。