跳转至

23 知其然知其所以然:聊聊API自动化测试框架的前世今生

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

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

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

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

早期的基于Postman的API测试

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

  1. 当需要频繁执行大量的测试用例时,基于界面的API测试就显得有些笨拙;
  2. 基于界面操作的测试难以与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测试框架,在使用上有很多优点,而且灵活性也很好,主要体现在以下几个方面:

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

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

图1 基于代码的API测试的伪代码示例

  • 代码的第1-12行,创建了CreateUserAPI类,其中包含了endpoint、操作方法PUT、InlineParam和Param的设置,并且构建了对应的request对象;
  • 代码的第14-19行,是测试的主体函数。这段函数的逻辑是这样的:

  • 首先,构建CreateUserAPI的对象;

  • 然后,用CreateUserAPI对象的buildRequest方法结合输入参数构建request对象;
  • 接着,通过request对象的request()方法发起了API调用;
  • 最后,验证response中的状态码是不是200。

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

  1. 代码中“CreateUserAPI的父类RestAPI”“_buildRequest()方法”“request()方法”“addInlineParam()方法”等,都是由API测试框架提供的。
  2. 为了简化代码,这里并没有引入数据驱动的data provider。但在实际项目中,代码第14行的测试输入参数,往往来自于data provider,即由数据驱动的方式提供测试输入数据。
  3. 由于测试过程完全由代码实现,所以可以很方便的在测试执行前后增加任意的额外步骤。比如,需要在CreateUser前增加数据创建的步骤时,只需要在代码第15行前直接添加就可以了。
  4. 这里的例子只有一个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转化成测试代码,但如果直接使用这个功能的话,还有两个问题需要解决:

  1. 测试中的断言(assert)部分不会生成代码,也就是说测试代码的生成只支持发起request的部分,而不会自动生成测试验证点的代码;
  2. 很多中大型互联网企业都是使用自己开发的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测试框架,对此又有什么看法呢?

欢迎你给我留言。

