05 | 案例:测试框架如何才能支持RESTful风格的接口?

你好,我是陈磊。

在前面的课程中,我们一起学习了如何把流程化的测试脚本,一步一步抽象成你自己的测试框架。无论你用的是什么编程语言,封装和抽象自己的测试框架都会让你的接口测试任务事半功倍。

我相信你在平时生活或工作中,一定会接触到各式各样的软件系统,而现在的软件系统和5年前相比,最大差别就在于结构不同。

在我读大学的时候,绝大部分系统还都是用一个Tomcat来搞定的;但现在的系统更加复杂,它们已经无法只用一个Web中间件独立对外提供服务,它们之间都也是通过相互调用来完成业务逻辑的,这里面既包含了服务端和服务端的调用,也包含了前端和服务端的调用,这就催生了RESTful风格的HTTP接口。

所以,这节课我就来和你讲讲,如何让你的测试框架完美支持RESTful风格的接口测试。这里我希望你能不断强化封装测试框架的三个流程,不断为自己的接口测试框架添砖加瓦。

不过,我不会将RESTful的规则一条一条念给你听,我想让你知道的重点是作为测试工程师,你要学会从测试工程师的角度观察RESTful接口,要学会怎么分析和验证这类接口,这也是今天我们今天这节课的主要内容。

RESTful风格接口关我什么事?

看到这里,你是不是一脸困惑:RESTful是一个接口的封装风格,和我们测试人员有什么关系呢?

要想理解它和我们测试工程师的关系,你就要先知道RESTful风格的接口到底有什么好。

如果你用螺丝、钉子和板材等一系列原材料组装过家具,那么你肯定见到过各种千奇百怪的螺丝,比如一字的、十字的、三角形的、六角形的和海星形的等等,为了加固这些各式各样的螺丝,你就要准备各式各样的螺丝刀,因此,你的工具箱就会被不同规格和大小的螺丝刀填满。

不知道你是不是也和我一样,面对塞满螺丝刀的、乱七八糟的工具箱,心里非常急躁。但后来我在宜家看到一款螺丝刀,它只有一个刀柄,但给你提供了一整套各种形状、各种大小的螺丝刀刀头。

这样你在使用时,只要根据螺丝规格的不同,选择替换同形状的刀头就可以了;与此同时,它们放在工具箱里面又会显得很整齐,而不会七零八落。而且,如果你后续需要使用其它特殊形状的螺丝刀,你只要买和刀柄连接口一样的刀头就可以了,而不用再买一个完整的螺丝刀了。

如果你理解了上面这个场景,也就能很好地理解RESTful风格的接口了。它主要就是一组设计原则和约束条件,本质上就是让消费者依据URI就可以找到资源,并通过简单的服务输入输出完成服务的交互。

它所约束的每一个URI,都是独一无二的一个资源,通过HTTP的方法进行资源操作,实现表现层的状态转化。这就和螺丝刀刀头一样,待解决的问题就像螺丝,每一个接口只面向一种特定的资源,而不需要关心其他接口的处理方式,这样,你就能够一目了然地知道,该用哪种螺丝刀头拧哪种螺丝了,这就降低了接口开发的复杂度。

软件开发人员只要遵循RESTful的实践标准,按照一定的内部定义开发外接口,就会形成类似螺丝刀刀头一样轻便的接口,对外提供服务。现在的很多项目,无论是服务端和服务端的调用,还是前端和服务端的调用,都采用了这一种方式来设计接口。

对于我们测试工程师来说,RESTful风格的接口还是之前的访问模式,它同样是一种HTTP协议的接口,同样可以使用我们上节课一起封装的框架完成接口测试的任务。

但是,它和我们之前讲过的HTTP协议的接口测试还是有一些区别,这些区别导致了你现在的框架还需要做一些修改,这样才能让它支持RESTful风格的接口测试。

让你的框架可以测试一个RESTful风格接口

现在,你知道RESTful接口和你的接口测试有很大关系了,那么,RESTful接口的测试和原始的HTTP协议接口的测试,又有什么区别呢?

这里面有两部分需要你特别关注:数据交换的承载方式和操作方式

我先说说数据交换的承载方式,RESTful风格的接口主要是以JSON格式来进行数据交换,如果你还记得我之前提过的“Battle”这个系统(你可以回到03中查看它),那么你一定在它的Readme.md中,看到了Request和Response对数据部分的一些定义,那就是JSON。虽然“战场”不能算是一个严格的RESTful接口的系统,但是,在数据交接的承载方式上,它模仿了RESTful的样子。

