微服务测试

微服务测试简介

传统应用(基于单体架构开发的应用)的测试通常在开发完成后执行。开发人员将代码部署到测试环境,测试团队(更多的公司将其定义为质量团队,也称QA团队),测试团队按照黑盒的方式,从功能性、易用性、性能、响应时间等方面对已实现特性进行测试。传统的测试大多是手动执行,但存在以下问题:
(1) 手动测试效率极低。使用机器或自动化工具替代人工是必然的趋势。与机器相比,手动测试不仅有人工成本开销,而且执行速度慢,不能全天候工作。单纯依赖手动测试,无法保证快速且安全地交付高质量的软件。编写自动化测试至关重要。
(2) 测试执行越早,代价越低。在编写应用之后,确实应通过测试来找出软件潜在的问题,但这些测试是不充分的。一种更好的方式是让开发人员编写测试,并以此作为开发的一部分(业内将其称测试迁移,有时也称开发者测试)。开发者测试可以提高开发人员的工作效率,尽量保证功能的向后兼容性。
虽然开发者测试很重要,那么开发者测试落地程度怎样?实际上并不高。缺乏开发者测试的原因主要是文化:“测试是测试团队的工作”。而且,开发那些能够快速执行并且高效、可维护的测试用例也非常具有挑战性。
使用微服务架构的一个关键动机是提高可测试性。但微服务架构的复杂性对测试提出了更多的挑战。相比单体架构,微服务架构中进程间通信更频繁。基于微服务架构开发的应用本质是一个分布式系统。微服务间使用各种交互方式和进程间通信机制进行通信,更多微服务通信介绍可参考笔者之前的文章。

测试基本概念

在介绍微服务测试前,先简单回顾下测试的基本概念。

测试用例

本文重点介绍自动化相关测试,后面的测试都指自动化测试。测试用例的定义(wiki)如下:

测试用例是用于特定目标的一组测试输入、执行条件和预期结果,例如执行特定的程序路径或验证是否符合特定要求。  

可见,测试的目标是验证被测试系统的行为。在这里的“被测试系统”,是一个泛称,它指的是被测试的软件元素。它可以小到一个类,也可以大到整个应用。一组相关的测试用例集构成一个测试套件,简称测试套

自动化测试

自动化测试通常使用测试框架编写。如Junit是一种流程的Java测试框架。笔者所参与的云服务开发使用Junit 5框架编写测试用例。
每个自动化测试都是通过测试类中的一个测试方法实现。测试包括四个阶段:(1) 设置环境;(2)执行测试;(3) 验证结果;(4)清理环境。
(1) 测试环境: 将被测试系统以及其他相关元素组成的测试环境初始化为所需的状态。如初始测试类,设置属性等。
(2) 执行测试: 调用被测试系统。如在被测试的类上调用一个方法。
(3) 验证结果: 对调用的返回结果和被测试系统的状态进行判断。如验证方法的返回值,验证是否抛出异常等。
(4) 清理环境: 必要时清理测试环境。许多测试省略了这个阶段,但是某些类型的测试则必须考虑,如涉及数据库的测试,如果在测试阶段插入了新数据,在测试结束后,还应删除这部分数据,如果有必要。

模拟和打桩测试

被测试系统在运行时常常会依赖另一个系统。依赖的困难之处是这会把测试复杂化,并减慢测试速度。如服务A依赖其他服务、数据库、消息中间件、缓存中间件等服务。把整个系统的大部分都运行起来,而仅仅为测试一小部分功能,显然不切实际(又会回到单体地狱)。最好的做法是可以单独测试被测试系统。
上述问题的解决方案是用**测试替身(Test Double)**来消除被测试系统的依赖性,示意图如下:
微服务测试_第1张图片
测试替身是一个对象,该对象负责模拟依赖对象的行为。有两种类型的测试替身:桩(stub)和模拟(mock)。术语桩和术语模拟通常可以互换使用,尽管它们的行为略有不同。桩用来代替依赖项向被测试系统发送调用的返回值。模拟用来验证被测试系统是否正确调用了依赖项。此外,模拟通常也扮演桩的角色,向被测试系统发送调用的返回值。测试替身也有可直接使用的框架,笔者参与的云服务开发使用的测试替身框架是Mockito。

测试金字塔

