【软件测试】学习笔记-微服务模式下API测试

news2024/11/19 0:22:58

这篇文章探讨当下最热门的技术领域的API测试,即微服务模式下的API测试。微服务架构下,API测试的最大挑战来自于庞大的测试用例数量,以及微服务之间的相互耦合。这篇文章探讨这两个问题的本质,以及如何基于消费者契约的方法来应对这两个难题。

而为了掌握微服务模式下的API测试,需要先了解微服务架构(Microservice Architecture)的特点、测试挑战;而要了解微服务架构,又需要先了解一些单体架构(Monolithic Architecture)的知识。所以,这篇文章将逐层展开,目的就是希望可以真正理解,并快速掌握微服务模式下的API测试。

单体架构(Monolithic Architecture)

单体架构是早期的架构模式,并且存在了很长时间。单体架构是将所有的业务场景的表示层、业务逻辑层和数据访问层放在同一个工程中,最终经过编译、打包,并部署在服务器上。

比如,经典的J2EE工程,它就是将表示层的JSP、业务逻辑层的Service、Controller和数据访问层的DAO(Data Access Objects),打包成war文件,然后部署在Tomcat、Jetty或者其他Servlet容器中运行。

显然单体架构具有发布简单、方便调试、架构复杂性低等优点,所以长期以来一直被大量使用,并广泛应用于传统企业级软件。

但是,随着互联网产品的普及,应用所承载的流量越来越庞大,单体架构的问题也被逐渐暴露并不断放大,主要的问题有以下几点:

  • 灵活性差:无论是多小的修改,哪怕只修改了一行代码,也要打包发布整个应用。更糟的是,由于所有模块代码都在一起,所以每次编译打包都要花费很长时间。
  • 可扩展性差:在高并发场景下,无法以模块为单位灵活扩展容量,不利于应用的横向扩展。
  • 稳定性差:当单体应用中任何一个模块有问题时,都可能会造成应用整体的不可用,缺乏容错机制。
  • 可维护性差:随着业务复杂性的提升,代码的复杂性也是直线上升,当业务规模比较庞大时,整体项目的可维护性会大打折扣。

正是因为面对互联网应用时,单体架构有这一系列无法逾越的鸿沟,所以催生了微服务架构。

其实,微服务架构也不是一蹴而就的,也经历了很长时间的演化发展,中间还经历了著名的SOA架构。但是这个由单体架构到SOA架构再到微服务架构的演进过程,并不是本文的重点,所以我就不再详细展开了,如果你感兴趣的话,可以自行去查阅一些相关资料。

微服务架构(Microservice Architecture)

微服务是一种架构风格。在微服务架构下,一个大型复杂软件系统不再由一个单体组成,而是由一系列相互独立的微服务组成。其中,各个微服务运行在自己的进程中,开发和部署都没有依赖。

不同服务之间通过一些轻量级交互机制进行通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,只需要关注并很好地完成一件任务就可以了,不同的服务可以根据业务需求实现的便利性而采用不同的编程语言来实现,由独立的团队来维护。

图1就很形象地展示了单体架构和微服务架构之间的差异。

图1 单体架构 VS 微服务架构

微服务架构具有以下特点:

  • 每个服务运行在其独立的进程中,开发采用的技术栈也是独立的;
  • 服务间采用轻量级通信机制进行沟通,通常是基于HTTP协议的RESTful API;
  • 每个服务都围绕着具体的业务进行构建,并且能够被独立开发、独立部署、独立发布;
  • 对运维提出了非常高的要求,促进了CI/CD的发展与落地。

微服务架构下的测试挑战

由于微服务架构下,一个应用是由很多相互独立的微服务组成,每个微服务都会对外暴露接口,同时这些微服务之间存在级联调用关系,也就是说一个微服务通常还会去调用其他微服务,鉴于以上特点,微服务架构下的测试挑战主要来自于以下两个方面:

  1. 过于庞大的测试用例数量;
  2. 微服务之间的耦合关系。

接下来,我会针对这两项挑战分别展开,包括它们从何而来,以及如何应对这些挑战,最终完成测试。

第一,过于庞大的测试用例数量

在传统的API测试中,我们的测试策略通常是:

  • 根据被测API输入参数的各种组合调用API,并验证相关结果的正确性;
  • 衡量上述测试过程的代码覆盖率;
  • 根据代码覆盖率进一步找出遗漏的测试用例;
  • 以代码覆盖率达标作为API测试成功完成的标志。

这也是单体架构时代主流的API测试策略。为了让你更好地理解这种测试策略,我来举一个实际的例子。