另外一个部分是操作方式,在“战场”系统中,我们用了HTTP协议的Get和Post,其实HTTP协议有很多方法,但是我们仅仅用了这两种,而RESTful的规定,使HTTP的很多方法都被利用到了,比如说,Get方法用来获取资源,Post方法用来新建资源(或者更新资源);再比如说,Put方法用来更新资源、Delete方法用来删除资源等等。

在明白RESTful风格的接口和普通的HTTP协议接口的区别后,我们现在来想一想,你自己的框架需要添加什么内容,才能支持RESTful风格的接口呢?这里我总结了两个方法:借助外力和自己封装。

借助外力

这里,我们RESTful的第一个数据交换的承载方式是JSON,我们的框架在之前的“战场”系统中就已经使用了它,虽然全部的操作都是参数拼凑的过程,但这已经满足了我们的需求。

这时如果你仍要拼凑很多复杂的数据,就需要使用JSON字符串和代码对象实体的转换,它有一个专业的叫法:序列化和反序列化。这个词语听着就很难理解,所以现在,我用一个生活中的小例子来告诉你,这个晦涩难懂的概念到底是什么意思。

如果你在商场看中了一款衣柜,但它很大,为了方便运输,就必须要先把它拆掉,运到家后再重新组装。你和商家协商好了,由他们为你把这个衣柜拆成可重组的零件运到家里,然后由你自己把这些零件重新组装成一个衣柜。

那么在这里,商家把衣柜拆成各个零件、然后打包的这个过程就是“序列化”,在代码中,就是将一些程序对象转换成JSON等格式的字符串的过程。接下来,你用这些零件再重新组装成一个衣柜,这个过程就是“反序列化”,在代码中,就是JSON等格式的字符串转换成程序的对象的过程。

为了能让你的框架可以快速完成序列化和反序列化,我建议你在代码中引入一个外部支持的库,就像Python有JSON库、Java有Fastjson库。这些公开的库其实都不需要做任何的修改,就可以拿来使用,所以,无论你使用哪种技术栈,这样的基础库都是存在的,你只需要在网上找一下,然后花几分钟看一下怎么使用,就可以拿到自己的框架里使用了。

自己封装

现在,我们已经可以借助开源库,解决数据交换的事情了,但是,RESTful风格接口和普通HTTP接口相比,还有一个明显的区别,那就是RESTful规定了HTTP的每一个方法都做固定的事情,可我们原来框架中的Common类却只支持Get和Post方法,因此,你需要在Common类中加入Delete和Put方法的支持。具体的操作你可以依据下面这个代码段来完成:

  def put(self,uri,params=None):
    '''
    封装你自己的put方法,uri是访问路由,params是put请求需要传递的参数,如果没有参数这里为空
    :param uri: 访问路由    
    :param params: 传递参数,string类型,默认为None    
    :return: 此次访问的response
    '''    
    url = self.url_root+uri
    if params is not None:
      # 如果有参数,那么通过put方式访问对应的url,并将参数赋值给requests.put默认参数data 
      # 返回request的Response结果,类型为requests的Response类型
      res = requests.put(url, data=params)
   else:
  
      # 如果无参数,访问方式如下
      
      # 返回request的Response结果,类型为requests的Response类型
      
      res = requests.put(url)
  
   return res


def delete(self,uri,params=None):
  '''
  封装你自己的delete方法,uri是访问路由,params是delete请求需要传递的参数,如果没有参数这里为空
  :param uri: 访问路由
  :param params: 传递参数,string类型,默认为None
  :return: 此次访问的response
  '''
  url = self.url_root + uri
  if params is not None:
    # 如果有参数,那么通过delete方式访问对应的url,并将参数赋值给requests.delete默认参数data
    # 返回request的Response结果,类型为requests的Response类型
    res = requests.delete(url, data=params)
  else:
    # 如果无参数,访问方式如下
    # 返回request的Response结果,类型为requests的Response类型
    res = requests.delete(url)
  return res

