HttpRunner 使用小结
摘要: 最近项目要求快速完成接口自动化测试,因时间有限,经过考察后决定使用开箱即用的 httprunner,在使用过程同时也顺便总结了一些较为常见的问题和用法,在此记录一下。
为避免业务信息泄漏,文中某些 api 例子使用 https://httpbin.org/,后续使用过程中如有新的内容,将持续更新此文。
https 请求证书验证
在对 https 接口进行测试时如果请求经过代理则可能会有 certificate verify failed
的报错,如:
ERROR: test_0000_000 (httprunner.api.TestSequense) --------------------------------------------------------------------- Traceback (most recent call last): ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)
原因是 request
模块发送请求函数有个参数 verify
值默认为 True
.
使用 httprunner 则可在用例 yml 或 json 文件中将 config 或 teststep 中该参数设置为 False
跳过证书验证。
- config: ... verify: False - test: ... request: verify: False
官方 changlog 中也描述过修复之后的这两处设置优先级:
2.0.3 (2019-02-24)
Bugfixes
- fix verify priority: teststep > config
代理调试
httprunner 库本身没有提供设置代理的接口,但是底层使用了 urllib.requests 等库,可以设置 HTTP_PROXY 和 HTTPS_PROXY 环境变量,常用的网络库会自动识别这些环境变量,使用变量设置的代理发起请求:
httprunner 本身支持多种环境变量设置与读取方式,用法参考:https://cn.httprunner.org/prepare/dot-env/
e.g. 日常调试使用代理(如 charles 等工具)可在 debugtalk.py 开头加上
import os os.environ['http_proxy'] = 'http://127.0.0.1:8888' os.environ['https_proxy'] = 'https://127.0.0.1:8888'
$ 符引用
在 httprunner 中,$
符被用来引用变量或函数,如果遇到需要将 $
当成一个普通字符来使用时则可以通过 $$
来表示一个普通 $
字符而不是引用。
参考源码 httprunner/parser.py
第 598~603 行:
# content is in string format here if not is_var_or_func_exist(content): # content is neither variable nor function # replace $$ notation with $ and consider it as normal char. # e.g. abc => abc, abc$$def => abc$def, abc$$$$def$$h => abc$$def$h return content.replace("$$", "$")
e.g.
# 在下面的 testcase yaml 文件中,command 变量中的 $$5 则代表 $5 ,否则只写为 $5,解析时则会去找变量名为 5 的变量,如果该变量不存在,则会报错。 - config: name: xxxx - test: name: xxxx api: api/xx/xxxx.yaml variables: command: ifconfig | awk '{print $$5}'
Issue
但在这里,我也遇到一个问题,当我把变量设置放在 - config
中时,如:
- config: name: xxxx variables: command: ifconfig | awk '{print $$5}' - test: name: xxxx api: api/xx/xxxx.yaml
$$
并没有被当成普通的 $
,而是报错变量未找到
File "/Users/mac/application/miniconda/envs/httprunner/lib/python3.7/site-packages/httprunner/parser.py", line 508, in __parse raise exceptions.VariableNotFound(var_name) httprunner.exceptions.VariableNotFound: 5
该问题已向代码库提交 Issue: https://github.com/httprunner/httprunner/issues/657
json 响应中数组的提取和断言
对于包含数组的 Json 响应,如下
# api 地址:https://httpbin.org/#/Response_formats/get_json { "slideshow": { "author": "Yours Truly", "date": "date of publication", "slides": [ { "title": "Wake up to WonderWidgets!", "type": "all" }, { "items": [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets" ], "title": "Overview", "type": "all" } ], "title": "Sample Slide Show" } }
如果需要在 extract 或 validate 中对 "title": "Wake up to WonderWidgets!"
字段进行断言,提取方式可为 content.slideshow.slides.0.title
(其中数字 0 为取数组中的第 1 位,序号以 0 开始):
e.g.
- config: name: testcase description verify: False - test: name: /json request: method: GET url: https://httpbin.org/json extract: title: content.slideshow.slides.0.title validate: - eq: - status_code - 200 - eq: - headers.Content-Type - application/json - eq: - content.slideshow.slides.0.title - 'Wake up to WonderWidgets!'
text/html 响应的提取和断言
对于文本内容和 html 响应需要对关键字进行提取和断言,可使用正则表达式进行匹配查找,有 html 响应内容如下:
<!DOCTYPE html> <html> <head> </head> <body> <h1>Herman Melville - Moby-Dick</h1> <div> <p> Availing himself of the mild, summer-cool weather that now reigned in these latitudes, and in preparation for the peculiarly active pursuits shortly to be anticipated, Perth, the begrimed, blistered old blacksmith, had not removed his portable forge to the hold again, after concluding his contributory work for Ahab's leg, but still retained it on deck, fast lashed to ringbolts by the foremast; </p> </div> </body>
需要对文中关键字 summer-cool
进行断言,则可以使用正则表达式分组的方式对单词进行提取:
- config: name: testcase description verify: False - test: name: /html request: method: GET url: https://httpbin.org/html extract: key: Availing himself of the mild, (.+) weather that now reigned in these latitudes validate: - eq: - status_code - 200 - eq: - headers.Content-Type - text/html; charset=utf-8 - eq: - $key - summer-cool
这个方法原理是通过正则分组,默认提取第一个分组匹配到的内容,用法类似于 loadrunner 的左右边界提取。
testcase 之间传递参数
使用 output/export 和 extract 可以在 testcase 之间传递参数
testcase 级的变量:
- config 中设置的 variables
- teststep 中 extract 提取的
可以在 testcase 的 - config 中通过 output 或者 export (2.2.2 版本添加) 将其暴露,然后在该 testcase 被引用时通过 extract 将变量提取出来使用。
- config: name: testcase description verify: False variables: configVar: configVar export: - key - configVar - teststepVar - test: name: /html request: method: GET url: https://httpbin.org/html variables: teststepVar: teststepVar extract: key: Availing himself of the mild, (.+) weather that now reigned in these latitudes validate: - eq: - status_code - 200 - eq: - headers.Content-Type - text/html; charset=utf-8
执行结果
# hrun output message WARNING variable 'teststepVar' can not be found in variables mapping, failed to export! INFO ==================== Output ==================== Variable : Value ---------------- : ----------------------------- key : summer-cool configVar : configVar ------------------------------------------------
在 teststep 中引用以上 testcase:
- config: name: testcase description verify: False - test: name: /html testcase: testcases/html.yaml extract: - key - configVar validate: - eq: - $key - summer-cool - test: name: /json request: method: GET url: https://httpbin.org/json validate: - eq: - $configVar - configVar
2.2.2 版本 CHANGELOG 中对此处用法做了相关说明:
2.2.2 (2019-06-26)
Changed
extract
is used to replaceoutput
when passing former teststep’s (as a testcase) export value to next teststepexport
is used to replaceoutput
in testcase config
复用 cookies 和 token
很多时候测试 api ,我们并不希望频繁登录获得授权,这个时候就需要复用 cookies 和 token 。
1. 每个 testcase 登录一次
在 httprunner 中如果不同 api 是在同一个 testcase 的不同步骤,那么只需要在一个 teststep 登录授权,则其他的 teststep 是可以共用授权,推测应该是 testcase 里共用同一个 requests.seesion 。
2. 将 cookies 或 token 写入文件,读取时按需刷新
cookies
在 debugtalk.py 文件中增加两个函数,generate_cookies 用于在读取文件中的 cookies,teardown_saveCookies 用于在 login.yaml 中保存 cookies 内容到文件。
import time import requests from httprunner.api import HttpRunner COOKIES_PATH = r'xxx/xxx/cookies' def generate_cookies(): if (not os.path.exists(COOKIES_PATH)) or ( 3600 < int(time.time()) - int(os.path.getctime(COOKIES_PATH))): # cookies 文件不存在 或 最后修改时间超过 3600 秒 (1小时) 则重新登录刷新 cookies runner = HttpRunner() runner.run(r'api/login.yaml') with open(COOKIES_PATH, 'r') as f: cookies: str = f.read() return cookies def teardown_saveCookies(response: requests.Response): """保存 cookies 到文件给其他 api 调用""" cookies: dict = response.cookies foo: list = [] # 遍历 cookies 拆分 dict 并拼接为特定格式的 str # 如: server=xxxxx; sid=xxxxxx; track=xxxxx; for k, v in cookies.items(): foo.append(k + '=' + v + '; ') bar: str = "".join(foo) with open(COOKIES_PATH, 'w') as f: f.write(bar)
具体调用如下:
# login.yaml name: login base_url: http://xxxxxxx request: method: POST url: /login data: uid: xxx password: xxx teardown_hooks: - ${teardown_saveCookies($response)} # other api or testcase name: other api base_url: http://xxxxxxx request: method: GET url: /others headers: Cookie: ${generate_cookies()}
token
同理,token 的保存和读取也大同小异:
import time import json import requests from httprunner.api import HttpRunner AUTHORIZATION_PATH = r'xxx/xxx/authorization' def generate_authorization(): if (not os.path.exists(AUTHORIZATION_PATH)) or ( 3600 < int(time.time()) - int( os.path.getctime(AUTHORIZATION_PATH))): # authorization 文件不存在或最后修改时间超过 3600 秒(1 个小时)则重新登录 runner = HttpRunner() runner.run(r'api/login.yaml') with open(AUTHORIZATION_PATH, 'r') as f: authorization: str = f.read() return authorization def teardown_saveAuthorization(response: requests.Response): foo: dict = json.loads(response.text) bar: str = "Bearer " + foo['data']['token'] # 保存 authorization 到文件给其他 api 调用 with open(AUTHORIZATION_PATH, 'w') as f: f.write(bar)
这里生成 cookies 和 token 直接用的是 httprunner 的 api ,当然也可以直接使用 requests 拼装登录授权的接口来获取 cookies 或 token。
原文链接:https://www.luckycoding.com/2019/07/11/httprunner-summarize/