组件测试将被测试软件的范围限制为被测试系统的一部分,通过内部代码接口操作系统,并使用测试双精度(test double)将被测试代码与其他组件隔离开来。
组件是大型系统中任何封装良好、一致且独立可替换的部分。
孤立地测试这些组件有许多好处。通过将范围限制到单个组件,可以对该组件封装的行为进行彻底的验收测试,同时维护比广义堆栈等效物执行更快的测试。
使用测试双精度包将组件与同级组件隔离,可以避免它们可能存在的任何复杂行为。它还有助于为组件提供受控的测试环境,以可重复的方式触发任何适用的错误情况。
在微服务体系结构中,组件就是服务本身。通过在这个粒度上编写测试,API的契约可以从消费者的角度通过测试来驱动。服务的隔离是通过用测试双精度对象替换外部协作器和使用内部API端点探测或配置服务来实现的。
这种测试的实现包括许多选项。测试应该在与服务相同的流程中执行,还是通过网络在流程外执行?测试双结点应该位于服务内部还是外部,通过网络到达?应该使用真实的数据存储还是用内存中的替代数据存储替换?下面的部分将进一步讨论这一点。
过程中组件测试允许全面测试,同时最小化移动部件
通过使用内存测试副本和数据存储在内存中实例化完整的微服务,可以编写不接触网络的组件测试。
这可以导致更快的测试执行时间,并最小化移动部件的数量,降低构建的复杂性。
然而,这也意味着被测试的工件必须为了测试的目的而改变,以允许它以“测试”模式启动。依赖注入框架可以根据启动时提供的配置以不同方式连接应用程序,从而帮助实现这一点。
测试通过内部接口与微服务通信,允许发送请求和检索响应。通常使用定制的shim来实现这一点,尽管存在许多预构建的库,如基于JVM的微服务的inproctester和基于。net的微服务的plasma。
通过这种方式,进程内组件测试可以尽可能接近于对服务执行真正的HTTP请求,而不会产生实际网络交互的额外开销。
为了将微服务与外部服务隔离开来,可以将网关配置为使用测试重复而不是真正的协议级客户机。通过使用内部资源,可以对这些测试双精度对象进行编程,使其在匹配某些请求时返回预定义的响应。
这些测试双工还可以用来模拟组件中的不愉快路径,例如当外部合作者脱机或响应缓慢或响应格式不正确时。这允许以可控和可重复的方式测试错误条件。
用内存实现替换外部数据存储可以显著提高测试性能。虽然这将真正的数据存储从测试边界中排除,但任何持久性集成测试都将提供足够的覆盖范围。
在某些情况下,使用的持久性机制非常简单,可以使用轻量级的定制实现。另外,一些数据存储(如cassandra和elasticsearch)提供嵌入式实现。还有一些工具可以模拟内存中的外部数据存储,例如H2数据库引擎。
虽然在编写进程内接受测试时可以直接配置测试副本和设置数据,但通过特权内部资源路由所有请求可以使服务更像一个黑盒子一样进行测试。这允许在不影响组件测试套件的情况下进行持久性技术或外部服务通信的更改。
内部资源不仅仅对测试有用
尽管看起来很奇怪,但将内部控制作为资源公开在许多情况下都是有用的,除了监控、维护和调试等测试之外。RESTful API的统一性意味着已经存在许多用于与此类资源交互的工具,这些工具有助于降低总体操作复杂性。
通常公开的内部资源类型包括日志、特性标志、数据库命令和系统度量。许多微服务还包括健康检查资源,这些资源提供关于服务及其依赖项的健康状况、关键事务的时间和配置参数的详细信息。一个简单的ping资源也有助于负载平衡。
由于这些资源在它们所拥有的控制或它们所公开的信息方面具有更大的特权,它们通常需要自己的身份验证或在网络级别锁定。通过对那些使用URL命名约定形成内部控制的API部分进行命名空间,或者通过在不同的网口上公开这些资源,可以在防火墙级别限制访问。
流程外组件测试执行完全部署的工件,将存根复杂性推入测试工具中
对部署为独立流程的微服务执行组件测试允许执行更多的层和集成点。由于所有交互都使用真实的网络调用,部署工件可以保持不变,而不需要任何特定于测试的逻辑。
通过这种方法,复杂性被推到负责启动和停止外部存根以及协调网络端口和配置的测试套件中。
由于网络交互和实际数据存储的使用,测试执行时间可能会增加。但是,如果微服务具有复杂的集成、持久化或启动逻辑,则进程外方法可能更合适。
由于微服务正在监听不同进程中的端口,因此除了验证行为之外,进程外组件测试还验证微服务具有正确的网络配置并能够处理网络请求。
类似地,客户端和持久性模块在与独立进程中的外部依赖项集成时执行。测试工具在启动时配置微服务,以确保它指向测试依赖项的正确url。
外部服务存根有许多不同的种类:一些通过API动态编程,一些使用手工制作的fixture数据,还有一些使用记录回放机制捕获对实际外部服务的请求和响应。
示例工具包括moco、stubby4j和mountebank,它们支持基于动态和夹具的存根,以及vcr,它允许记录回放风格的存根。
如果一个外部服务有许多合作者,它可能需要构建一个特定于该服务的定制存根,以便使用者不必自己管理存根。
测试策略的组合导致了高测试覆盖率
通过结合单元、集成和组件测试,我们能够实现组成微服务的模块的高覆盖率,并能够确保微服务正确地实现所需的业务逻辑。
然而,除了最简单的用例外,除非许多微服务协同工作以完成更大的业务流程,否则就无法实现业务价值。在这个测试方案中,仍然没有测试能够确保外部依赖满足它们所期望的契约,或者我们的微服务集合能够正确协作以提供端到端业务流。
外部依赖的契约测试和整个系统更粗粒度的端到端测试有助于提供这一点。