假设我们采用单体架构开发了一个系统,这个系统对外提供了3个Restful API接口,那么我们的测试策略应该是:

  • 针对这3个API接口,分别基于边界值和等价类方法设计测试用例并执行;
  • 在测试执行过程中,启用代码覆盖率统计;
  • 假设测试完成后代码行覆盖率是80%,那么我们就需要找到那些还没有被执行到的20%的代码行。比如图2中代码的第242行就是没有被执行到,分析代码逻辑后发现,我们需要构造“expected!=actual”才能覆盖这个未能执行的代码行;
  • 最终我们要保证代码覆盖率达到既定的要求,比如行覆盖率达到100%,完成API测试。

图2 基于代码覆盖率指导测试用例设计的示例

而当我们采用微服务架构时,原本的单体应用会被拆分成多个独立模块,也就是很多个独立的service,原本单体应用的全局功能将会由这些拆分得到的API共同协作完成。

比如,对于上面这个例子,没有微服务化之前,一共有3个API接口,假定现在采用微服务架构,该系统被拆分成了10个独立的service,如果每个service平均对外暴露3个API接口,那么总共需要测试的API接口数量就多达30个。

如果我还按照传统的API测试策略来测试这些API,那么测试用例的数量就会非常多,过多的测试用例往往就需要耗费大量的测试执行时间和资源。

但是,在互联网模式下,产品发布的周期往往是以“天”甚至是以“小时”为单位的,留给测试的执行时间非常有限,所以微服务化后API测试用例数量的显著增长就对测试发起了巨大的挑战。

这时,我们迫切需要找到一种既能保证API质量,又能减少测试用例数量的测试策略,这也就是我接下来要分享的基于消费者契约的API测试

第二,微服务之间的耦合关系

微服务化后,服务与服务间的依赖也可能会给测试带来不小的挑战。

如图3所示,假定我们的被测对象是Service T,但是Service T的内部又调用了Service X和Service Y。此时,如果Service X和Service Y由于各种原因处于不可用的状态,那么此时就无法对Service T进行完整的测试。

图3 API之间的耦合示例

我们迫切需要一种方法可以将Service T的测试与Service X和Service Y解耦。

解耦的方式通常就是实现Mock Service来代替被依赖的真实Service。实现这个Mock Service的关键点就是要能够模拟真实Service的Request和Response。当我介绍完基于消费者契约的API测试后,你会发现这个问题也就迎刃而解了。

基于消费者契约的API测试

那到底什么是基于消费者契约的API测试呢?直接从概念的角度解释,会有些难以理解。所以我打算换个方法来帮助你从本质上真正理解什么是基于消费者契约的API测试。接下来,就跟着我的思路走吧。

首先,我们来看图4,假设图4中的Service A、Service B和Service T是微服务拆分后的三个Service,其中Service T是被测试对象,进一步假定Service T的消费者(也就是使用者)一共有两个,分别是Service A和Service B。

图4 Service A、Service B和Service T的关系

按照传统的API测试策略,当我们需要测试Service T时,需要找到所有可能的参数组合依次对Service T进行调用,同时结合Service T的代码覆盖率进一步补充遗漏的测试用例。

这种思路本身没有任何问题,但是测试用例的数量会非常多。那我们就需要思考,如何既能保证Service T的质量,又不需要覆盖全部可能的测试用例。

静下心来想一下,你会发现Service T的使用者是确定的,只有Service A和Service B,如果可以把Service A和Service B对Service T所有可能的调用方式都测试到,那么就一定可以保证Service T的质量。即使存在某些Service T的其他调用方式有出错的可能性,那也不会影响整个系统的功能,因为这个系统中并没有其他Service会以这种可能出错的方式来调用Service T。

现在,问题就转化成了如何找到Service A和Service B对Service T所有可能的调用方式。如果能够找出这样的调用集合,并以此作为Service T的测试用例,那么只要这些测试用例100%通过,Service T的质量也就不在话下了。

从本质上来讲,这样的测试用例集合其实就是,Service T可以对外提供的服务的契约,所以我们把这个测试用例的集合称为“基于消费者契约的API测试”。

那么接下来,我们要解决的问题就是:如何才能找到Service A和Service B对Service T的所有可能调用了。其实这也很简单,在逻辑结构上,我们只要在Service T前放置一个代理,所有进出Service T的Request和Response都会经过这个代理,并被记录成JSON文件,也就构成了Service T的契约。

如图5所示,就是这个过程的原理了。

图5 收集消费者契约的逻辑原理

在实际项目中,我们不可能在每个Service前去放置这样一个代理。但是,微服务架构中往往会存在一个叫作API Gateway的组件,用于记录所有API之间相互调用关系的日志,我们可以通过解析API Gateway的日志分析得到每个Service的契约。

至此,我们已经清楚地知道了如何获取Service的契约,并由此来构成Service的契约测试用例。接下来,就是如何解决微服务之间耦合关系带来的问题了。

