现在前端 Web、移动端 APP 开发基本上是面向接口编程,后端各种 HTTP 接口已经提供好了,大家只要实现逻辑交互和展示就行。那么问题来了,这么多接口如何方便的进行测试呢?根据个人经验,我认为需要解决三个问题:1. HTTP 接口多种多样,测试程序如何统一?2. 测试程序如何规范组织?3. 接口间有上下文依赖,如何解决?
HTTP 接口多种多样,测试程序如何统一?
后端接口可能来自各个系统,GET/POST 协议的、遵循 RESTful 规范的,使用 session 认证、token 认证的,各式各样的接口都存在。但无论怎么变都无法脱离 HTTP 协议。因为组内的技术栈是 Python,这就很容易想到使用 Python 的 requests 库。首先我们使用requests.Session()
会话对象,来进行会话保持和 Header、Cookie 等处理。这里我们可以简单封装一个 HttpSession 类,把一些常用操作和设置封装一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #!/usr/bin/env python # -*- coding: utf-8 -*- import requests # 屏蔽https安全警告 # https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings from requests.packages.urllib3.exceptions import InsecurePlatformWarning from requests.packages.urllib3.exceptions import InsecureRequestWarning from requests.packages.urllib3.exceptions import SNIMissingWarning requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(SNIMissingWarning) class HttpSession(requests.Session): """ http请求会话 """ def __init__(self): super(HttpSession, self).__init__() # 不校验SSL证书 self.verify = False # 超时时间 self.timeout = (60, 600) def update_cookies(self, cookies): """ 更新当前会话cookie :param cookies: cookie字典 """ requests.utils.add_dict_to_cookiejar(self.cookies, cookies) def update_headers(self, headers): """ 更新当前会话的header :param headers: header字典 """ self.headers.update(headers) |
通过HttpSession()
来获取一个requests session对象,后续的 HTTP 请求都通过它发起。requests 提供 get()、post() 等各种方法调用,但这会让测试代码显得不统一,这里我们直接调用底层的 request 方法,该方法几乎提供了发起一个 HTTP 请求需要的所有参数,非常强大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | http_session = HttpSession() def test_get_access_token(self): """ 获取access_token """ params = { "corpid": 'corp_id', "corpsecret": 'corp_secret', } response = http_session.request( 'GET', config['dingtalk_oapi'] + '/gettoken', params=params ).json() data = response['access_token'] print data is not None |
这样每一个 HTTP 接口的测试都可以通过准备参数
、发起请求
、断言结果
三板斧来解决。
测试程序如何规范组织?
前面我们已经解决了如何快捷的发起一个 HTTP 请求的问题,这让我们几行代码就可以测试一个接口的返回值。但你会发现新的问题又来了,各种脚本散乱在一堆,返回值解析各种中间变量,各种配置硬编码在代码中,测试结果全靠 print,这时 nose 框架就派上用场了。nose 是 Python 最流行的一个单测框架,提供测试 case 设置标签、测试 case 查找、强大的断言函数,格式化/可视化报告输出等功能。这样就可以像写单测一样写 HTTP 接口测试了。我们可以把一类接口的测试放在一个类里面,甚至可以把基础变量的定义放在基础测试类里面,其它测试类继承这个基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #!/usr/bin/env python # -*- coding: utf-8 -*- try: import ConfigParser except ImportError: import configparser as ConfigParser import os import sys import unittest import nose.config import ruamel.yaml from py_http_api_test.http_session import HttpSession class HttpTest(unittest.TestCase): """ http接口测试类 """ # case配置信息(执行环境) config = [] # http会话对象 http_session = HttpSession() @classmethod def setUpClass(cls): """ 初始化配置信息和http_session对象 :return: """ env = None nose_cfg = None argvs = sys.argv[1:] for idx, arg in enumerate(argvs): # 直接获取case配置文件路径 if '-env=' in arg: env = arg.split('=')[-1] break # 获取nose的配置文件 if '--config=' in arg: nose_cfg = arg.split('=')[-1] if '-c' == arg: nose_cfg = argvs[idx + 1] # 未获取到case配置文件路径,尝试从nose配置文件中获取 nose_config_files = nose.config.all_config_files() if env is None and (nose_cfg is not None or len(nose_config_files) > 0): if nose_cfg is None: # 用户未指定nose配置文件,则从系统全局配置文件中获取 nose_cfg = nose_config_files[-1] if not os.path.isabs(nose_cfg): nose_cfg = os.getcwd() + '/' + nose_cfg cf = ConfigParser.ConfigParser() cf.read(nose_cfg) try: env = cf.get('others', 'env') except ConfigParser.Error: env = None if env is not None: # 参数不是绝对路径 if not os.path.isabs(env): # 根据当前工作路径获取到绝对路径 env = os.getcwd() + '/' + env with open(env) as f: inp = f.read() cls.config = ruamel.yaml.safe_load(inp) @classmethod def tearDownClass(cls): cls.http_session.close() |
HttpTest基类只做了两个工作:1. 创建http会话对象;2. 读取配置文件到类变量config
中。配置文件是一个很好的编程实践,这让我们测试程序和数据能够分离,可以通过调用不同的配置文件来测试不同环境的接口,让我们测试程序不光能做线下测试,还能做线上回归和监控,切换成本非常低。
我这里选择 yaml 语法来组织配置信息,yaml 语法风格类似 json,自带基础数据结构,但更加易于书写和阅读,非常适合做配置文件。通过-env=xxx
指定配置文件路径(或者 nose.cfg 配置文件中指定),使用 ruamel.yaml 来解析 yaml,转换为 Python 中能直接操作的数据结构。
1 2 3 4 5 6 7 | %YAML 1.2 --- # http://open-dev.dingtalk.com/#/corpAuthInfo dingtalk_oapi: 'https://oapi.dingtalk.com' corpid: 'dingc2ac01025c48fb7635c2f4657eb6378f' corpsecret: 'RTw5Vbsh6uKSOu2G25wOmQjQK6SA2NOHNyfHtlEgzQ-CWmvjoGH3c4a-MWTzaJ6Q' department_parentid: 52711158 |
这一切都放在setUpClass
方法中,在具体测试 case 运行之前就已经准备好这些工作了。 Continue Reading...