1.质量保证体系
测试策略
1.分层
2.自动化
在微服务架构下,提升自动化测试的覆盖率对测试环境、测试技术和测试方法都提出了新的要求,具体如下。
● 对于测试环境,需要保证有专门的人员进行维护,避免当需要测试的时候没有环境可用,以及当环境出现问题时不知道找谁维护。
● 对于测试技术,我们需要根据不同微服务的特点选取合适的测试技术。但需要注意的是,技术的多样性会导致测试环境搭建和维护的成本增加,如果基于同样的技术栈,我们推荐在团队内部尽量使用同一套测试技术和编程语言。
● 对于测试方法,我们需要调整之前用于单体应用的测试方法,针对微服务的特点选取不同层次的测试方法来保证交付质量。
自动化测试的顺利开展还有赖于持续集成与持续部署的基础设施建设
构建质量保证体系
1.建立质量保证模型
需求定义阶段:产品经理会给出用户验收测试(UAT,User Acceptance Testing)的关键测试点作为应用验收的标准。
需求分析与系统设计阶段:我们会召开技术方案评审会议(Tech Approach)来探讨几种技术实现方案的优劣,并确定最终的总体技术方案。对涉及多个微服务或多个应用的需求,我们会邀请对应团队的开发测试人员一起评审,并形成一份接口文档让各方“签字画押”(sign-off)。这样每个团队就可以基于接口文档各自进行开发测试。
详细设计阶段:我们会对应召开两类会议,技术设计审计会议和测试用例审计会议,分别讨论应用实现方案中的技术细节和测试用例设计。对涉及多个微服务或多个应用的需求,还会举行专门的集成测试用例评估会议。这个会议将再度邀请对应团队的开发测试人员一起参与,确认测试用例是否满足需求,并约定好在将来的某个时间点进行集成测试。
编写代码阶段:开发人员通过编写业务代码,撰写单元测试、回归测试来保证实现是符合预期的。我们通过代码审计、Sonar检测、持续集成流水线的结果在这一环节检测代码的质量。
集成阶段:测试人员通过搭建好的测试平台,执行之前各方约定好的集成测试。
部署上线阶段:开发测试人员会在线上再进行Bug大扫除(Bug Bash)和发布后检查(Post-release Check),确保上线后的功能按预期运行。
交付验收阶段:产品经理会根据之前UAT确定的关键点进行用户验收测试,完成之后将产品交付给客户,同时借助持续集成流水线实现蓝绿部署、灰度发布等功能,使得交付更加灵活和可控。
持续监控错误报警阶段:开发测试人员会加上相应的监控报警来观察整个系统的运转情况,必要时会对线上流量进行收集并进行线下回放来满足离线调试、大数据测试等需求。
2.质量保证体系实战
线下部分涉及开发阶段的质量保证相关操作
基础测试实践:涵盖单元测试、回归测试、集成测试
文档管理:伴随着架构演进和业务调整,测试文档需要进行归并,业务文档也需要及时维护
性能测试与混沌工程:链路级的性能测试有助于团队对系统瓶颈进行评估,进而对可能遇到的问题提前准备预案。混沌工程则以随机实验的方式进行“故障演练”,通过观察系统的反应来明确系统稳定工作的边界
分支管理、依赖版本管理、包管理:在代码部署层面,我们需要维护开发、测试、预发布、生产等多种环境,因而开发测试也需要针对不同的分支进行。在此基础上,还需要管理好不同版本的依赖和安装包,保证它们在每个环境下都能正常运行
线上部分涉及产品发布到生产环境之后进行的质量保证相关操作
灰度发布与蓝绿部署
业务监控与错误报警
用户验收测试与安全审计测试
容灾测试与多活测试
基础设施用于助力质量保证体系的实现,例如测试工具、质量理念等,具体分为三类
自动化测试框架:自动化测试框架有助于简化团队成员撰写各层次测试代码的难度,同时还能更快捷地提供诸如代码覆盖率等关键数据。
持续集成流水线与持续交付流水线:持续集成与持续交付的支持使微服务系统得以更快迭代,结合自动化测试可以帮助团队更快地发现问题和解决问题。
质量文化氛围:微服务架构下的系统迭代飞速,对质量的保证离不开团队里每个成员的努力。在团队里建立起质量文化氛围是十分重要的一环。
2.测试实践
在测试金字塔里,从底端到顶端依次为单元测试、集成测试、端到端测试和性能测试。其中,越靠近底端,测试速度越快,反馈周期也越短,测试发现问题后更容易定位受影响的功能;越靠近金字塔的顶端,测试覆盖的范围越大,完成测试所需的时间越长,经过测试后,功能的正确性也更有保证。
单元测试与mock实践
“单元”是软件的最小可测试部件。单元测试就是软件开发中对最小部件进行正确性检验的测试,它是所有测试中的底层测试,由开发人员在开发代码时同步编写,是第一个也是最重要的一个测试环节。
1.基础规则
该框架为测试文件定义了一些规则。
● 每个测试文件必须以_test.java为后缀进行命名,通常与被测试文件放在同一个包内。
● 单元测试的函数名必须以Test开头,为可导出的函数。
● 单元测试函数在定义时必须接收一个指向testing.T类型的指针作为参数,并且没有返回值。
2.表格驱动测试
在单元测试的编写过程中,经常需要重复指定一些输入输出对不同的用例进行测试。使用表格驱动(Table Driven)的方式编写单元测试。通过在表格中列出输入、输出,循环遍历执行测试。
3.mock实践编写单元测试时往往有独立性的要求,很多时候因为业务逻辑复杂,代码逻辑也随之变得复杂,依赖了很多其他组件,导致在编写单元测试时存在比较复杂的依赖项,如数据库环境、网络环境等,编写工作量大大增加。解决这类问题的主要思路是使用mock。
集成测试实践
集成测试在单元测试完成后进行,它将多个代码单元及所有集成服务(如数据库等)组合在一起,测试它们之间的接口的正确性。随着核心业务架构转向微服务架构的步伐加快,以及构建的服务越来越多,我们设计了适用于不同服务的集成测试用例,在构建新服务时可以最大限度地降低学习和测试成本。
端到端测试实践
端到端测试是站在用户使用角度进行的测试,它将要测试的软件视为黑盒,无须了解其内部具体实现细节,只关注输出结果是否符合预期。
● 本地测试:当代码位于自定义分支尚未被合并到主干分支时,需要进行端到端本地测试,开发人员通过添加新的端到端测试用例来完成功能检测。
● 回归测试:功能代码合并到主干分支后,需进行端到端回归测试。
● 发布后检查测试:功能发布到线上环境之后,需进行端到端测试实现发布后检查,以确保该功能在线上环境仍能按预期工作。
测试自动化
持续集成阶段
代码合并到主干分支前会触发持续集成测试,测试通过后,代码才允许被合并到主干分支。
代码合并到主干后会触发持续集成测试,目的是检验主干分支是否符合质量预期。
由Groovy脚本定义的Jenkins流水线的blue ocean效果图,下面将结合具体案例对测试相关的几个重要阶段进行分析,包括单元测试与覆盖率报告阶段(UT&Coverage)、集成测试与覆盖率报告阶段(Regression,Combine Coverage),以及代码覆盖率通知阶段。
(1)单元测试与覆盖率报告阶段
只需要在Groovy脚本中指定单元测试使用的脚本,并将生成覆盖率的开关打开,即可获得测试覆盖率报告
(2)集成测试与覆盖率报告阶段
获取集成测试覆盖率报告的方式与单元测试类似,只需要在Groovy脚本中指定回归测试使用的脚本,持续集成流水线就会将生成的结果输出
(3)代码覆盖率通知阶段
公司层面设定了90%的代码覆盖率目标。这个值是将单元测试和集成测试的覆盖率合并之后的结果。为了达到这一目标,我们采取了逐步提升覆盖率的方式,对单元测试和集成测试分别设定各自达标的时间线。在每个新产品版本的开发周期,通过Groovy脚本设置该版本需要达到的测试覆盖率目标值。对于测试失败或覆盖率没有达标的代码,其合并请求均不能通过,并且会通过Slack通知相关人员。
持续部署阶段
产品被部署到线上之后,可通过流水线关联触发功能,触发端到端测试的Jenkins任务,进行产品上线之后的相关测试。
3.混沌工程
混沌工程是在分布式系统上进行实验的学科,目的是建立系统抵御生产环境中失控情况的能力及信心。
混沌工程的核心理念
混沌工程的意义就是在系统上进行实验,根据实验结果发现系统有哪些缺陷,然后找到应对方案来避免故障,或者在发生故障时将损失降到最低。通过不断进行迭代实验,我们可以建立起稳定可靠的系统,这样不仅可以很好地给客户提供服务,还能减少工程师们半夜被叫起来处理故障的次数。
混沌工程实验(以下简称混沌实验)与传统的测试有很大的区别。传统测试会提前定义好输入和输出,如果不满足期望结果,测试用例就不会通过。混沌实验不是,实验会产生什么样的结果是不确定的。比如网络延迟、CPU过载、内存过载、I/O异常等会对系统产生什么样的影响,有些我们可以预料,有些则不然,只有发生了才会知道。混沌实验最好能够成为团队日常工作的一部分,这样每一位团队成员都可以担任混沌工程师的角色,并有机会从实验结果中学到知识。
在运行混沌实验时,有五大原则需要遵循:
定义稳定状态、
定义改变现实世界的事件、
在生产环境中运行、
使实验可以自动化连续运行、
最小化爆炸半径。
基于服务网格的网络流量故障注入方法
服务A发送请求到服务B,网络流量故障注入是通过服务B的Envoy Proxy劫持流量实现的。
Istio提供了网络延迟和网络中断两种故障注入方式。延迟主要模拟网络延迟或过载,中断主要模拟上游服务崩溃。这两种故障注入都是通过配置Istio的Virtual Service中的HTTP节点实现的。
网络延迟示例如下,每100个请求中会有一个请求发生5s的延迟。
网络中断示例如下,每100个请求中有一个请求返回404状态码。