微服务测试的依赖解耦和Mock Service

在前面的内容中,我说过一句话:实现Mock Service的关键,就是要能够模拟被替代Service的Request和Response。

此时我们已经拿到了契约,契约的本质就是Request和Response的组合,具体的表现形式往往是JSON文件,此时我们就可以用该契约的JSON文件作为Mock Service的依据,也就是在收到什么Request的时候应该回复什么Response。

下面的图6就解释了这一关系,当用Service X的契约启动Mock Service X后,原本真实的Service X将被Mock Service X替代,也就解耦了服务之间的依赖,图6中的Service Y也是一样的道理。

图6 基于Mock Service解决API之间的调用依赖

代码实例

自此,已经讲完了基于消费者契约的API测试的原理。

由于这部分内容的理论知识比较多,为了更好地理解这些概念,找了一个基于Spring Cloud Contract的实际代码的示例演示契约文件格式、消费者契约测试以及微服务之间解耦,希望可以帮到你。

具体的实例代码,你可以从GitHub - SpectoLabs/spring-cloud-contract-blog下载,详细的代码解读可以参考Consumer-Driven Contract Testing with Spring Cloud Contract | API simulations for development and testing。

这个实例代码,基于Spring Boot实现了两个微服务:订阅服务(subscription-service)和账户服务(account-service),其中订阅服务会调用账户服务。这个实例基于Spring Cloud Contract,所以契约是通过Groovy语言描述的,也就是说实例中会通过Groovy语言描述的账户服务契约来模拟真实的账户服务。

这个实例的逻辑关系如图7所示。

图7 基于Spring Cloud Contract的契约测试实例

总结

单体架构,具有灵活性差、可扩展性差、可维护性差等局限性,所以有了微服务架构。

微服务架构的本身的特点,比如微服务数量多,各个微服务之间的相互调用,决定了不能继续采用传统API测试的策略。

为了既能保证API质量,又能减少测试用例数量,于是有了基于消费者契约的API测试。基于消费者契约的API测试的核心思想是:只测试那些真正被实际使用到的API调用,如果没有被使用到的,就不去测试。

基于消费者契约的测试方法,由于收集到了完整的契约,所以基于契约的Mock Service完美地解决了API之间相互依赖耦合的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1375844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL-数据类型

目录 DDL-表操作-数据类型 数值类型 字符串类型 举例: 案例: 日期时间类型 案例 表操作-案例 🎉欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克🍹 ✨博客主页:小小恶斯法克的博客 &a…

【常用的简单功能及算法】拦截器 加盐算法 深克隆 时间日期格式化 加盐算法 sql分页算法 验证码

1.实现拦截器 Interceptor (以登录拦截器为例) 1.1 写一个登录拦截器普通类 实现HandlerInterceptor接口重写preHandle方法 //检验登录状态拦截器 //实现接口HandlerInterceptor 重写方法preHandle public class LoginInterceptor implements HandlerInterceptor {/** 该方…

网络安全保险发展起始阶段的挑战及应对措施

文章目录 前言一、网络安全保险的有序发展二、当前我国网络安全保险发展的初期态势(一)网络安全风险类型(二)网络安全保险的作用(三)与外国网络安全保费的规模对比 三、我国网络安全保险发展初期面临的挑战…

一天一个设计模式---适配器模式

概念 适配器模式是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的接口之间进行协同工作,使得原本由于接口不匹配而无法合作的类能够一起工作。 具体内容 适配器模式主要包括以下几个要素: 目标接…

yolov8 瑞芯微 RKNN 的 C++部署,部署工程难度小、模型推理速度快

之前写过两次yolov8目标检测部署,后续继续思考,针对部署还有优化空间,本示例的部署方式优化了部署难度,加快了模型推理速度(略微增加了后处理的时耗)。 特别说明:如有侵权告知删除,…

半Happy的一天