一种组织测试的方法是测试金字塔。测试金字塔这一概念最早在 Mike Cohn 的著作《Succeeding with Agile》一书中提出。最初的测试金字塔由三层组成(自下往上分别是):单元测试、服务测试、用户界面测试。但对于微服务架构应用来说,测试金字塔由四层组层(自下往上分别是):单元测试、集成测试、组件测试、端到端测试。各层的含义是:
单元测试:测试服务的一小部分,如类。
集成测试:验证服务是否可以与基础设施服务或其他服务进行交互。
组件测试:单个服务的验收测试。
端到端测试:整个应用的验收测试。
微服务架构下测试金字塔示意图如下:
微服务测试_第2张图片
在金字塔的底部是快速、简单和可靠的单元测试。单元测试是自动化测试策略稳固的根基。最上层是缓慢、复杂和脆弱的端到端测试。测试金字塔的关键思想是:在金字塔中从下往上移动时,应编写的测试越来越少。
测试微服务架构的应用的关键挑战是:测试的复杂性从单个服务转移到它们之间的交互。

单元测试

单元测试是测试金字塔的最低级别。它们是面向技术的白盒测试,目标是协助开发。单元测试通常是一个类,因此单元测试的目标是验证这个类的行为是否符合预期。

集成测试

集成测试是测试金字塔中单元测试的上一层。它们验证服务是否与依赖服务正确交互。但与端到端测试不同,它们不会启动服务。但是,可以借助一些方法来简化测试,同时不影响测试有效性。第一个策略是测试替身。第二个策略是使用契约,它也可以实现简化验证服务之间交互的目的(实际上会更复杂)。契约是一对服务之间交互的具体实例。契约用于测试消费者和生产生,确保它们就API达成一致。它们的使用方式略有不同,具体取决于被测试服务是生产者还是消费者。笔者参与的云服务开发,使用的契约测试框架时Spring Cloud。在实际的应用中,契约测试的受欢迎度并不高,这主要因为契约测试需要跨团队协作,而对微服务架构团队来说,应尽可能少的涉及团队协同开发的场景。本质上,开发人员在修改已有接口时,如果能保证向下兼容,也能达到和契约测试一样的效果。如果发现问题,则通过问题单跟踪,避免服务间推诿。

组件测试

组件测试是测试金字塔中集成测试的上一层。组件测试就是服务的验收测试。组件测试单独验证服务的行为。它使用模拟其行为的桩代替服务的依赖关系。它甚至可能使用内存版本的基础设施服务,例如数据库。因此,组件测试更容易编写,运行速度更快。
验收测试是这对软件组件的面向业务的测试。验收测试从组件客户端而不是内部实现的角度描述了所需的外部可见行为。这些测试源自用户故事或用例。

端到端测试

组件测试分别测试每个服务,端到端测试会测试整个应用。端到端测试位于测试金字塔的顶端。因为这种类型的测试开发缓慢、脆弱且耗时。端到端测试由大量组件构成。开发人员必须部署多个服务及支撑它基础设施服务。因此,端到端测试很慢。此外,如果测试需要部署大量服务,则很有可能其中一个服务无法部署,从而使测试不可靠。一个好的策略是编写用户旅程(user journey)测试。用户旅程测试对应于用户使用系统的过程。如编写一个完成所有三个用例的单项测试,而不是单独测试三个用例。这种方法可以显著减少必须编写的测试数量并缩短测试执行时间。

探索性测试

介绍完微服务架构的测试金字塔的四层组层,接下来再介绍下探索性测试。探索性测试,最早是由测试专家 Cem Kaner 博士在 1983 年提出的。这里不再展开探索性测试,有兴趣的同学可以自行学习。

测试与流水线

云原生时代,每个服务都有一个部署流水线。Jez Humble的《Continuous Delivery》一书将部署流水线描述为把开发人员的代码部署到生产环境中的一个自动化过程。示意图如下:
微服务测试_第3张图片
随着代码通过部署流水线,测试套使其更像在生产环境进行测试。部署流水线中各阶段说明如下:
(1) 提交前测试阶段:执行单元测试。这是由开发人员在提交代码变更前执行的。
(2) 提交测试阶段:编译服务,执行单元测试,并执行静态代码分析。
(3) 集成测试阶段:执行集成测试。
(4) 组件测试:执行服务的组件测试。
(5) 部署阶段:将服务部署到环境(如开发环境、测试环境、预发环境、生产环境)中。
一般情况下,从代码提交到生产环境部署,部署流水线完全自动化。但是,有些情况需要手动步骤。如在将代码提交到生产环境前,需要在预发环境上浸泡一段时间。在这种情况下,只要当环境责任人审批后才能放行(卡点)。
部署流水线通常使用持续集成(Continuous Integration)服务器(如Jenkins)实现。

参考

微服务设计 Sam Newman 著, 崔力强 等 译
微服务架构设计模式 Chris Richardson 著, 陈斌 等 译
https://www.testwo.com/article/1618 测试金字塔的思考与总结
http://www.uml.org.cn/Test/202108205.asp?artid=24318 微服务架构下单元测试落地实践
https://blog.csdn.net/wxt_hillwill/article/details/115910345 什么是探索性测试

你可能感兴趣的:(云原生,微服务架构,微服务测试,测试)