精选留言(15)
  • Martin 龚平 👍(77) 💬(3)

    用postman转python或者java测试脚本还是太慢了,而且需要一定编程技能,感觉已经是上一代了,我现在根据httprunner的yml的脚本规则,加上一些开源的组件,做了一个web页面可以进行代理抓包,测试人员无论从web页面还是app操作只要设置代理过来,就可以看到自己的所有请求,然后选择想自动化的请求,后台自动转成测试脚本,再在管理界面上通过拖拽等性质组装成自动化测试集,并可以执行调试、定时任务等。这样的自动化程度基本对编程技能降到了最低,而且生成测试脚本的成本比上一代低了很多

    2018-09-07

  • Cynthia🌸 👍(9) 💬(2)

    感觉本篇对我最有用的,就是Response结果发生变化时的自动识别这块啊!自己在项目中遇到过类似的问题,虽然采取了一定的措施,但是没有想到作者这个解决方案。准备顺着作者的思路捋一捋,看看把他具体实现一下。

    2018-08-21

  • 口水窝 👍(4) 💬(1)

    以前有用过locust,但不知道什么原理,就知道能压测,今天查了下可以和httprunner完美组合,先记下来,后面实践。

    2019-04-12

  • 静静张 👍(4) 💬(1)

    老师你好,基于配置文件的api测试,和将数据外化到文件是一回事吗?

    2018-08-25

  • zhangliqun 👍(2) 💬(1)

    老师,接口自动化测试我看过robotframeworker接口自动化框架,老师RF框架和您讲的有差异呢?

    2018-12-26

  • Yaoqi😀 👍(0) 💬(1)

    loadrunner怎么没有提及,也可以做api啊

    2018-08-22

  • 楚耳 👍(20) 💬(2)

    我觉得老师对api的测试都是基于单个api的测试,我是做P2P这块的,公司的api执行后都会去操作数据库,操作各种表,api调用后,需要验证的是各表的字段是否修改正确,这个才是我们验证的重点,我想如果业务复杂的api基本都是跟数据库关联很大,Response的返回值验证都是次要的了。所以我觉得老师这个api测试的讲解不够深入

    2019-03-22

  • yudi5158 👍(12) 💬(0)

    response结果变化的自动识别,我目前项目使用了大量的json schema验证 效果还不错

    2019-03-30

  • 不错的葱 👍(12) 💬(1)

    老师请教两个问题: 1.做api测试时,是否有必要对数据库做验证?比如测试添加接口,添加成功后是否需要逐一验证数据库字段是否正确;再比如,测试获取详情接口,是否需要把接口的返回和数据库中数据做对比验证。 2.引入数据驱动后,对于不同的输入,验证的步骤相差很大。比如:可能有的输入直接验证responseCode即可,而有的输入需要验证更多字段。这种情况应该如何处理呢?由于这个问题,我这里没有使用数据驱动,而是不同的数据写了不同的用例。

    2018-12-19

  • sylan215 👍(8) 💬(0)

    确实,Postman 作为自测工具或者开发之间的接口对接,还是很方便的,但是涉及批量每日回归执行,或者同一个接口的多参数验证等,稍微有点不方便,这应该也是那么的代码级 API 测试框架出现的原因吧。 相对于自动生成 API 测试代码,我更倾向于配置文件的方式,毕竟通过工具把 JSON 转换为自己需要的格式,说到底也是配置文件而已,只是还要增加一个转换的过程。 Response 结果发生变化时的自动识别,这个很赞,也给我提供了一些思路,目前我都是只校验不变的部分,这样会有覆盖不全的情况,后面我也会考虑这种自适应的实现。 以上,欢迎沟通交流,公众号「sylan215」

    2018-08-20

  • johnny 👍(5) 💬(0)

    老师将的逻辑结构严谨、清晰,能够由简入繁,既有实操,又有解决思路,赞。

    2019-01-17

  • 颜瑞 👍(4) 💬(0)

    在框架的选择上,茹老师也提了一个开放性问题,Rest-Assured和HttpRunner哪个更合适。其实我也一直在犹豫,自己封装接口,对各个产品线上测试人员有编码的能力要求,另外框架的开发还是需要一定工作量的,HttpRunner少了这些编码成本。HttpRunner还没开始用,疑惑点在于数据准备和环境清理能否在这个框架里面实现,考虑到一些数据准备需要插表,是不是从扩展性和兼容性上讲,企业还是自己开发一套API测试框架的好?茹老师有什么建议么?

    2018-10-24

  • 向日葵盛開の夏天 👍(3) 💬(0)

    使用过robotframework进行API测试,一个API测试都需要填写较长的表单,用起来感觉很复杂,冗余,在公司内部也不好做推广。目前,我还是比较喜欢用fiddler+jmeter,感觉使用起来还是蛮方便的,jmeter还可以集成到Jenkins下。对于老师说的自己封装API测试框架,很可惜,待的公司都项目较多的有很多的接口,自己封装API测试工程量太大,都没有实施的。

    2018-08-20

  • 楚耳 👍(2) 💬(1)

    老师 能具体讲下在框架中如何实现 可以灵活支持多个 API 的顺序调用,方便数据在多个 API 之间传递 ,即上一个 API 调用返回结果中的某个字段值可以作为后续 API 调用的输入参数 这个需求吗 讲下设计思路

    2018-09-13

  • Gavin 👍(1) 💬(2)

    现在的测试人员对编码能力有一定要求了,不会代码做起来真的有些吃力。 举个很简单的例子,懂Java代码在看Jmeter时使用BeanShell可以直接使用脚本,去获取请求中的参数并传递给下个请求使用,自己写不出来只能通过正则表达式或者JSON提取器,把值取到再在下个请求中引用,效率上感觉就差了一截...

    2020-04-20