前言
说到接口自动化,那肯定少不了参数化,这也是pytest的一个特色之一,相比与unitest实现起来更加方便好用。实验参数化常见的就是使用@pytest.mark.parametrize
在测试函数或类中定义多组参数,在用例中实现参
数化。
# 参数化方式一
import pytest
@pytest.mark.parametrize("test_input,expected",[ ["3+5", 8], ["2+4", 6], ["6 * 9", 42] ])
def test_eval(test_input, expected):
assert eval(test_input) == expected
1. 使用pytest_generate_tests实现参数化
由官方文档,可以知道,实现参数化的方式有三种,我们使用第三种方式进行实验,可以更适合本框架的开发。具体的实现方式如官方详细文档,实现的步骤就是:
1.在test函数中传指定的参数名称
2.conftest中实现hook函数:pytest_generate_tests
3.传入参数化数据,实现参数化
1.1 实现参数化示例
# test_demo3.py
# 用例的id名称
names = ["login nam1", "login name2"]
# 测试数据 list of dict
test_data = [{
"url": "http://49.235.x.x:5000/api/v1/login/",
"method": "POST",
"json": {
"username": "test",
"password": "123456"
}
},
{
"url": "http://49.235.x.x:5000/api/v1/login/",
"method": "POST",
"json": {
"username": "test",
"password": "123456"
}
}]
def test_login(param):
print('---->', param)
# conftest.py
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
if "param" in metafunc.fixturenames:
metafunc.parametrize("param",
metafunc.module.test_data,
ids=metafunc.module.names,
scope="function")
2 在yaml中实现参数化
在hook函数中,我们分为module级别的参数化,以及function级别的参数化,module级别就是在yml文件的config中传入的参数,function级别就是在单个用例中传入的参数,所以都支持。
所以可以知道,在yml中要实现参数化,需要解决2个问题:
1.在用例函数中需要传入fixtures参数,也就是需要将参数写在fixtures中,再传入用例函数。
2.在创建函数时,要设置参数属性,以及参数数据属性,即:在生成动态用例函数时,就要添加这2个属性
# conftest.py
def pytest_generate_tests(metafunc):
"""
测试用例参数化:
:param metafunc: 共有五个属性值
1.matafunc.fixturenames: 参数化收集时的参数名称
2.matafunc.module: 使用参数名称进行参数化的测试用例所在的模块对象
3.matafunc.config: 测试用例会话
4.matafunc.function: 测试用例对象,即函数或方法对象
5.matafunc.cls: 测试用例所属的类的类对象
:return: None
"""
if hasattr(metafunc.module, 'module_params_data'): # 检查函数对象
params_data = getattr(metafunc.module, 'module_params_data')
params_fixtures = getattr(metafunc.module, 'module_params_fixtures')
params_len = 0 # 参数化,参数的个数
if isinstance(params_data, list):
if isinstance(params_data[0], list): # 支持下面这种格式
"""fixtures: username, password
parameters:- [test1, '123456']
- [test2, '123456']"""
params_len = len(params_data[0])
elif isinstance(params_data[0], dict): # 支持下面这种格式
"""parameters:
- {"username": "test1", "password": "123456"}
- {"username": "test2", "password": "1234562"}"""
params_len = len(params_data[0].keys())
else:
params_len = 1
params_args = params_fixtures[-params_len:]
metafunc.parametrize(
','.join(params_args),
params_data,
scope="module"
)
# 用例级别
if hasattr(metafunc.module, f'{metafunc.function.__qualname__}_params_data'): # __qualname__:获取用例函数名
params_data = getattr(metafunc.module, f'{metafunc.function.__qualname__}_params_data')
params_fixtures = getattr(metafunc.module, f'{metafunc.function.__qualname__}_params_fixtures')
params_len = 0 # 参数化,参数的个数
if isinstance(params_data, list):
if isinstance(params_data[0], list): # 支持下面这种格式
"""fixtures: username, password
parameters:- [test1, '123456']
- [test2, '123456']"""
params_len = len(params_data[0])
elif isinstance(params_data[0], dict): # 支持下面这种格式
"""parameters:
- {"username": "test1", "password": "123456"}
- {"username": "test2", "password": "1234562"}"""
params_len = len(params_data[0].keys())
else:
params_len = 1
params_args = params_fixtures[-params_len:]
metafunc.parametrize(
','.join(params_args),
params_data,
scope="function"
)
在run.py文件中我们分别添加module级别的和用例级别的代码。
def run(self):
...
# 收集config中的fixtures
config_fixtures = self.raw.get('config').get('fixtures', [])
# 多个则变成list形式存储
if isinstance(config_fixtures, str):
config_fixtures = [item.strip(' ') for item in config_fixtures.split(',')] # 这里做了优化,需要去掉item中的" ",否则fixture传入的不合法
# 在收集config——fixtures这里,我们添加收集parameters参数data
config_param_data = self.raw.get('config').get('parameters', [])
# 2.先渲染config_variables
...
# # --------------给module加参数化-------
config_param_data = render_template_obj.rend_template_any(config_param_data, **self.context)
config_param_fixtures = render_template_obj.rend_template_any(config_fixtures, **self.context)
# # config中支持2种格式,把他们合并然后返回一种统一处理
config_param_fixtures, config_param_data = self.parameters_data(config_param_fixtures, config_param_data)
if config_param_data:
# 向module中加入参数化的属性
setattr(self.module, 'module_params_data', config_param_data)
setattr(self.module, 'module_params_fixtures', config_param_fixtures)
# # -------------------end----------------
...
# 在用例这里,添加用例级别的代码
case = {} # 收集用例名称和执行内容
for case_name, case_value in self.raw.items():
...
# 用例中的fixtures
case_fixtures = []
if 'fixtures' in case[case_name][0]: # fixtures写在第一个步骤中
case_raw_fixtures = case[case_name][0].get('fixtures', [])
case_fixtures = render_template_obj.rend_template_any(case_raw_fixtures, **self.context)
# 如果有多个,则变成list的形式
if isinstance(case_fixtures, str): # fixtures: run_fixt or [run_fixt1, run_fixt2] --> list # 这里做了修改优化,放到了if层级下
case_fixtures = case_fixtures.split(',')
# --------------用例级别的参数化----------
if 'parameters' in case[case_name][0]:
case_raw_parameters = case[case_name][0].get('parameters', [])
case_parameters = render_template_obj.rend_template_any(case_raw_parameters, **self.context)
case_fixtures, case_parameters = self.parameters_data(case_fixtures, case_parameters)
if case_parameters:
# 向module中加入参数化数据和属性
setattr(self.module, f'{case_name}_params_data', case_parameters)
setattr(self.module, f'{case_name}_params_fixtures', case_fixtures)
# -------------end--------------------
# 新增函数:对传入的2种方式,做了统一处理,统一返回:参数(传入的fixtures),value(参数数据)的形式
def parameters_data(self, fixtures, parameters):
"""
参数化实现2种方式:
方式1:
config:
name: post示例
fixtures: username, password
parameters:
- [test1, '123456']
- [test2, '123456']
方式2:
config:
name: post示例
parameters:
- {"username": "test1", "password": "123456"}
- {"username": "test2", "password": "1234562"}
:returns 统一合并:key和value
fixtures: 用例需要用到的fixtures: ['username', 'password']
parameters: 参数化的数据list of list : [['test1', '123456'], ['test2', '123456']]
"""
#1.将fixtures的str换成list
if isinstance(fixtures, str):
fixtures = [item.strip(' ') for item in fixtures.split(',')]
if isinstance(parameters, list) and len(parameters) >= 1: # 收集方式二中的key和value
if isinstance(parameters[0], dict):
params = list(parameters[0].keys())
new_parameters = []
for item in parameters:
new_parameters.append(list(item.values()))
for param in params:
if param not in fixtures:
fixtures.append(param)
return fixtures, new_parameters
else:
return fixtures, parameters
else:
# 没有参数化数据,直接返回[]
fixtures, []
上面增加的代码解决了:1.将参数作为fixtures传入到用例函数中,2.动态生成用例函数前,先增加module中的属性:参数名和参数数据
创建test_x.yml文件
config:
fixtures: user, psw
parameters:
- [a, 'b']
- [c, d]
test_x1:
name: 用例1
parameters:
- {"username": "test1", "password": "123456"}
- {"username": "test2", "password": "1234562"}
print: "1-${user}"
test_x2:
name: 用例2
print: "2-${user}"
由参数化数据,可知,该yml文件参数化,生成6条case。运行:pytest .\test_x.yml