1.1 微服务测试的要点
微服务测试要点有很多,而真正打通测试链路的要点通常有这6个。
1.1.1 一份有效的测试策略
测试一个项目之前,有一份有效的策略相当重要,它就像一把利器,可以用最低的成本达到最优的效果。制定测试策略,梳理和识别项目中可能会出现的质量风险并制定应对措施是关键。例如,制定测试微服务系统策略时一般更多地关注接口的幂等性、接口层的自动化测试比例以及上下游服务调用链的正确性。另外,改进传统三层测试金字塔,增加契约测试并且调整各层自动化比重,这些都能帮助我们更好地适配微服务系统的测试需求。所以,本书第2章会讨论在微服务系统中如何有针对性地制定测试策略。
1.1.2 一个构建接口层数据的好办法
想要做好接口层自动化测试,不备好数据是万万不行的。我们知道,单体应用中的测试数据通常是在页面操作中构造的,这样做有个很大的优点:易于理解和使用,操作几次就能记住构造数据的大体流程。而在微服务中,一个团队开发的内容可能没有页面,因为它是用来被其他服务调用的。如果没有页面,则了解页面操作是无效的,但这时我们要了解几个点:机器和机器之间调用的接口数据定义,测试时所需要的其他依赖数据的构造方法,如数据库中表的关联数据、消息队列的消息体等。
了解接口定义并不难,每个字段的含义、可允许的输入范围、不同数据对后续业务的影响等信息,通常可以从开发工程师给出的说明文档中获取。比较麻烦的是构造测试所需要的依赖数据,而这些依赖数据的生成过程并不直观,远不如直接在页面上操作方便。
例如,一个生成订单的接口,它的依赖数据一般包括客户详情、公司详情、库存信息、支付信息等。这些由其他团队开发的接口,常会缺少一些文档详细说明数据格式以及不同的数据内容对业务的影响。这些测试数据需要由测试人员来生成:查看数据库中相关数据表的内容,并尝试用写SQL等方式构造出一整套依赖数据,有时还需要构造消息队列消息体生成依赖数据,或者调用某些接口去生成数据。这个过程在初期特别容易出错,后期逐渐稳定下来会好很多,但仍会面临一堆SQL脚本不易维护的问题。碰到这种情况,我们都会把这些SQL脚本(或者构造消息队列的消息的过程等)自动化起来,用Java或者Python等语言编写的代码替换SQL脚本中需要改动的部分,最终一键生成整套测试数据。
对于这套初始化数据脚本,我们需要在测试业务流程中做进一步验证,以确保数据关联关系的完整性以及准确性。“前面业务流程都通过了,后面业务流程却抛出了异常,怎么办?”这时,要针对测试数据脚本进行查看并调试。这个工作并不好做,可能需要开发人员在代码中设置断点调试来寻找原因。但它的优势是,一旦这套测试数据脚本稳定下来,只需几秒就能生成一套复杂的测试依赖数据。更为重要的是,在后续的接口自动化测试中,这套数据依然可以继续使用。
在第3章中,我们讨论的内容是如何在微服务中做接口层的自动化测试,包括测试数据的生成、用例的设计和测试框架的使用。
1.1.3 端到端测试,减少耗时
端到端的测试通常包括两阶段。
第一阶段,针对新开发功能的上下游服务/系统做联调,确保接收到的数据能被正确处理,处理结束后还能将正确结果发送给下游。
第二阶段,联调工作涉及范围更广,从整个大系统的入口发起调用,经多个服务,到达新开发服务并进行业务处理,接着流转到下游服务(下游服务也可能做多个层次的处理),直到整个业务流程处理完毕。
第二阶段联调成本极大,主要体现在端到端联调的沟通上。因为这些服务是不同团队来维护,所以通常采取线上沟通的方式。而线上沟通又常出现对方不在线或者回应时间长等问题,这会导致联调过程的严重延时。
端到端联调的过程并不难,耗时是最主要的问题。除了上述线上沟通耗时外,还有一个特耗时的问题—定位Bug。Bug的定位很麻烦,需要一层层排查,有时候看似是上游发送了错误数据,实则是上上游发送的。这样来回地讨论和定位,就能耗上大半天时间。有没有更好的办法来解决这个问题呢?
我们知道,联调测试中出现问题的根本原因,在于“各忙各的”。各团队平时只负责各自的服务开发和变更,导致最后联调时一些接口对不上。为了避免问题集中式、爆发式出现,建议采用契约测试。在日常开发过程中,契约测试,特别是消费者驱动的契约测试,能够起到时刻防范和预警的作用。
契约测试可以实现自动化验证服务接口之间约定的参数格式和返回结果。当一个服务修改了代码,破坏了约定,测试程序便会在第一时间让流水线(pipeline)构建失败,从而快速暴露问题,让服务之间的接口契约得以保证。契约测试的优势在于能有效防止契约遭到破坏,从而让联调更加顺利。在我们日常工作中,如果每一天的流水线构建都能成功地通过契约测试,那端到端的联调也会顺利、省时很多。
第4章会详细讨论契约测试的思路、实施方法、工具与实践。
1.1.4 把握微服务系统整体质量
微服务团队中的测试工程师只服务于自己的开发团队,只保障自己团队的产品质量。而最终产品形态需要把各团队的单个服务组合起来,再对外提供服务。那么问题来了:当单个服务通过全面测试,假设已经做到100%的测试覆盖,那么微服务整体系统的质量是否就没有问题了呢?
以性能测试为例,正确答案是:在性能测试中,对单个服务及其所在调用链进行性能测试并通过后,不能确保微服务系统上线后不会出现性能问题。原因在于,多个服务之间存在共享消息队列、Redis缓存、硬件设备等,这会导致系统出现性能瓶颈。
关于如何对整体系统做全链路性能压测,我们会在第5章进行讨论。
微服务分布式的天然属性,决定了各服务间的调用可能存在网络不稳定、服务暂时不可用,甚至服务器物理停机等情况。为了应对这类异常场景,我们可以采取混沌工程的方式。我们可通过主动制造基础设施、网络和程序本身的故障,来验证系统在各种故障下的行为,提前识别问题,避免造成严重后果,继而验证生产环境出现失控情况,系统能否按照预期进行应对。
第8章介绍了混沌工程与传统测试、故障注入的区别,以及混沌工程的实施原则和工具的使用方法。
在微服务日常测试中,还有一个起着关键性作用的系统—监控系统。是否具备完备的监控措施是实施全链路压测、混沌工程实验的关键所在。监控不仅会记录操作日志、硬件指标等信息,还会记录多个请求间跨服务完成的逻辑请求信息,这为我们了解微服务系统实时运行状态提供了支持,也帮助我们更有针对性地进行性能优化和问题追踪。
第6章介绍了微服务监控工具的使用和监控指标的分析。
1.1.5 隔离依赖,实现独立测试
微服务系统的分布式特点带来的首当其冲的问题就是测试环境的稳定性。这一特点体现在系统结构上、服务部署上,甚至团队协作上。在这种多点网络中,任何一个节点的失效都会给微服务的测试工作带来阻碍。这里说的失效,在现实微服务项目中可能有各种呈现方式,比如:
- 服务A依赖服务B,但服务B很不稳定,经常出现异常;
- 服务B只有产品环境,没有集成测试环境;
- 服务B的集成测试环境只能满足一般的功能测试,无法承载服务A的性能测试需求。
这些环境问题的根源在于分布式部署极大地扩散了服务间相互依赖的关系图谱,识别依赖、解决依赖问题,很多时候会成为微服务测试工作在某个时间节点的痛中之痛。围绕这一问题,目前业界通用的解决方案聚焦在构建虚拟服务上。通过构建和引入合适的虚拟服务来局部地、有限地替换真实的依赖服务,解决从功能测试到性能测试中的大量依赖问题。
我们在第7章会了解虚拟化工具的原理、使用场景和详细示例。
1.1.6 守住第一道安全防护层
对安全要求高的公司在服务开发完成之后,一般会将安全测试交给专业的第三方安全部门或者安全咨询公司,这样安全测试对于团队内的测试人员来说就属于可选项,这导致大部分测试人员对安全测试并不十分清楚。在微服务构架中,这种在软件开发流程末端才介入安全测试的方式是不可取的,原因有二。
一是,开发团队内的安全测试是第一层防护。如果没有这层防护,当安全团队接手时,会出现大量明显的安全问题,需要二次甚至多次送检,这将给安全团队造成无谓的消耗,也会影响产品发布的时效。
二是,过晚发现安全问题会增加返工成本,甚至安全问题的结果会严重到让人无法承受,如架构设计上的安全问题。因此,微服务对安全内建的诉求更加迫切。
我们在第9章会从如何确定安全测试需求开始,了解在微服务中如何进行安全测试,以及如何利用安全测试工具。