23|知其然知其所以然:聊聊API自动化测试框架的前世今生
文章目录
你好,我是茹炳晟,今天我和你分享的主题是“知其然知其所以然:聊聊 API 自动化测试框架的前世今生”。
在上一篇文章中,我以一个简单的 Restful API 为例,分别介绍了 cURL 和 Postman 的使用方法,相信你已经对 API 测试有个感性认识了。
但是,我们不能仅仅停留在感性认识的层面,还需要熟悉并掌握这些测试方法,完成相应的 API 测试工作。所以,也就有了我今天分享的主题,希望可以通过对 API 自动化测试框架发展的介绍,让你理解 API 测试是如何一步一步地发展成今天的样子,以“知其所以然”的方式加深你对 API 自动化测试的理解。
接下来,我将会遵循由简入繁的原则,为你介绍 API 测试框架,以发现问题然后解决问题的思路为主线,展开今天的分享。
早期的基于 Postman 的 API 测试
早期的 API 测试,往往都是通过类似 Postman 的工具完成的。但是,由于这类工具都是基于界面操作的,所以有以下两个问题亟待解决:
- 当需要频繁执行大量的测试用例时,基于界面的 API 测试就显得有些笨拙;
- 基于界面操作的测试难以与 CI/CD 流水线集成。
所以,我们迫切需要一套可以基于命令行执行的 API 测试方案。这样,API 测试可以直接通过命令行发起,与 CI/CD 流水线的整合也就方便得多了。
基于 Postman 和 Newman 的 API 测试
于是就出现了集成 Postman 和 Newman 的方案,然后再结合 Jenkins 就可以很方便地实现 API 测试与 CI/CDl 流水线的集成。Newman 其实就是一个命令行工具,可以直接执行 Postman 导出的测试用例。
用 Postman 开发调试测试用例,完成后通过 Newman 执行,这个方案看似很完美。但是在实际工程实践中,测试场景除了简单调用单个 API 以外,还存在连续调用多个 API 的情况。
此时,往往会涉及到多个 API 调用时的数据传递问题,即下一个 API 调用的参数可能是上一个 API 调用返回结果中的某个值。另外,还会经常遇到的情况是,API 调用前需要先执行一些特定的操作,比如准备测试数据等。
因此,对于需要连续调用多个 API 并且有参数传递的情况,Postman+Newman 似乎就不再是理想的测试方案了。
基于代码的 API 测试
为了解决这个问题,于是就出现了基于代码的 API 测试框架。比较典型的是,基于 Java 的 OkHttP 和 Unirest、基于 Python 的 http.client 和 Requests、基于 NodeJS 的 Native 和 Request 等。
小型的互联网企业,往往会根据自己的业务需求,选用这些成熟的 API 测试框架。
但是,对于中大型的互联网企业,一般都会自己开发更适合自身业务上下文的 API 测试框架,比如 eBay,我们为了实现代码化的 API 测试,开发了自己的 HttpClient,后期为了使 API 测试的代码更简洁易懂,就基于 Rest-Assured 封装了全新的 API 测试框架。
这种根据公司业务上下文开发实现的 API 测试框架,在使用上有很多优点,而且灵活性也很好,主要体现在以下几个方面:
- 可以灵活支持多个 API 的顺序调用,方便数据在多个 API 之间传递,即上一个 API 调用返回结果中的某个字段值可以作为后续 API 调用的输入参数;
- 方便在 API 调用之前或者之后执行额外的任意操作,可以在调用前执行数据准备操作,可以在调用后执行现场清理工作等;
- 可以很方便地支持数据驱动测试,这里的数据驱动测试概念和 GUI 测试中的数据驱动测试完全相同,也就是可以将测试数据和测试代码分离解耦;
- 由于直接采用了代码实现,所以可以更灵活地处理测试验证的断言(Assert);
- 原生支持命令行的测试执行方式,可以方便地和 CI/CD 工具做集成。
这里我给出了一段伪代码示例,用于展示如何用代码实现一个简单的 API 测试。
图 1 基于代码的 API 测试的伪代码示例
- 代码的第 1-12 行,创建了 CreateUserAPI 类,其中包含了 endpoint、操作方法 PUT、InlineParam 和 Param 的设置,并且构建了对应的 request 对象;
- 代码的第 14-19 行,是测试的主体函数。这段函数的逻辑是这样的:
- 首先,构建 CreateUserAPI 的对象;
- 然后,用 CreateUserAPI 对象的 buildRequest 方法结合输入参数构建 request 对象;
- 接着,通过 request 对象的 request() 方法发起了 API 调用;
- 最后,验证 response 中的状态码是不是 200。
在这段伪代码中,有以下几点需要你特别注意:
- 代码中“CreateUserAPI 的父类 RestAPI”“_buildRequest() 方法”“request() 方法”“addInlineParam() 方法”等,都是由 API 测试框架提供的。
- 为了简化代码,这里并没有引入数据驱动的 data provider。但在实际项目中,代码第 14 行的测试输入参数,往往来自于 data provider,即由数据驱动的方式提供测试输入数据。
- 由于测试过程完全由代码实现,所以可以很方便的在测试执行前后增加任意的额外步骤。比如,需要在 CreateUser 前增加数据创建的步骤时,只需要在代码第 15 行前直接添加就可以了。
- 这里的例子只有一个 API 调用,当需要多个 API 顺序调用时,直接扩展 testCreateUser 方法即可,两个 API 之间的数据传递可以通过上一个 API 返回的 response.XXXX 完成。
通过这段伪代码,我们可以看到,虽然基于代码的 API 测试灵活性很好,也可以很方便地和 CI/CD 集成,但是也引入了一些新的问题,比如:
- 对于单个 API 测试的场景,工作量相比 Postman 要大得多;
- 对于单个 API 测试的场景,无法直接重用 Postman 里面已经积累的 Collection。
在实际工程中,这两个问题非常重要,而且必须要解决。因为公司管理层肯定无法接受相同工作的工作量直线上升,同时原本已经完成的部分无法继续使用,所以自动化生成 API 测试代码的技术也就应运而生了。
自动生成 API 测试代码
自动生成 API 测试代码是指,基于 Postman 的 Collection 生成基于代码的 API 测试用例。
其实,在上一篇文章《从 0 到 1:API 测试怎么做?常用 API 测试工具简介》最后的部分,我已经提到过 Postman 工具本身已经支持将 Collection 转化成测试代码,但如果直接使用这个功能的话,还有两个问题需要解决:
- 测试中的断言(assert)部分不会生成代码,也就是说测试代码的生成只支持发起 request 的部分,而不会自动生成测试验证点的代码;
- 很多中大型互联网企业都是使用自己开发的 API 测试框架,那么测试代码的实现就会和自研 API 测试框架绑定在一起,显然 Postman 并不支持这类代码的自动生成。
鉴于以上两点,理想的做法是自己实现一个代码生成工具,这个工具的输入是 Postman 中 Collection 的 JSON 文件,输出是基于自研 API 框架的测试代码,而且同时会把测试的断言一并转化为代码。
这个小工具实现起来并不复杂,其本质就是解析 Collection JSON 文件的各个部分,然后根据自研 API 框架的代码模板实现变量替换。 具体来讲,实现过程大致可以分为以下三步:
- 首先,根据自研 API 框架的代码结构建立一个带有变量占位符的模板文件;
- 然后,通过 JSON 解析程序,按照 Collection JSON 文件的格式定义去提取 header、method 等信息;
- 最后,用提取得到的具体值替换之前模板文件中的变量占位符,这样就得到了可执行的自研框架的 API 测试用例代码。
有了这个工具后,我建议你的工作模式(Working Model)可以转换成这样:
- 对于 Postman 中已经累积的 Collection,全部由这个工具统一转换成基于代码的 API 测试用例;
- 开发人员继续使用 Postman 执行基本的测试,并将所有测试用例保存成 Collection,后续统一由工具转换成基于代码的 API 测试用例;
- 对于复杂测试场景(比如,顺序调用多个 API 的测试),可以组装由工具转换得到的 API 测试用例代码,完成测试工作。
如图 2 所示,就是一个组装多个由工具转换得到的 API 测试用例代码的例子。其中,代码第 3 行的类“CreateUserAPI”和第 10 行的类“BindCreditCardAPI”的具体代码就可以通过工具转换得到。
图 2 多个 API 顺序调用的测试用例代码
至此,基于代码的 API 测试发展得算是比较成熟了,但在实际应用过程中还有一个痛点一直未被解决,那就是测试验证中的断言,也是我接下来要和你一起讨论的话题。
Response 结果发生变化时的自动识别
在实际的工程项目中,开发了大量的基于代码的 API 测试用例后,你会发现一个让人很纠结的问题:到底应该验证 API 返回结果中的哪些字段?
因为你不可能对返回结果中的每一个字段都写 assert,通常情况下,你只会针对关注的几个字段写 assert,而那些没写 assert 的字段也就无法被关注了。
但对 API 测试来说,有一个很重要的概念是后向兼容性(backward compatibility)。API 的后向兼容性是指,发布的新 API 版本应该能够兼容老版本的 API。
后向兼容性除了要求 API 的调用参数不能发生变化外,还要求不能删减或者修改返回的 response 中的字段。因为这些返回的 response 会被下游的代码使用,如果字段被删减、改名或者字段值发生了非预期的变化,那么下游的代码就可能因为无法找到原本的字段,或者因为字段值的变化而发生问题,从而破坏 API 的后向兼容性。
所以,我们迫切需要找到一个方法,既可以不对所有的 response 字段都去写 assert,又可以监测到 response 的结构以及没有写 assert 的字段值的变化。
在这样的背景下,诞生了“Response 结果变化时的自动识别”技术。也就是说,即使我们没有针对每个 response 字段都去写 assert,我们仍然可以识别出哪些 response 字段发生了变化。
具体实现的思路是,在 API 测试框架里引入一个内建数据库,推荐采用非关系型数据库(比如 MongoDB),然后用这个数据库记录每次调用的 request 和 response 的组合,当下次发送相同 request 时,API 测试框架就会自动和上次的 response 做差异检测,对于有变化的字段给出告警。
你可能会说这种做法也有问题,因为有些字段的值每次 API 调用都是不同的,比如 token 值、session ID、时间戳等,这样每次的调用就都会有告警。
但是这个问题很好解决,现在的解决办法是通过规则配置设立一个“白名单列表”,把那些动态值的字段排除在外。
总结
为了让你可以更好地理解今天的 API 测试框架,我从其发展历程的角度进行了分析:
早期的基于 Postman 的 API 测试在面临频繁执行大量测试用例,以及与 CI/CD 流水线整合的问题时,显得心有余而力不足。为此,基于命令行的 API 测试实践,也就是 Postman+Newman,具有很好的灵活性,解决了这两个问题。
但是,Postman+Newman 的测试方案,只能适用于单个 API 调用的简单测试场景,对于连续调用多个 API 并涉及到参数传递问题时,这个方案就变得不那么理想和完美了。随后,API 测试就过渡到了基于代码的 API 测试阶段。
一些小型企业,则往往会选择适合自己业务的成熟 API 测试框架。中大型的互联网企业,一般都会根据自己的业务上下文,在成熟 API 测试框架的基础上封装自己的 API 测试框架,提升测试效率和灵活性。
但是,不管是采用现成的还是自己去开发 API 测试框架,都会遇到测试用例开发效率低下,以及无法直接重用 Postman 中积累的 Collection 的问题,为此我分享了两个比较好用的方法,也就是:自动生成 API 测试代码和 Response 结果变化的自动识别,并给出了这两个方法的实现思路。
希望我分享的这些内容,可以帮你解决在实际测试项目中遇到的问题。
思考题
目前,基于代码的 API 测试框架已经比较成熟了,所以在此基础上又出现了基于配置文件的 API 测试框架,比如典型的 HttpRunner,在此类 API 测试框架的支持下,测试用例本身往往就是纯粹的配置文件了。你是否有接触过这类 API 测试框架,对此又有什么看法呢?
欢迎你给我留言。
文章作者 anonymous
上次更新 2024-04-10