<audio id="audio" title="23 | 知其然知其所以然：聊聊API自动化测试框架的前世今生" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0b/3a/0bbab498518d7d829fcf11bf28cd883a.mp3"></audio>

你好，我是茹炳晟，今天我和你分享的主题是“知其然知其所以然：聊聊API自动化测试框架的前世今生”。

在上一篇文章中，我以一个简单的Restful API为例，分别介绍了cURL和Postman的使用方法，相信你已经对API测试有个感性认识了。

但是，我们不能仅仅停留在感性认识的层面，还需要熟悉并掌握这些测试方法，完成相应的API测试工作。所以，也就有了我今天分享的主题，希望可以通过对API自动化测试框架发展的介绍，让你理解API测试是如何一步一步地发展成今天的样子，以“知其所以然”的方式加深你对API自动化测试的理解。

接下来，我将会遵循由简入繁的原则，为你介绍API测试框架，以发现问题然后解决问题的思路为主线，展开今天的分享。

## 早期的基于Postman的API测试

早期的API测试，往往都是通过类似Postman的工具完成的。但是，由于这类工具都是基于界面操作的，所以有以下两个问题亟待解决：

<li>
当需要频繁执行大量的测试用例时，基于界面的API测试就显得有些笨拙；
</li>
<li>
基于界面操作的测试难以与CI/CD流水线集成。
</li>

所以，我们迫切需要一套可以基于命令行执行的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测试框架，在使用上有很多优点，而且灵活性也很好，主要体现在以下几个方面：

<li>
可以灵活支持多个API的顺序调用，方便数据在多个API之间传递，即上一个API调用返回结果中的某个字段值可以作为后续API调用的输入参数；
</li>
<li>
方便在API调用之前或者之后执行额外的任意操作，可以在调用前执行数据准备操作，可以在调用后执行现场清理工作等；
</li>
<li>
可以很方便地支持数据驱动测试，这里的数据驱动测试概念和GUI测试中的数据驱动测试完全相同，也就是可以将测试数据和测试代码分离解耦；
</li>
<li>
由于直接采用了代码实现，所以可以更灵活地处理测试验证的断言（Assert）；
</li>
<li>
原生支持命令行的测试执行方式，可以方便地和CI/CD工具做集成。
</li>

这里我给出了一段伪代码示例，用于展示如何用代码实现一个简单的API测试。

<img src="https://static001.geekbang.org/resource/image/be/6f/be4e9d0cf1442e89831629f3f5727c6f.png" alt="" />

<li>
代码的第1-12行，创建了CreateUserAPI类，其中包含了endpoint、操作方法PUT、InlineParam和Param的设置，并且构建了对应的request对象；
</li>
<li>
代码的第14-19行，是测试的主体函数。这段函数的逻辑是这样的：
<ul>
- 首先，构建CreateUserAPI的对象；
- 然后，用CreateUserAPI对象的buildRequest方法结合输入参数构建request对象；
- 接着，通过request对象的request()方法发起了API调用；
- 最后，验证response中的状态码是不是200。

在这段伪代码中，有以下几点需要你特别注意：

<li>
代码中“CreateUserAPI的父类RestAPI”“_buildRequest()方法”“request()方法”“addInlineParam()方法”等，都是由API测试框架提供的。
</li>
<li>
为了简化代码，这里并没有引入数据驱动的data provider。但在实际项目中，代码第14行的测试输入参数，往往来自于data provider，即由数据驱动的方式提供测试输入数据。
</li>
<li>
由于测试过程完全由代码实现，所以可以很方便的在测试执行前后增加任意的额外步骤。比如，需要在CreateUser前增加数据创建的步骤时，只需要在代码第15行前直接添加就可以了。
</li>
<li>
这里的例子只有一个API调用，当需要多个API顺序调用时，直接扩展testCreateUser方法即可，两个API之间的数据传递可以通过上一个API返回的response.XXXX完成。
</li>

通过这段伪代码，我们可以看到，虽然基于代码的API测试灵活性很好，也可以很方便地和CI/CD集成，但是也引入了一些新的问题，比如：

- 对于单个API测试的场景，工作量相比Postman要大得多；
- 对于单个API测试的场景，无法直接重用Postman里面已经积累的Collection。

在实际工程中，这两个问题非常重要，而且必须要解决。因为公司管理层肯定无法接受相同工作的工作量直线上升，同时原本已经完成的部分无法继续使用，所以自动化生成API测试代码的技术也就应运而生了。

## 自动生成API测试代码

自动生成API测试代码是指，基于Postman的Collection生成基于代码的API测试用例。

其实，在上一篇文章[《从0到1：API测试怎么做？常用API测试工具简介》](https://time.geekbang.org/column/article/13421)最后的部分，我已经提到过Postman工具本身已经支持将Collection转化成测试代码，但如果直接使用这个功能的话，还有两个问题需要解决：

<li>
测试中的断言（assert）部分不会生成代码，也就是说测试代码的生成只支持发起request的部分，而不会自动生成测试验证点的代码；
</li>
<li>
很多中大型互联网企业都是使用自己开发的API测试框架，那么测试代码的实现就会和自研API测试框架绑定在一起，显然Postman并不支持这类代码的自动生成。
</li>

鉴于以上两点，理想的做法是自己实现一个代码生成工具，这个工具的输入是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”的具体代码就可以通过工具转换得到。

<img src="https://static001.geekbang.org/resource/image/8c/e9/8cc97f6e1459aac8aedbb14ab8bec4e9.png" alt="" />

至此，基于代码的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测试框架，对此又有什么看法呢？

欢迎你给我留言。