终于差不多将SWMM模型与LisFlood模型耦合运转起来了 MDL的雏型也出来了,注册了模型方法和参数,差一个方法参数 晚上和师兄聊了聊未来规划,回顾了这半年研究生生涯的“拍烂”生活(其实也没特别摆烂,还是学了不少东西&…

服务器数据传输安全如何保障?保障意义是什么?

数据安全,是指通过采取必要措施确保数据处于有效保护和合法利用的状态,以及具备保障持续安全状态的能力。数据安全应保证数据生产、存储、传输、访问、使用、销毁、公开等全过程的安全,并保证数据处理过程的保密性、完整性、可用性。无论是互…

SpringBoot知识02

1、快速生成mapper和service &#xff08;自动生成简单的单表sql&#xff09; 2、springboot配置swagger&#xff08;路径不用加/api&#xff09; &#xff08;1&#xff09;主pom导包&#xff08;子pom要引用&#xff0c;可选依赖&#xff09; <!-- swagger3…

git提交记录全部删除

目录 问题描述 解决方案 结果 问题描述 新复制的项目具有特比多的提交记录我想给他清除&#xff0c;因为不清楚过多历史也就导致包特别大下载和提交等方面都不是很快 解决方案 查看代码clone网址&#xff1b; 打开远程仓库&#xff0c;选择要去除历史记代码分支&#xff08…

C2855 命令行选项“/Zc:referenceBinding“与预编译头不一致和C2855 命令行选项“/Zc:__cplusplus“与预编译头不一致

在VS2019和Qt5.12.12环境下&#xff0c;笔记本上编译这个工程没有问题&#xff0c;把工程拷贝到台式机上&#xff0c;一样的配置&#xff0c;但是报如下错误&#xff1a; 打开项目的命令行配置如下&#xff1a; 解决办法&#xff1a;在编译选项"/Zc:referenceBinding"…

大模型LLM Agent在 Text2SQL 应用上的实践

1.前言 在上篇文章中「如何通过Prompt优化Text2SQL的效果」介绍了基于Prompt Engineering来优化Text2SQL效果的实践&#xff0c;除此之外我们还可以使用Agent来优化大模型应用的效果。 本文将从以下4个方面探讨通过AI Agent来优化LLM的Text2SQL转换效果。 1 Agent概述2 Lang…

内网穿透的应用-使用Docker部署开源建站工具—Halo,并实现个人博客公网访问

文章目录 1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可参考已安装Docker步骤&#xff1a;1.2 在Docker中部署Halo 2. Linux安装Cpolar2.1 打开服务器防火墙2.2 安装cpolar内网穿透 3. 配置Halo个人博客公网地址4. 固定Halo公网地址 本篇文章介绍如何在CentOS下使用D…

2023年北邮渣硕的暑期秋招总结

背景 实验室一般是在研究生二年级的时候会放实习&#xff0c;在以后的日子就是自己完成毕业工作要求&#xff0c;基本上不再涉及实验室的活了&#xff0c;目前是一月份也是开始准备暑期实习的好时间。实验室每年这个时候都会有学长学姐组织暑期实习经验分享&#xff0c;本着不…

Nginx配置反向代理实例二

Mac 安装Nginx教程 Nginx配置反向代理实例一 提醒一下&#xff1a;下面实例讲解是在Mac系统演示的&#xff1b; 反向代理实例二实现的效果 使用nginx 反向代理&#xff0c;根据访问的地址跳转到不同端口的服务中 nginx 监听端口为81&#xff1b; 访问地址1&#xff1a;http:/…

ptaR7-6/zzuli2106 有去有回

题目 输入n个整数&#xff0c;第一趟按从左到右间隔k个数取数据&#xff0c;然后第二趟再从右到左间隔k-1个数取余下的数&#xff0c;如果数据没有取完&#xff0c;下一趟再间隔k-2个从左到右取数据&#xff0c;如此反复&#xff0c;直到所有的数据取完为止。注意&#xff1a;…

API设计:从基础到优秀实践

在这次深入探讨中&#xff0c;我们将深入了解API设计&#xff0c;从基础知识开始&#xff0c;逐步进阶到定义出色API的最佳实践。 作为开发者&#xff0c;你可能对许多这些概念很熟悉&#xff0c;但我将提供详细的解释&#xff0c;以加深你的理解。 API设计&#xff1a;电子商…

2024年 最新 iPhone手机 历代机型、屏幕尺寸、纵横比、分辨率 整理

&#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#xff1a;游戏制作 &…

css3边框与圆角

css3边框与圆角 前言边框的三要素边框的三要素小属性 四个方向的边框四个方向边框的三要素小属性 去掉边框利用边框制作三角形圆角 border-radius单独设置四个圆角小属性百分比为单位 盒子阴影阴影延展内阴影多阴影 结语 前言 在网页设计中&#xff0c;边框与圆角不仅仅是简单…

强化学习求解TSP(七):Qlearning求解旅行商问题TSP(提供Python代码)

一、Qlearning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于奖励的决策问题。它是一种无模型的学习方法&#xff0c;通过与环境的交互来学习最优策略。Q-learning的核心思想是通过学习一个Q值函数来指导决策&#xff0c;该函数表示在给定状态下采取某个动作所获…

对git中tag, branch的重新理解

1. 问题背景 项目中之前一个tag&#xff08;v1.0&#xff09;打错了&#xff0c;想删除它&#xff0c;但我们从此tag v1.0中迁出新建分支Branch_v1.0,在此分支下修复了bug&#xff0c;想重新打一个tag v1.0&#xff0c;原来的tag v1.0可以删除掉吗&#xff1f; 错误的理解&am…