微服务架构的应用如何测试?
最近几年,微服务架构越来越火爆,逐渐被企业所采用。随着软件架构的变化,对应的软件测试策略需要作何调整呢?本文将介绍微服务架构下的测试策略,并结合分享在业务和架构演变过程中,一个历经九年的项目测试策略的演进。
关于微服务
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的进程中,服务间采用轻量级通信机制互相沟通(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体的业务进行构建,并且能够被独立部署到生产环境、预生产环境。
从微服务的概念可以看出它有如下好处:
- 每个服务可以独立开发
- 处理的单元粒度更细
- 单个服务支持独立部署和发布
- 更有利于业务的扩展
同时,独立开发导致技术上的分离,HTTP通信加上Queue的机制增加了问题诊断的复杂度,对系统的功能、性能和安全方面的质量保障带来了很大的挑战。另外,服务间的复杂依赖关系带来了很多的不确定性,要实现独立部署,对运维也提出了更高的要求。
微服务架构的系统要特别关注这几个方面:
- 服务间的依赖、连通性
- 服务的容错、可用性
- 数据的最终一致性
- 独立部署
- 不确定性
测试策略的选择,微服务框架下的测试策略是这样的:
经典策略模型
这个策略模型强调测试分层以及每一层的恰当覆盖,整体符合金字塔结构。它是最优的吗?
有人对此提出了质疑...认为策略模型应该是蜂巢形状的(请参考文章):
蜂巢模型
这个模型重点关注服务间的集成测试,两端的单元测试和UI层E2E测试较少。
也有同事提出微服务下的测试结构应该是钻石形状的,服务间的集成依然是重点,单元测试较少,而顶层增加了安全和性能等非功能测试。
钻石模型
好像都有道理,到底选择什么样的策略模型好呢?不禁陷入了困境…...怎么办?不妨先来听听我们项目的故事吧!
项目的故事
测试策略的演进
还是那个蓝鲸项目,不知不觉进入了第九个年头。在这九年里,随着业务的不断发展,系统架构也进行了多次演进和调整。相应的,测试策略也发生了有意思的演进变化。
测试策略的演进
最初单一用户系统、单体架构的时候,严格按照测试金字塔来组织各层的自动化测试。随着功能的扩展,大量mock的单元测试给重构带来了很大的不便。
企业系统开始开发的时候,我们调整了策略,减少单元测试的编写,增加UI层E2E测试的覆盖,测试结构由原来的金字塔演变成上面梯形下面倒三角的形式。
后来,架构调整,开始服务化。此时,大量的E2E测试渐渐暴露出问题:
- CI上的测试执行时间越来越长,而且定位问题的能力很弱,测试一旦失败需要很长时间修复,测试人员好几天也拿不到可以测试的版本,反馈周期过长;
- 由于服务化带来的不稳定因素增加,E2E测试没法很好的覆盖到需要的场景,测试人员就算拿到可测的版本也总有各种缺陷发生。
因此,项目引入契约测试,停止编写新的E2E测试,将测试下移,分别用API测试和契约测试取代。
随着功能的不断增加,虽然E2E测试的量并不增加,但是其不稳定性、维护难、定位难的问题有增无减,此时已经很难由自动化测试来保证产品的质量。为了平衡成本和收益,项目考虑去掉大部分E2E测试,只保留少量的Smoke测试,将更多的测试下移。
同时,技术雷达上新的技术“生产环境下的QA”出现,项目也开始关心生产环境,并且在QA测试阶段结合微服务的特点进行对应的探索式测试。
应对微服务的挑战
前文提到过微服务带来的挑战,下面来看项目是如何应对这些挑战的。
- 服务间的依赖、连通性
微服务架构下,独立开发的服务要整合起来最具挑战,如何保证服务间的依赖关系和连通性非常关键。前面已经讲过E2E集成测试有很大的挑战,并不适合,而消费端驱动的契约测试是个不错的选择。项目正是利用契约测试去保证服务间的连通性,取代一部分E2E集成测试。
- 服务的容错、可用性
在系统负荷达到一定程度或者某个服务出现故障的时候,微服务架构有两种技术来确保系统的可用性:服务的熔断和降级。服务的熔断是指当某个服务出现故障时,为了保证系统整体的可用性,会关闭掉出现故障的服务;服务的降级则是当系统整体负荷过载的时候,考虑关闭某些外围服务来保证系统的整体可用性。
对应的测试包括:
- 熔断:从性能角度,当系统负载达到某个熔断状态的时候,服务是否能正确熔断;同时,从功能角度验证熔断后系统的行为是否跟预期相符;
- 降级:从业务的角度,要能区分出核心业务和外围业务,在需要降级的时候不能影响核心业务;当某个服务降级后,从功能角度验证系统行为是否跟预期相符。
- 数据的最终一致性
数据一致性
数据一致性是微服务特别需要关注的。举个例子,电商平台某个订单支付成功以后,需要更新积分和订单状态,当订单服务或者积分服务其中有一个出现故障的时候,就会导致最终的数据不一致性。
测试这种情况,从业务的角度分析哪些服务会导致数据不一致性,制造对应的异常情况去测试数据的最终一致性。
- 独立部署
微服务的独立部署需要有CI、CD的支持,跟DevOps实践分不开。同时,更为关键的是需要契约测试来验证独立部署后服务行为的正确性。
- 不确定性
微服务架构使得系统复杂度增加不少,很多的事情发生都是不可预测的,只能在其发生以后找到产生的原因。因此,也就没法在预生产环境通过测试去发现在真实生产环境才会发生的issue,我们需要把目光转移到生产环境,利用生产环境的不确定性、微服务的不可预测性来构建反脆弱的系统。
项目测试策略
从前面介绍的演进过程可以看到,项目测试策略在不同阶段结合参考了不同的策略模型:金字塔->近似钻石(除非功能测试外,类似于钻石模型)->蜂巢。后期全面服务化的时候,我们认为蜂巢模型是比较适合的。
当然,光有符合这个策略模型的自动化测试是远远不够的,我们项目还采用了针对微服务特点的探索式测试,保持持续交付节奏,践行DevOps实践,结合生产环境下的QA等技术把关注点右移到生产环境。
现在,项目整体测试策略演变成下图的形式:
项目测试策略
- 项目采用的是敏捷迭代开发和持续交付的模式,每四周一个发布周期。
- 在开发过程中实现的自动化测试是分层实现的:底层少量的单元测试,中间量最多的是API测试(类似于老马推荐的策略模型里的组件测试),上面有一部分契约测试和少量的Smoke测试来保证服务间的契约和集成。除此之外,QA有手动的探索式测试,其中包括针对微服务特点进行的一些测试。整个测试结构是类似于蜂巢模型的。
- 采用生产环境下的QA技术,利用生产环境,进行error监控、用户行为分析、用户反馈收集,从而来影响和指导预生产环境的开发和测试工作。
- 利用DevOps实践,做到高效的部署和监控,跟生产环境下的QA结合,形成良性的环路,保证项目的正常交付。
测试策略再思考
项目上多次测试策略的调整,看似很简单,其实每次调整并不是一个轻松的过程,都是平衡利弊、综合考虑多个因素才做出的决定。
分析整个调整过程,最后突然发现:当我们面对多个策略模型不知道如何选择的时候,其实我们陷入了一个太过于关注测试结构的误区,忘记了最初的目标是什么。
影响测试策略的因素
跳出误区,回到原点,重新思考测试策略的目标。影响策略的最关键因素是业务价值、质量要求、痛点。
影响测试策略的因素
- 业务价值
带来更大的业务价值、帮企业赢得更多的利润,是软件系统的目标;软件测试是软件系统成功的保障之一,业务价值也是测试策略的终极目标。所有测试活动都要围绕这个目标开展,考虑业务优先级,有效规避业务风险。
- 质量要求
不同的系统、同一系统的不同利益干系人(参与的不同角色)对于质量的定义和要求都可能是不同的,这毫无疑问是影响测试策略的一个关键因素。
对于仅有内部用户的系统,关注的重心可能是系统的功能;而对外发布的产品,则要求更高,一个按钮位置的不恰当都可能带来大量用户的流失。
- 痛点
真正的痛点往往也是优先级最高,迫切需要解决的。那些可以通过测试策略的调整来解决的痛点,自然成为了关键的影响因素之一。比如,CI Pipeline出包太慢,为了提高出包的效率,一方面在Pipeline本身想办法,另一方面调整自动化测试的比例、执行频率等也是解决方案之一。
演进式测试策略
处在不同阶段的项目,在业务价值这个大目标下,其他影响因素也是会不一样的,跟技术架构的演进一样,测试策略也应该是演进式的。
从目标出发,综合所处阶段各个方面的影响因素,制定出适合当时的测试策略。随着时间的推移,对策略进行评估和度量,并进一步改进、提高,以更好的满足需求。这就是目标驱动的演进式测试策略。
总结
微服务架构下多个服务的整合是最具有挑战的,对此最重要的是契约测试。契约测试有效保证服务间的契约关系不被破坏,确保服务的连通性,有助于实现真正的独立部署和独立交付。
微服务架构引入的不确定性并不是坏事,可以利用这些不确定性,采用生产环境下的QA等技术,增强系统的反脆弱性,从中获益。
测试策略的影响因素不是唯一的,技术架构并不是最关键的因素。微服务架构下的测试策略跟其他架构下的并不会有本质的区别。
业务价值始终是我们的终极目标。在这个终极目标的驱动下,测试策略不是制定完了就可以束之高阁的,需要在整个软件系统构建过程中不断的度量和改进,是演进式的。