在上面的代码中,你可以看到,我们为了实现HTTP协议的Put和Delete方法,自己封装了put()函数和delete()函数。其实,要实现RESTful风格的接口测试,你只要封装HTTP协议对应的Method方法就可以了,这样,你的框架就能完美的支持RESTful风格的接口了。完成了这个操作后,我们的Common类就既可以完成HTTP协议接口的测试,也可以完成RESTful接口的测试了。

总结

到这里,我们已经结束了今天的课程了。我们今天主要完成了RESTful风格接口的测试,对比之前的例子以及你自己的测试框架,针对框架中RESTful里缺失的部分,我为你提供了对应的解决方法。

在文中我讲了很多内容,但是完成RESTful风格接口测试,主要是通过两步操作,来为你的测试框架添加对应接口的测试能力的:

  1. 借助外力。目前网上已经有很多成熟的、各式各样的支持库,你要尽量拿来为己所用,而不要从零建设,这样,既弥补了我们开发能力不强的短板,也能提高我们的研发效率。
  2. 自己封装。你要注意的是,自己封装和借助外力并不互相冲突,你要借助外力,然后将它封装到你自己的框架中,这是一个借力打力的好方法。

随着我们的课程的不断深入以及内容的不断丰富,我相信,你最终会获得一个完全适合你自己,又可以解决实际工作任务的测试框架,这也是你自己的接口测试武器仓库,里面有解决各种接口测试问题的方法。它会是一个私有仓库,里面每一个武器都是为你自己量身定制的,因此,每一件武器你用起来都会更得心应手。

思考题

我今天讲了RESTful接口测试,并为你的私有测试框架添加了各式各样的新武器,那么,你能用你现在的新武器,解决一个你负责的RESTful的接口测试吗?在今天的框架中,随着你实际工作的使用,你又有了什么样的新设计呢?

我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起探讨和学习。

