httprunner4详解
httpruuner官方文档:https://httprunner.com/docs/introduction/overview/
1.Httprunner环境搭建
HttpRunner v4.0 同时采用了 Golang/Python 两种编程语言,底层会有两套相对独立的执行引擎,兼具 Golang 的高性能和 pytest 的丰富生态。
方式一:直接部署(以Windows系统为例)
你可以在 GitHub Releases 页面中,自行选择版本进行下载,下载地址:https://github.com/httprunner/httprunner/releases,下载完成后,解压到自己电脑任意目录, 如下:D:\soft\httpruner_exe
:
将解压的目录,添加到系统环境变量:在「我的电脑=>属性=>高级系统设置=>环境变量」配置中,在 PATH 下新增系统变量,将 解压目录写入 PATH。配置成功后cmd输入hrp,结果如下:
安装配置完成后能使用hrp命令
方式二:安装依赖包(开发者模式)
HttpRunner
除了可以作为命令行工具提供给用户进行使用,还可以作为库函数,供开发者调用进行二次开发。
安装httprunner库:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple httprunner #安装httprunner,安装后,就可以使用hrun命令 pip install har2case#用于将har文件转化成yaml/json/pytest测试用例
安装完成后,使用以下命令检验
C:\Users>har2case -V 0.3.1 C:\Users>httprunner -V v4.3.5
安装Allure命令行:下载Allure命令行,下载地址:https://github.com/allure-framework/allure2/releases/,将下载到本地的allure压缩包解压到指定目录,并添加相应的环境变量Path=D:\Program Files\Allure\allure-2.16.0\bin,在命令行中输入allure,不出现报错即可
安装allure-pytest插件
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple allure-pytest#安装python的allure插件,可生成漂亮的测试报告
>>>待续
2. Httprunner快速上手
2.1创建脚手架
使用命令:hrp startproject dmo
新建项目的层级目录如下:
2.2 录制脚本,导出.har文件
使用fiddler用录制 Gitee上的开源项目:http://erp2.hzb-it.com/为例:
step1:打开fidddler设置过滤域名(erp2.hzb-it.com)、过滤js、css静态文件等[ 使用正则表达式:REGEX:(?insx)/[^\?/]*\.(css|ico|jpg|png|gif|bmp|wav|js|jpeg)(\?.*)?$ ],设置如下图:
设置后,点击Actions->Run Fliterset now,设置的过滤器正式生效
Step2:抓取一条业务用例: 登录—>组织和人员—>职位管理—>新增职位为:测试工程师
Step3:点击File ->Exprot Sessions ->HTTPArchive v1.2 ->将导出的.var文件放到项目的har目录
2.3 har文件转成测试用例
方式一:使用hrp命令转换
(1)使用命令,将目标目录har下的.har文件转成 yaml文件
hrp convert --from-har E:\work5-2\HttpRunner_demo\har\test_login.har --to-yaml --output-dir E:\work5-2\HttpRunner_demo\testcases
注意:如果不标注--from-har会失败
(2)使用命令,将目标目录testcase下的.yaml文件转成 json文件
hrp convert --from-yaml E:\work5-2\HttpRunner_demo\testcases\test_login_test.yaml --to-json --output-dir E:\work5-2\HttpRunner_demo\testcases
(3)使用命令,将目标目录testcase下的.json文件转成 pytest文件
hrp convert --from-har E:\work5-2\HttpRunner_demo\har\test_login.har --to-pytest --output-dir E:\work5-2\HttpRunner_demo\testcases
方式二:使用har2case转换
使用har2case可以直接将har文件转换为yaml/json格式的测试用例
har2case -h usage: har2case [-h] [-V] [--log-level LOG_LEVEL] [-2y] [-fmt FMT_VERSION] [--filter FILTER] [--exclude EXCLUDE] [har_source_file] Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner. positional arguments: har_source_file Specify HAR source file options: -h, --help show this help message and exit -V, --version show version --log-level LOG_LEVEL Specify logging level, default is INFO. -2y, --to-yml, --to-yaml Convert to YAML format, if not specified, convert to JSON format by default. -fmt FMT_VERSION, --format FMT_VERSION Specify YAML/JSON testcase format version, v2 corresponds to HttpRunner 2.2.0+. --filter FILTER Specify filter keyword, only url include filter string will be converted. --exclude EXCLUDE Specify exclude keyword, url that includes exclude string will be ignored, multiple keywords can be joined with '|'
方式三:使用httprunner 的make命令转换
如果直接使用hrp命令转换yaml/json为pytest格式失败,可以使用httprunner make命令转换为pytest测试用例
httprunner make E:\work5-2\HttpRunner_demo\testcases\test_login_test.yaml
2.4 使用pytest 运行测试用例
import pytest import os if __name__=="__main__": pytest.main(['-s', '-v','./testcases/test_login_test_test.py','--alluredir', './report/tmp', "--clean-alluredir"]) os.system(r"allure generate ./report/tmp -o ./report/html --clean")
2.5 v4 版本的 Go & Python 功能对比
3. httprunner详解
3.1 测试用例
一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:
- 明确的测试目的(achieve a particular software testing objective)
- 明确的输入数据(inputs)
- 明确的运行环境(execution conditions)
- 明确的测试步骤描述(testing procedure)
- 明确的预期结果(expected results)
按照上述的测试用例定义,HttpRunner 的测试用例应该保证是完整并且可以独立运行的。
从测试用例的组成结构来看,一个测试用例可以分为「测试脚本」和「测试数据」两部分:
- 测试脚本:重点是描述测试的业务功能逻辑,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.go/debugtalk.py)实现复杂的运算逻辑
- 测试数据:重点是对应测试的业务数据逻辑,例如数据驱动文件中的定义的 UUID、用户名等等,以及环境配置文件中定义的 base_url 环境变量等等
3.2 httprunner的YAML/JSON格式测试用例手工编写
httprunner 4.x 版本,YAML/JSON 格式用例(testcase)结构延续了之前的config 和 teststeps 两个部分
3.1.2 config配置部分
config的modle定义如下:
class TConfig(BaseModel): name: Name verify: Verify = False base_url: BaseUrl = "" # Text: prepare variables in debugtalk.py, ${gen_variables()} variables: Union[VariablesMapping, Text] = {} parameters: Union[VariablesMapping, Text] = {} # setup_hooks: Hooks = [] # teardown_hooks: Hooks = [] export: Export = [] path: Text = None # configs for other protocols thrift: TConfigThrift = None db: TConfigDB = TConfigDB()
每个测试用例都应该有一个config部分,您可以在其中配置测试用例级别的设置,有以下属性
属性名称 是否必填 作用 name 必填 指定测试用例名称。这将显示在执行日志和测试报告中。 base_url 可选 如果base_url指定,则 teststep 中的 url 可以设置相对路径部分 verify 可选 https请求时,是否校验证书,默认True,忽略证书校验可以设置为False headers 可选 公共请求头部 variables 可选 指定测试用例的公共变量。每个测试步骤都可以引用未在步骤变量中设置的配置变量。换句话说,步骤变量比配置变量具有更高的优先级。 export 可选 (早期版本用的output)指定导出的测试用例会话变量,把变量暴露出来,设置为全局变量 parameters 可选 参数化设置,对整个文件生效 #除了上面的一些自动化会用到的参数,4.x 版本新增了一些关键字 属性名称 是否必填 作用 parameters_setting 可选 配置参数驱动的具体策略 think_time 可选 设置思考时间,性能测试用到 websocket 可选 设置 WebSocket 断开重连的最大次数和间隔等(todo) weight 可选 性能测试用到,分配给当前测试用例的虚拟用户权重 environs 可选 配置环境变量(如果未指定则会从 .env 文件导入) path 可选 当前测试用例所在路径(通常不需要手工填写)
config部分示例
config: name: "request methods testcase with functions" variables: foo1: config_bar1 foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 headers: User-Agent: ${get_user_agent()} verify: False export: ["foo3"]
3.1.2 teststeps测试步骤部分
每个用例可以有多个测试步骤,每个步骤可以看成是一个接口的请求,发送 http 协议接口,可以用到request 关键字,相关参数和requests 库的参数完全一致。
teststep定义模型如下:
class TStep(BaseModel): name: Name request: Union[TRequest, None] = None testcase: Union[Text, Callable, None] = None variables: VariablesMapping = {} setup_hooks: Hooks = [] teardown_hooks: Hooks = [] # used to extract request's response field extract: VariablesMapping = {} # used to export session variables from referenced testcase export: Export = [] validators: Validators = Field([], alias="validate") validate_script: List[Text] = [] retry_times: int = 0 retry_interval: int = 0 # sec thrift_request: Union[TThriftRequest, None] = None sql_request: Union[TSqlRequest, None] = None
测试步骤 teststep 常用的一些基本关键字
#测试步骤 teststep 常用的一些基本关键字 测试步骤类型 含义 name 步骤名称 request 用于发起 HTTP 请求的步骤类型 api 用于引用 API 的步骤类型 testcase 用于引用其他测试用例的步骤类型 #每个步骤可以加变量,前置/后置,以及 提取和校验相关操作 测试步骤类型 作用 适用的测试步骤 variables 局部变量 通用 setup_hooks 前置函数 request/api/websocket teardown_hooks 后置函数 request/api/websocket extract 参数提取 request/api/websocket validate 结果校验 request/api/websocket export 导出变量 testcase #httprunner4.x 版本新增的一些关键字 测试步骤类型 含义 transaction 用于定义一个事务 rendezvous 集合点 think_time 思考时间 websocket 于发起 WebSocket 请求的步骤类型
teststep部分示例
teststeps: - name: get with params variables: foo1: ${ENV(USERNAME)} foo2: bar21 sum_v: "${sum_two_int(10000000, 20000000)}" request: method: GET url: $base_url/get params: foo1: $foo1 foo2: $foo2 sum_v: $sum_v extract: foo3: "body.args.foo2" validate: - eq: ["status_code", 200] - eq: ["body.args.foo1", "debugtalk"] - eq: ["body.args.sum_v", "30000000"] - eq: ["body.args.foo2", "bar21"] - name: post raw text variables: foo1: "bar12" foo3: "bar32" request: method: POST url: $base_url/post headers: Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - eq: ["status_code", 200] - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] - name: post form data variables: foo2: bar23 request: method: POST url: $base_url/post headers: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - eq: ["status_code", 200] - eq: ["body.form.foo1", "$expect_foo1"] - eq: ["body.form.foo2", "bar23"] - eq: ["body.form.foo3", "bar21"]
3.3 变量声明与引用
在 HttpRunner 中,支持变量声明(variables)和引用($var 或 ${var} )的机制。
在 config 和 step 中均可以通过 variables 关键字定义变量,然后在测试步骤中可以通过 $变量名称的方式引用变量。
区别在于
- 在 config 中定义的变量为全局的,整个测试用例(testcase)的所有地方均可以引用;
- 在 step 中定义的变量作用域仅局限于当前测试步骤(teststep)
3.3.1 config 全局变量
在config下声明变量(variables)是全局变量,这样就在整个.yml文件生效了
config:
name: logincase
variables:
user: test
psw: "123456"
通过 ${} 或 $ 的形式来引用变量, 例如:$var 或 ${var}
config:
name: 引用变量
variables:
username: test123
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
validate:
- eq: [status_code, 200]
3.3.2 teststeps局部变量
如下用例有2个步骤,其中第二个步骤post请求设置了局部变量, 那么它的作用范围只在第二个步骤中生效。
config:
name: 引用变量
teststeps:
-
name: get
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
-
name: post
variables:
username: test123
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
validate:
- eq: [status_code, 200]
3.3.3 变量优先级
原则上 config 变量和 step 变量名称尽量不要重复, 当config和step中都用同一个变量时,step变量优先级大于config变量
config:
name: 引用变量
variables:
username: test123
teststeps:
-
name: get
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
-
name: post
variables:
username: test456
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
validate:
- eq: [status_code, 200]
${username}
引用的结果是局部变量中的值 test456
3.4 提取返回结果与校验
3.4.1 extract提取返回结果
HttpRunner4.x 支持 2 种响应结果字段提取方式:jmespath 表达式和 正则表达式(regex)
- 响应结果为 JSON 结构,支持采用 jmespath 表达式进行参数提取。jmespath表达式,有关更多详细信息,请参考JMESPath教程https://jmespath.org/tutorial.html
- 返回的非json格式,可以用正则表达式(regex) 提取
extract 提取返回结果:
extract 的对象仅有 5 种类型:
- status_code:提取响应状态码,例如 200、404
- proto:提取协议类型,例如 “HTTP/2.0”、“HTTP/1.1”
- headers:从响应 headers 中提取字段,例如 headers.name
- cookies:从响应 cookies 中提取字段,例如 cookies.Token
- body:从响应 body 中提取字段,例如 body.args.foo1
如果表达式中存在 - 的情况,那么需要加引号处理。
headers."Content-Type"
响应结果为 JSON 结构,支持采用 jmespath 表达式进行参数提取。
使用示例1:
config:
name: 引用变量
variables:
username: test123
teststeps:
-
name: post
variables:
username: test456
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
extract:
url: body.url
origin: body.origin
agent: headers."Content-Type"
validate:
- eq: [status_code, 200]
使用pytest 命令行运行
httprunner make E:\work5-2\HttpRunner_demo\testcases\test_extract.yml#使用httprunner转换yaml格式的测试用例为py格式
pytest -vs E:\work5-2\HttpRunner_demo\testcases\test_extract_test.py#使用pytest命令行运行转换后的测试用例
运行日志可以看到提取的结果:
2023-11-28 11:06:28.893 | INFO | httprunner.response:extract:151 - extract mapping: {'url': 'http://httpbin.org/post', 'origin': '36.231.82.225', 'agent': 'application/json'} 2023-11-28 11:06:28.896 | INFO | httprunner.response:validate:230 - assert status_code equal 200(int) ==> pass
使用示例2:
config:
name: 测试get请求
teststeps:
-
name: get
request:
method: GET
url: https://www.baidu.com/
params:
wd: "python"
extract:
agent: headers."Content-Type"
validate:
- eq: [ "status_code", 200 ]
使用hrp命令运行:
hrp run E:\work5-2\HttpRunner_demo\testcases\extract.yaml --gen-html-report
运行日志可以看到提取结果:
3:33PM INF extract value from="headers.\"Content-Type\"" value=application/x-gzip 3:33PM INF set variable value=application/x-gzip variable=agent
返回的非json格式,可以用正则表达式(regex) 提取.
使用示例:
config: name: 引用变量 variables: username: test123 teststeps: - name: blog request: method: GET url: https://www.cnblogs.com/yoyoketang/ extract: title: "<title>(.*)</title>" validate: - eq: [status_code, 200] - eq: ["${title}", 200]
hrp run E:\work5-2\httprunner_demo\testcases\regex_extract.yml -s -g -c --log-plugin --continue-on-failure
运行日志可以看到提取结果:
-------------------------------------------------- 4:54PM INF extract value from=<title>(.*)</title> value="上海-悠悠 - 博客园" 4:54PM INF set variable value="上海-悠悠 - 博客园" variable=title 4:54PM INF validate status_code assertMethod=eq checkExpr=status_code checkValue=200 checkValueType=int64 expectValue=200 expectValueType=int64 result=true 4:54PM INF validate ${title} assertMethod=eq checkExpr=${title} checkValue="上海-悠悠 - 博客园" checkValueType=string expectValue="上海-悠悠 - 博客园" expectValueType=string result=true
注意最新版4.3.6的python代码块是没有regex提取功能的
可将httprunner.response.py代码做如下修改,实现功能:
class ResponseObject(ResponseObjectBase):类加如下类 方法 def _search_regexp(self,expr: Text) ->Any: try: encoding_way = self.resp_obj.encoding if encoding_way: body_str = self.resp_obj.content.decode(encoding_way) else: body_str = self.resp_obj.content.decode()#默认utf-8解码 if not isinstance(body_str,str): logger.error(f"convert body to string failed: {self.resp_obj.text}") regexp_compile = re.compile(expr) match= regexp_compile.search(body_str) if match and match.groups(): check_value = match.group(1) return check_value # return first matched result in parentheses logging.error(f"search regexp failed: {expr}") return expr except Exception as e: logging.error(f"An error occurred: {e}") return expr extract修改如下: def extract( self, extractors: Dict[Text, Text], variables_mapping: VariablesMapping = None, ) -> Dict[Text, Any]: if not extractors: return {} extract_mapping = {} for key, field in extractors.items(): if "$" in field: # field contains variable or function field = self.parser.parse_data(field, variables_mapping) if textExtractorSubRegexp in field: field_value = self._search_regexp(field) else: field_value = self._search_jmespath(field) extract_mapping[key] = field_value logger.info(f"extract mapping: {extract_mapping}") return extract_mapping 添加如下方法: def _search_regexp(self,expr: Text) ->Any: try : regexp_compile = re.compile(expr) match = regexp_compile.search(self.resp_obj.text) if match and match.groups(): check_value = match.group(1) return check_value # return first matched result in parentheses logging.error(f"search regexp failed: {expr}") return expr except Exception as e: logging.error(f"An error occurred: {e}") return expr
3.4.2 Validate校验结果
使用jmespath提取 JSON 响应正文(text/html 格式用正则表达式提取)并使用预期值进行验证。
- 提取结果:jmespath 表达式或正则表达式提取,更多细节参考JMESPath 教程
- 预期值:这里也可以使用指定的预期值、变量或函数引用
示例:
config: name: 校验结果 teststeps: - name: post variables: username: test456 request: method: POST url: http://httpbin.org/post json: username: ${username} password: "123456" validate: - eq: [status_code, 200] - eq: [body.url, "http://httpbin.org/post"] - eq: [body.origin, "183.193.27.78"] - eq: [headers."Content-Type", "application/json"]
validate 也可以支持引用提取的结果
示例:
config: name: 校验结果 teststeps: - name: post variables: username: test456 request: method: POST url: http://httpbin.org/post json: username: ${username} password: "123456" extract: url: body.url origin: body.origin type: headers."Content-Type" validate: - eq: [status_code, 200] - eq: ["${url}", "http://httpbin.org/post"] - eq: ["${origin}", "183.193.27.78"] - eq: ["${type}", "application/json"]
validate 可以支持的校验方式
assert 缩写 功能 equal "eq", "equals", "equal" 相等 less_than "lt", "less_than" 小于 less_or_equals "le", "less_or_equals" 小于或等于 greater_than "gt", "greater_than" 大于 greater_or_equals "ge", "greater_or_equals" 大于或等于 not_equal "ne", "not_equal" 不等于 string_equals "str_eq", "string_equals" 转字符串相等 length_equal "len_eq", "length_equal" 长度相等 length_greater_than "len_gt","length_greater_than" 长度大于 length_greater_or_equals "len_ge","length_greater_or_equals" 长度大于或等于 length_less_than "len_lt", "length_less_than" 长度小于 length_less_or_equals "len_le", "length_less_or_equals" 长度小于或等于 contains check_value 包含 expect_value contained_by expect_value 包含check_value type_match type类型匹配 regex_match 正则匹配re.match(expect_value, check_value) startswith 字符串以xx开头 endswith 字符串以xx结尾
yaml 中可以写2种格式校验
{"assert_name": [check_value, expect_value]}
{"check": check_value, "assert": assert_name, "expect": expect_value, "message": check status code}
第一种校验方式,也就是前面常用到的
validate:
- eq: [status_code, 200]
- eq: [body.url, "http://httpbin.org/post"]
- eq: [body.origin, "183.193.27.78"]
- eq: [headers."Content-Type", "application/json"]
第二种校验方式
validate:
- check: status_code # 检查点,支持 jmespath 和正则提取
assert: eq # 断言方式
expect: 200 # 期望结果
message: check status code # 可选项,当校验失败时的提示语
contains 校验包含
关于 contains 和 contained_by 函数定义
- check_value 是需要校验的返回结果
- expect_value 是期望结果,可以是这几种类型:list, tuple, dict, basestring
使用示例:
config:
name: 校验结果
teststeps:
-
name: post
variables:
username: test456
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- contains: ["${url}", "httpbin.org/post"]
3.5 debugtalk辅助函数
httprunner 4.x 可以支持go语言和python语言写辅助函数,本篇主要介绍python语言写辅助函数。
3.5.1 debugtalk函数编写示例
在项目根目录新建 debugtalk.py 文件写辅助函数可以实现自动化生成动态参数。
如下:生成时间戳 和 随机字符串示例
# debugtalk.py import time import uuid def current_time(): """获取时间戳""" return time.strftime("%Y-%m-%d %H:%M:%S") def rand_str(): """生成随机字符串""" return str(uuid.uuid4())[:6]
3.5.2 在yaml 用例中引用debugtalk文件的函数
示例1:
config: name: 引用变量 teststeps: - name: post request: method: POST url: http://httpbin.org/post json: username: ${rand_str()} password: "123456" validate: - eq: [status_code, 200]
变量中也可以引用函数
示例2:
config: name: 引用变量 variables: user: ${rand_str()} teststeps: - name: post request: method: POST url: http://httpbin.org/post json: username: ${user} password: "123456" validate: - eq: [status_code, 200]
使用pytest运行测试用例:
httprunner make E:\work5-2\httprunner_demo\testcases\extract.yaml
pytest -vs E:\work5-2\httprunner_demo\testcases\extract_test.py
3.6 环境变量的使用
一般来说,在进行实际应用的开发过程中,应用会拥有不同的运行环境,通常会有以下环境:
- 本地开发环境
- 测试环境
- 生产环境
在不同环境中,我们可能会使用不同的数据库或邮件发送等配置,这时候则需要通过 .env
文件来针对不同的运行环境作不同的设置。
3.6.1 环境变量基础知识
在自动化测试中,有时需要借助环境变量实现某些特定的目的,常见的场景包括:
- 切换测试环境
- 切换测试配置
- 存储敏感数据(从信息安全的角度出发)
Windows 系统中使用 set 命令设置环境变量和值,接下来设置以下变量
- base_url = http://127.0.0.1:8000 设置base_url值,可以一键切换运行的环境
- username = test 设置登陆账号和密码,切换账号测试
- password = 123456 设置登陆账号和密码,切换账号测试
打开cmd,使用 set key=value
格式设置环境变量(linux使用 export
命令)
C:\Users\dell>set base_url=http://127.0.0.1:8000 C:\Users\dell>set username=test C:\Users\dell>set password=123456
查看环境变量值使用 set keyname
查看对应的值
C:\Users\dell>set base_url base_url=http://127.0.0.1:8000 C:\Users\dell>set username username=test C:\Users\dell>set password password=123456
在windows系统里面,命令行引用变量用 %var%
C:\Users\dell>echo 账号:%username% 账号:test
linux 系统中使用 export 命令设置环境变量和值, 引用变量$keyname
[root@VM_0_2_centos ~]# export base_url=http://127.0.0.1:8000 [root@VM_0_2_centos ~]# export username=test [root@VM_0_2_centos ~]# echo $base_url http://127.0.0.1:8000 [root@VM_0_2_centos ~]# echo $username test
在cmd设置的环境变量只是临时的环境变量,不会保存到电脑本地,关闭窗口后自动清除。
3.6.2 使用.env 文件设置环境变量
winodws无法直接创建 .env 的文件,会提示文件名不能为空,这里使用 pycharm 新建用过 .env 文件.env
文件必须放到项目根目录(debugtalk.py同一层级)
# .env base_url=http://127.0.0.1:8000 username=test password=123456
在 HttpRunner 4.x版本中内置了函数 ENV
函数读取环境变量的值
3.6.3 用例引用环境变量
引用环境变量使用ENV函数 ${ENV(keyname)
config: name: 引用env文件 base_url: ${ENV(base_url)} variables: user: ${ENV(username)} psw: ${ENV(password)} teststeps: - name: post request: method: POST url: /post json: username: ${user} password: ${psw} validate: - eq: [status_code, 200]
3.7 base_url 环境地址变量的使用
config 中有个 base_url 关键字可以设置环境地址,这样其它接口就只需写相对地址了
比如我要测试的API接口如下
- http://httpbin.org/get 第一个是get请求
- http://httpbin.org/post 第二个是post请求
如果一个测试用例,有多个步骤中,测试环境地址不变http://httpbin.org
, 只是接口地址不一样/get
和 /post
于是可以把公共的环境地址单独拿出来
示例1:
config: name: 引用变量 base_url: http://httpbin.org variables: username: test123 teststeps: - name: get request: method: GET url: /get validate: - eq: [status_code, 200] - name: post variables: username: test456 request: method: POST url: /post json: username: ${username} password: "123456" validate: - eq: [status_code, 200]
如果多个yaml 文件公用base_url ,可以写到环境变量.env
# .env base_url=http://127.0.0.1:8000 username=test password=123456
config: name: 引用env文件 base_url: ${ENV(base_url)} variables: user: ${ENV(username)} psw: ${ENV(password)} teststeps: - name: post request: method: POST url: /post json: username: ${user} password: ${psw} validate: - eq: [status_code, 200]
3.8 parameters参数化
httprunner 4.x 实现参数化使用parameters 关键字,数据源有三种方式
- 1.在yaml 文件中直接写测试数据源
- 2.测试数据源写到csv文件
- 3.自定义函数,函数返回列表形式数据
3.8.1 参数配置
参数名称的定义分为两种情况:
- 独立参数单独进行定义;
- 多个参数具有关联性的参数需要将其定义在一起,采用短横线(-)进行连接。
数据源指定支持三种方式:
- 在 YAML/JSON 中直接指定参数列表:该种方式最为简单易用,适合参数列表比较小的情况
- 通过内置的 parameterize(可简写为P)函数引用 CSV 文件:该种方式需要准备 CSV 数据文件,适合数据量比较大的情况
- 调用 debugtalk.py 中自定义的函数生成参数列表:该种方式最为灵活,可通过自定义 Python 函数实现任意场景的数据驱动机制,当需要动态生成参数列表时也需要选择该种方式
三种方式可根据实际项目需求进行灵活选择,同时支持多种方式的组合使用。
假如测试用例中定义了多个参数,那么测试用例在运行时会对参数进行笛卡尔积组合,覆盖所有参数组合情况。
3.8.2 单个参数的参数化
先准备测试数据,准备四组登录用的账号和密码,账号为test1,test2,test3,test4,密码统一设置为123456。
参数user账号数据,设置对应的值 ["test1", "test2", "test3", "test4"],定义单个参数用variables,定义参数化用parameters
config: name: login case parameters: user: [test1, test2, test3, test4] teststeps: - name: post request: method: POST url: http://httpbin.org/post json: username: ${user} password: "123456" validate: - eq: [status_code, 200]
如果参数化里面的数据只有一个,比如psw对应的值只有一个,parameters 也可以设置 password的值
config:
name: login case
parameters:
user: [test1, test2, test3, test4]
password: 123456
variables 和 parameters 设置相同名称变量时,parameters 优先级大于variables
完整的 login_params.yaml 脚本如下
config: name: login case variables: user: test password: 123456 parameters: user: [test1, test2, test3, test4] teststeps: - name: post request: method: POST url: http://httpbin.org/post json: username: ${user} password: ${password} validate: - eq: [status_code, 200]
使用命令行转换为py格式的用例,使用pytest运行
httprunner make E:\work5-2\httprunner_demo\testcases\login_params.yaml
import pytest import os if __name__=="__main__": pytest.main(['-s', '-v','./testcases/extract_test.py','--alluredir', './report/tmp', "--clean-alluredir"]) os.system(r"allure generate ./report/tmp -o ./report/html --clean")
生成的报告如下:
3.8.2 关联性参数的参数化
针对于一个账号对应一个密码,这种一一对应的关系,可以用关联性的参数化
config: name: login case parameters: user-password: - [test1, "123456"] - [test2, "123456"] - [test3, "123456"] - [test4, "123456"] teststeps: - name: post request: method: POST url: http://httpbin.org/post json: username: ${user} password: ${password} validate: - eq: [status_code, 200]
3.8.3 笛卡尔集参数化
比如测试账号有四种["test1", "test2", "test3", "test4"],密码也有四种 ["123456", "123456", "123456", "123456"]
用笛卡尔积组合的话,就是4*4=16种组合
config: name: login case parameters: user: [test1, test2, test3, test4] password: ["123456", "123456", "123456", "123456"] teststeps: - name: post request: method: POST url: http://www.example.com/ params: username: ${user} password: ${password} validate: - eq: [status_code, 200]
这样运行会生成16组用例
3.8.4 使用csv文件参数化
对于已有参数列表,并且数据量比较大的情况,比较适合的方式是将参数列表值存储在 CSV 数据文件中。
对于 CSV 数据文件,需要遵循如下几项约定的规则:
- CSV 文件中的第一行必须为参数名称,从第二行开始为参数值,每个(组)值占一行;
- 若同一个 CSV 文件中具有多个参数,则参数名称和数值的间隔符需实用英文逗号;
- 在 YAML/JSON 文件引用 CSV 文件时,文件路径为基于项目根目录(debugtalk.py 所在路径)的相对路径。
例如,user 的参数取值范围为 test1,test2,test3, test4,那么我们就可以创建 user.csv,放到项目根目录下的data目录下,并且在文件中按照如下形式进行描述。
data/ user.csv
user
test1
test2
test3
test4
然后在 YAML/JSON 测试用例文件中,就可以通过内置的 parameterize(可简写为 P)函数引用 CSV 文件
config: name: login case parameters: user: ${P(data/user.csv)} teststeps: - name: post request: method: GET url: http://www.example.com/ params: username: ${user} password: "12345" validate: - eq: [status_code, 200]
3.8.5 使用debugtalk函数参数化用例
用 debugtalk.py 中自定义的函数生成参数列表,生成的参数列表必须为 list of dict 的数据结构。
需对 user_id 进行参数化数据驱动,参数取值范围为 1001~1004,那么就可以在 debugtalk.py 中定义一个函数,返回参数列表。
debugtalk.py
def get_user_id(): return [ {"user_id": 1001}, {"user_id": 1002}, {"user_id": 1003}, {"user_id": 1004} ]
然后,在 YAML/JSON 的 parameters 中就可以通过调用自定义函数的形式来指定数据源。
config: name: "demo" parameters: user_id: ${get_user_id()}
另外,通过函数的传参机制,还可以实现更灵活的参数生成功能,在调用函数时指定需要生成的参数个数。
例如,在 debugtalk.py 中定义函数 get_account,生成指定数量的账号密码参数列表。
def get_account(num): accounts = [] for index in range(1, num+1): accounts.append( {"user": "test%s" % index, "password": "123456"}, ) return accounts
那么在 YAML/JSON 的 parameters 中就可以调用自定义函数生成指定数量的参数列表。
config: name: "demo" parameters: username-password: ${get_account(4)}
在 debugtalk.py 中定义 get_user_password 函数,返回 4 个用户名和密码数据。
def get_account(num): accounts = [] for index in range(1, num+1): accounts.append( {"user": "test%s" % index, "password": "123456"}, ) return accounts if __name__ == '__main__': print(get_account(4))
生成 list of dict 数据格式:
[ {'user': 'test1', 'password': '123456'}, {'user': 'test2', 'password': '123456'}, {'user': 'test3', 'password': '123456'}, {'user': 'test4', 'password': '123456'} ]
config: name: login case parameters: user-password: ${get_account(4)} teststeps: - name: post request: method: GET url: http://www.example.com/ params: username: ${user} password: ${password} validate: - eq: [status_code, 200]
3.9 hook机制
httprunner 4.x可以支持hook机制,在发送请求前做一些预处理或在请求完成后后置处理
- setup_hooks :主要用于处理接口的前置的准备工作,也可以对请求 request 参数签名加密等操作
- teardown_hooks:主要用于后置清理工作,也可以对返回 respone 解密等操作
3.9.1 测试步骤添加hook
在项目根目录新建 debugtalk.py,名称一定要一样
def hook_up(): print("前置操作:setup!") def hook_down(): print("后置操作:teardown!")
只在第一个步骤添加 setup_hooks 和 teardown_hooks
config: name: logincase teststeps: - name: step1 login request: url: http://www.example.com method: GET setup_hooks: - ${hook_up()} teardown_hooks: - ${hook_down()}
3.9.2 request预处理
针对请求request 发出去的参数预处理,也可以用到 setup_hooks,需传一个内置 request 参数,debugtalk.py代码如下
# debugtalk.py def request_sign(request): """请求sign签名""" print("请求body:",request.get("req_json")) # 新增 sign 参数 request["req_json"]["sign"] = "sign xxxxxxxxxxxxxxx" print("sign 签名后请求body:", request.get("req_json")) return request
config: name: logincase teststeps: - name: step1 login request: url: http://www.example.com method: POST json: user: test psw: "123456" setup_hooks: - ${request_sign($request)}
通过打印报告可以看出request预处理生效
================== request details ================== method : MethodEnum.POST url : http://www.example.com/ headers : { "User-Agent": "python-requests/2.31.0", "Accept-Encoding": "gzip, deflate, br", "Accept": "*/*", "Connection": "keep-alive", "HRUN-Request-ID": "HRUN-f3452b5a-1080-4f51-8fec-3ac81ef62c07-032632", "Content-Length": "65", "Content-Type": "application/json" } cookies : {} body : { "user": "test", "psw": "123456", "sign": "sign xxxxxxxxxxxxxxx"
3.9.3 返回response处理
如果需要在接口返回后,对返回的结果处理,可以添加respone请求参数,比如我把返回的body改成了"python"
def response_containt(response): """修改返回body""" print("返回response body:", response["body"]) response["body"] = "python" print("修改后返回response body:", response["body"]) return response
这对于返回的body是加密的数据,可以自己去解密后返回解密内容很有帮助
config: name: logincase teststeps: - name: step1 login request: url: http://www.example.com method: POST json: user: test psw: "123456" teardown_hooks: - ${response_status($response)} validate: - eq: [status_code, 200] - eq: [body, "python"]
3.10 接口关联实战
当登录用例写完后,后面想继续写其他用例,可以导入前面的login用例,当成下个用例的步骤使用
导入前一个用例之前,需先 export 导出变量,变成全局变量。
案例1:使用电商开源项目演示:
项目地址:https://github.com/macrozheng/mall
config: name: 测试登录关联 variables: username: admin password: macro123 export: - token teststeps: - name: 账号密码成功登录 request: url: "https://admin-api.macrozheng.com/admin/login" method: POST headers: User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" Content-Type: "application/json" json: username: ${username} password: ${password} extract: token: "body.data.token" validate: - eq: ["status_code", 200] - eq: ["body.message", "操作成功"]
成功拿到token鉴权码
2023-11-30 17:20:17.931 | INFO | httprunner.response:extract:156 - extract mapping: {'token': 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE3MDEzMzYwMTc5OTEsImV4cCI6MTcwMTk0MDgxN30.RHqCipsb3uLUzVS1j0ZtooHGKSv_Yty-Kz8lIjVsP_b8o74gu7PoWkxvKQPm0ntCSb6-C3cyfnK4ocCrxF1bOg'} 2023-11-30 17:20:17.931 | INFO | httprunner.response:validate:251 - assert status_code equal 200(int) ==> pass 2023-11-30 17:20:17.932 | INFO | httprunner.response:validate:251 - assert body.message equal 操作成功(str) ==> pass
从登录接口返回数据结果中得到鉴权参数后,传递给其他接口放到请求头中。
其他接口测试用例在teststeps中引入登录用例即可。
config: name: 广告搜索功能 verify: False teststeps: - name: 账号密码成功登录 testcase: testcases/login.yaml - name: 广告搜索 variables: pageNum: 1 pagesize: 5 name: "电影推荐" request: url: "https://admin-api.macrozheng.com/home/advertise/list" method: GET headers: User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" Content-Type: "application/json" Authorization: " Bearer $token" params: pageNum: $pageNum pagesize: $pagesize name: $name validate: - eq: ["status_code", 200] - contains : ["body.data.list[0].name", "电影推荐"]
import pytest import os if __name__=="__main__": pytest.main(['-s', '-v','./testcases/','--alluredir', './report/tmp', "--clean-alluredir"]) os.system(r"allure generate ./report/tmp -o ./report/html --clean")
生成的测试报告如下:
3.11 upload上传文件
requests 库里面上传文件会用到 requests_toolbelt, 可以很方便的解决 multipart/form-data 类型的文件上传相关接口。
HttpRunner4.x 集成了 requests_toolbelt,可以使用内置 upload 关键字来上传文件相关操作。
使用此功能需要安装额外的库
pip3 install requests-toolbelt filetype
在项目根目录新建data目录,放需要传的文件up.jpg
新建 test_up.yaml
config: name: file teststeps: - name: upload file request: url: http://124.70.221.221:8201/api/v1/upfile/ method: POST upload: file: data/up.jpg title: xxx validate: - eq: [body.code, 0]
>>>待续