这张图可以形象地展示单体服务和微服务的对比,单体应用就像左边巨大的集装箱,软件模块和应用都包括其中;而微服务就像是由一个小集装箱组成,微小的服务组成一个庞大、完整的系统。单体服务是一个大而全的应用体,而微服务由拆分成出来的很多小服务来组成一个庞大而完整的系统。
微服务是一种架构模式,是面向服务型架构 SOA 的一种变体,提倡将单一应用程序逐渐还原划分成小的服务,服务间互相协调、互相配合,为用户提供最终价值。微服务架构风格就是一些小而自治的服务协同工作形成松耦合的系统。另外,我们需要尽量避免一个统一的、集中式的服务管理机制,对具体的一个服务而言,应该根据上下文选择合适的语言工具对其进行构建。
结合下方的这张图,我们可以理解微服务构建的核心其实是中间领域的业务逻辑,围绕着这个领域业务逻辑,会有一些微服务去进行拆分构建。
微服务具有专注、自治、独立进程、独立部署和技术异构的特点,即每个服务只限定于特定的业务而专注做一件事情,每个服务承担的是单一职责,但是它也需要达到一定规模能够完整的处理特定的领域业务。很多人都会被微服务的“微”这个词所误导,认为微服务就是要拆分的越小越好。但是其实为了“微”而将同一领域的业务拆分到不同的服务,只会徒劳增加软件的复杂度和维护困难。
我们可以围绕应用的业务能力进行分组,每个小组的开发组人员开发微服务的技术可以不受限制。每个服务小组可以使用不同的技术架构和存储技术,有针对性地解决一些性能瓶颈问题。每个服务是互相独立部署互不影响的,这样的话我们可以实现独立打包、独立测试和独立附属,减少部署时间,提升研发效率。
微服务架构测试具有三个痛点:一、如何测试微服务的外部依赖是否正常;二、如何在微服务架构下验证系统的整个功能是否符合预期;三、这么多微服务的部署和测试,应如何开展。按照以上痛点我们可以看到,微服务测试是一种验证成本高、结果不稳定、反馈周期长的测试。
测试金字塔其实是一种方法论,解决微服务测试的关键在于将微服务的测试按照不同的力度来分组。测试金字塔的概念由麦克科恩首先提出。测试是分层次的,我们看到图片左边,这个金字塔被分为三个层次,从下往上分别是单元测试、服务测试、界面测试,从下往上测试的运行速度是逐渐减慢的,外物依赖或者服务间的依赖从下到上会依赖更多。这个测试金字塔的另外一个重要特征是,从下往上对每一层的测试代码是逐层减少的。下方应该写一些小而快的测试,往上应该编写一些粗粒度的测试,编写更少的高层次测试。
然而实际中如果以这个金字塔图来作为指导,会过于笼统简单,所以我们会采用右边的分为四层的测试金字塔来做内部测试的指导思想。底层是单元测试,在这之上是集成测试,再往上是端到端的测试,顶层是探索测试。
作为开发人员或测试人员,应该关注金字塔的哪些部分呢?微服务开发人员应更多关注位于塔基底部的单元测试与集成测试。在这两层需要开发人员编写一定量的测试代码来保证覆盖,应该写许多小而快的单元测试覆盖绝大部分的业务场景,再写一定的粗粒度的集成测试,来测试重要系统之间外部依赖的交互是否正常。测试人员和质量保证人员应更多关注金字塔上面两层,测试人员可以依据 BDD 的规范来编写测试用例,用于校验系统功能的交互是否正常,还可以用非常规的手段进行破坏性的探索测试。
单元测试是测试金字塔的底基,它的定义没有标准答案。从编程角度来看,在函数式语言中我们可以认为一个函数是一个单元,在面向对象的语言中一个方法或者一个类可以表示一个单元。单元测试具有能够及时发现 bug 、利于重构、保证代码质量的优势,我们系统中需要编写得最多的其实就是单元测试。
微服务的测试一般是对入栈适配器、业务逻辑和出栈适配器这三部分进行测试。入栈适配器测试的是 Controller API 是否正确;业务逻辑部分测试 Service 业务逻辑是否正确,而出栈适配器部分测试的是 SQL 逻辑是否正确。单元测试一般会遵循一个通用的 3A 结构:Arrange,Act,Assert,这样写出来的代码更有阅读性和表达力。
在微服务架构下我们所理解的集成测试是测试应用与外部依赖的集成。第三方外部服务依赖主要有两种类:第一种是微服务会依赖第三方系统的服务;第二种是系统内部的微服务与微服务之间,一种服务可能会依赖另一种微服务来实现自身逻辑。对应这两种情况会有不同的策略,第一种策略是准备真实的外部服务的依赖,第二种是使用测试替身隔绝外部依赖。进行集成测试的时候我们通常会使用一些,依赖第三方服务的话会采用 WireMock 或者 mountebank,而微服务之间的依赖调用会使用 Spring-Cloud-Contract 或者 Pact 。
微服务之间的测试会使用契约测试,服务之间的接口文档就是一个契约。契约测试可以解决联调成本过高,接口变动把控困难,契约变化时提供一种可立即被服务端和消费端发现的方式,这三种痛点。契约测试的提供者指微服务接口的提供者,消费者指微服务接口的消费者。契约文件是微服务提供者和消费者共同定义的接口规范,包括接口的访问路径和输出数据。
CDC 的核心思想在于从消费者业务实现的角度出发,由消费者自己定义需要的测试数据格式以及交互细节,并驱动生成一份消费者契约。然后生产者根据契约来实现自己的逻辑,并在服务提供者端进行测试验证。契约文档应该被转换成一个存根。生产者会根据契约编写契约验证测试,契约验证测试通过会将契约文件转换为存根,存根会被消费者引用,契约的修改会导致任意一方测试的失败。这样的话可以保证契约被消费者和生产者共同遵守。
契约测试适用于微服务接口的消费者和提供者由不同的团队维护,或提供者接口被多个消费者消费这样的场景中。
端到端测试主要用于验证工作流程中的所有流程,以检查一切是否按照预期工作,确保系统以统一的方式工作,从而满足业务需求。端到端测试的难点在于安装和配置相关依赖,测试数据的自动准备二号服务的自动部署。
做微服务测试需要做 TDD,也就是测试在先,编码在后的开发实践。有别于以往的先编码、后测试的开发过程,而是在编程之前,先写测试脚本或设计测试用例。TDD 可以增加开发人员代码质量的信心,有利于代码设计和重构,以及快速迭代和持续交付。
微服务测试推进主要分为四步:第一步是工具,依照微服务测试层次,阶段选择合适的测试框架与工具;第二步是依据测试金字塔制定规范,贯穿生命周期始终,明确开发、测试人员的职责;第三步是自动化,贯穿 CI 、CD 流程,与 DevOps 的融合;第四步是测试平台搭建,以容器化技术搭建测试平台,以 namespace 隔离不同测试环境。