精选留言

  • 2020-02-11 20:52:40

    万能螺丝刀柄比喻restful接口,家具拆装必须序列化和反序列化,很好理解。
    作者回复

    谢谢支持

    2020-02-11 21:22:38

  • Leo

    2020-02-18 13:04:37

    老师,有没有推荐的测试平台,支持web方式展示测试用例,用例的执行是调用背后开发的代码,支持定义测试集,支持多个测试环境,生成测试报告,提供restful接口集成CI流水线等?
    作者回复

    您好,对目前成熟的平台类如Yapi,httprunner我都是看看实现思路并没有实际应用过,如果你感兴趣可以就上面两个平台进行对比选择。

    2020-02-18 20:38:48

  • AllWin

    2020-02-16 15:44:06

    银行业中有些单个接口就有几百个参数,这几百个参数也不是都有联系的,会按场景分成很多参数组合,请问这样的设计是什么风格,我不理解银行为什么会有这样高复杂度的接口设计
    作者回复

    银行的很多技术问题都是由于长时间维护跨越多种技术栈儿导致的结果,我曾经也遇见过,没风格可讲,但是目前看了存在即合理了。只能随着时间的推移慢慢走向优化

    2020-02-16 21:49:50

  • 沛野

    2020-02-15 01:16:22

    RESTful 风格的 HTTP 接口是什么意思呀? 还有什么其他的接口么?这个是按什么分类的呀
    作者回复

    RESTful是一种设计风格,目前最流行的就只有这一种了,这不是一种分类方法,是一种实践风格,因此它是指让混乱的HTTP接口设计更加的成熟、优越。目前应该您只关注这一种就可以了。谢谢您

    2020-02-15 20:43:42

  • 陈磊@Criss

    2020-02-12 17:26:58

    序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程。
    作者回复

    就是为了方便程序处理和方便网络传输而做的两种互斥的转换。

    2020-02-12 21:47:50

  • Watermelon

    2021-01-05 15:05:01

    还是不懂RESTful风格接口跟普通HTTP接口有什么区别。。。传输数据的话,普通接口也用json传啊
  • 2020-02-26 09:57:34

    过,迭代抽象封装。
  • IT小村

    2022-05-15 16:06:57

    restful其实就是更好区分接口方法的用途
  • 捷后愚生

    2020-11-28 14:11:55

    RESTful 风格 HTTP 接口的产生
    简单系统可以使用单体结构,用一个 Tomcat 来做中间件就可搞定。随着系统急剧扩大,已经无法只用一个 Web 中间件独立对外提供服务,它们之间都也是通过相互调用来完成业务逻辑的,这里面既包含了服务端和服务端的调用,也包含了前端和服务端的调用,这就催生了 RESTful 风格的 HTTP 接口。


    RESTful 风格 HTTP 接口的特点
    每一个接口只面向一种特定的资源,而不需要关心其他接口的处理方式,接口简单、轻便。
    ESTful 风格接口同样是一种 HTTP 协议的接口,而主要是以 JSON 格式来进行数据交换,且不只会使用get、post方法,还会使用put、detele方法。


    封装可以测试RESTful风格接口地测试框架
    引入JSON库,拼凑参数已经无法满足需求,需要使用 JSON 字符串和代码对象实体的转换,也就是序列化和反序列化。
    加入 Delete 和 Put 方法的支持。
  • Chaos

    2022-04-03 16:01:35

    https://www.xinzhiweike.com/wenda/1630979493140646
  • 小昭

    2021-03-10 10:55:17

    百度了一下RESTful风格HTTP协议接口的详细介绍,发现自己之前对HTTP协议的接口的认知就是RESTful风格的……

    老师总能找到生活中的例子来讲一些晦涩的概念,厉害厉害
    作者回复

    谢谢

    2021-03-25 22:05:39

  • -_-

    2020-07-30 12:33:28

    使用json格式的数据,post请求的参数要改为json=xxx吗,还是data=json格式的数据也可。
    如果是用json=是要再写一个post方法吧
    作者回复

    data=是对的

    2020-08-12 22:22:20

  • 小老鼠

    2020-04-19 21:44:17

    RestFul中的delete、put.…等用的就是http协议中的delete、put⋯方法吧?
    作者回复

    是的

    2020-06-14 23:23:32

  • 彦鋆

    2020-04-12 13:03:38

    老师,rest风格的接口我们在设计测试用例和断言的时候预期返回要和接口返回的内容完全一样还是判断返回值中的某些重要的key-value一样就行了?
    作者回复

    这个要看业务要求了,不过绝大部分情况我们更加推崇关键value做断言

    2020-04-13 20:24:04

  • 深瞳

    2020-04-01 19:19:22

    思路有了,但是需要填充的内容还需要大量的学习
    作者回复

    加油,每一次努力都能得到更多的回报

    2020-04-01 21:56:06

  • Past

    2020-03-25 18:30:45

    原文:商家把衣柜拆成各个零件、然后打包的这个过程就是“序列化”,在代码中,就是将一些程序对象转换成 JSON 等格式的字符串的过程。接下来,你用这些零件再重新组装成一个衣柜,这个过程就是“反序列化”,在代码中,就是 JSON 等格式的字符串转换成程序的对象的过程。

    序列化和反序列化的例子挺有意思的,和Modrem调制解调器原理有点类似,调制(数字信号-->模拟信号)和解调原理(模拟信号-->数字信号),同时也让我想起了老师一个搞笑的解释,把整条萝卜切割成小块处理,是处理调制的过程,那么解调原理是再把小块小块的萝卜组合成完整的萝卜,保持原特性。
  • Hy

    2020-02-25 11:14:01

    最后delete函数的注解好像有问题,if params is not None,后面提示用Put方法,另外如果delete方法没参数请问为什么写成了res = requests.put(url),请老师赐教
    作者回复

    您好,谢谢您,这是我在代码调整过程中复制错了代码,我马上修正谢谢

    2020-02-25 14:18:48

  • Map

    2020-02-21 12:00:22

    老师这个框架源码有项目地址可以下载么
    作者回复

    正在整理

    2020-02-21 13:13:41

  • 王德发

    2020-02-18 17:55:40

    老师,这个python的测试框架,怎么做的切换环境,比如说,我现在有三个环境测试、仿真、预发布,不同的环境数据库、redis等这些配置都不一样,如何在测试框架运行的时候能动态切换不同的配置文件呢?比如像java里,可以用maven的插件来做。
    作者回复

    可以通过类似java的处理方法,使用配置区分环境,但是接口测试在生产上几乎不允许直接测试,因此我们课上没有讲解类似区分环境的处理,也可以通过数据层进行区分,这个你可以自行选择一种你喜欢的方式。

    2020-02-18 20:37:22

  • Geek_f644f9

    2020-02-15 10:50:43

    复制代码段存在语法错误,不能识别is not
    作者回复

    is not是python原生支持的,我也不太知道为什么您的ide有问题,您要不尝试修改成!=试一下

    2020-02-15 14:10:07