使用 Python nose 组织 HTTP 接口测试

现在前端 Web、移动端 APP 开发基本上是面向接口编程,后端各种 HTTP 接口已经提供好了,大家只要实现逻辑交互和展示就行。那么问题来了,这么多接口如何方便的进行测试呢?根据个人经验,我认为需要解决三个问题:1. HTTP 接口多种多样,测试程序如何统一?2. 测试程序如何规范组织?3. 接口间有上下文依赖,如何解决?

HTTP 接口多种多样,测试程序如何统一?

后端接口可能来自各个系统,GET/POST 协议的、遵循 RESTful 规范的,使用 session 认证、token 认证的,各式各样的接口都存在。但无论怎么变都无法脱离 HTTP 协议。因为组内的技术栈是 Python,这就很容易想到使用 Python 的 requests 库。首先我们使用requests.Session()会话对象,来进行会话保持和 Header、Cookie 等处理。这里我们可以简单封装一个 HttpSession 类,把一些常用操作和设置封装一下:

通过HttpSession()来获取一个requests session对象,后续的 HTTP 请求都通过它发起。requests 提供 get()、post() 等各种方法调用,但这会让测试代码显得不统一,这里我们直接调用底层的 request 方法,该方法几乎提供了发起一个 HTTP 请求需要的所有参数,非常强大。

这样每一个 HTTP 接口的测试都可以通过准备参数发起请求断言结果三板斧来解决。

测试程序如何规范组织?

前面我们已经解决了如何快捷的发起一个 HTTP 请求的问题,这让我们几行代码就可以测试一个接口的返回值。但你会发现新的问题又来了,各种脚本散乱在一堆,返回值解析各种中间变量,各种配置硬编码在代码中,测试结果全靠 print,这时 nose 框架就派上用场了。nose 是 Python 最流行的一个单测框架,提供测试 case 设置标签、测试 case 查找、强大的断言函数,格式化/可视化报告输出等功能。这样就可以像写单测一样写 HTTP 接口测试了。我们可以把一类接口的测试放在一个类里面,甚至可以把基础变量的定义放在基础测试类里面,其它测试类继承这个基类。

HttpTest基类只做了两个工作:1. 创建http会话对象;2. 读取配置文件到类变量config中。配置文件是一个很好的编程实践,这让我们测试程序和数据能够分离,可以通过调用不同的配置文件来测试不同环境的接口,让我们测试程序不光能做线下测试,还能做线上回归和监控,切换成本非常低。

我这里选择 yaml 语法来组织配置信息,yaml 语法风格类似 json,自带基础数据结构,但更加易于书写和阅读,非常适合做配置文件。通过-env=xxx指定配置文件路径(或者 nose.cfg 配置文件中指定),使用 ruamel.yaml 来解析 yaml,转换为 Python 中能直接操作的数据结构。

其实这个测试基类有一定违反编程规范,这些测试准备工作应该定义在setUp方法中。但这样的话测试子类每次都需要调用一下父类的setUp方法,比较麻烦。

另外,接口返回值解析我这里使用的是 json 查询语言:JMESPath,类似 XML 中的 XPath,通过直观的描述语言来表达 json 值的解析,下图是官网的例子,非常强大,此描述语言能帮助我们规范化的解析接口返回的 json,提取我们想要断言的值。

而且此描述语言支持主流编程语言,这里使用的是其对应的 Python库:jmespath.py,接口非常简单:

现在我们一个测试 case 就变成这个样子了:

是不是非常简洁,而且功能非常强大。

上面的 jmespath_custom 模块主要是扩展了 jmespath.py,新增了辅助函数,并自动注入:

接口间有上下文依赖,如何解决?

这里细化的话是两个需求:

1. 我们都知道单测执行时每个 case 间都是相互独立的(每次都重新实例化测试类),每次都会执行 setUp、tearDown,那么如何共享数据呢(比如上面那个 case 获取到的 access_token 怎么给后续的 case 使用)?

这里可以使用一个小技巧:类变量(类变量存储于全局区在整个实例化的对象中是公用的,类变量定义在类中且在函数体之外),可以使用类变量保存一些跨越类实例的全局变量。下面这个例子可以感受下:

输出:

这样获取到的 access_token 可以存储在 self.__class__.access_token 类变量中(HttpTest 测试基类中的 self.__class__.http_session 也是这样)。

2. 如何保证 case 的只需顺序,比如我要先调用获取 AccessToken 的接口之后,其它的 case 通过拿到的 AccessToken 才能够继续执行。依托 nose 生态的强大,我们可以通过 nose-dep 插件来实现这个需求。

这样我们的测试 case 优化成了:

运行结果:

基于此理论(思路),实现了一个简洁、实用的轻量级 http 接口测试框架(https://github.com/iyaozhen/py-http-test-framework),实际应用效果还不错,希望能帮助到有需要的同学。

参考资料:

pyresttest, https://github.com/svanoort/pyresttest

知识共享许可协议

说点什么

avatar
wpDiscuz