
Pytest
在pytest中,如何实现参数化呢?我们先来说说为什么需要参数化?
实际场景可能是下面这样:
假如你现在需要写一个登入的测试用例,需要设计几种测试用例如下:
账号密码正确。预期:登入成功
账号错误。预期:账号不存在
账号正确,密码错误。预期:账号或密码错误
账号为空 or 密码为空。预期:账号密码为空
我们通常会把方法写成这样:
user_account, password = ('admin', '123456')
def test_login_success():
"""
登入成功
:return:
"""
usr = 'admin'
psw = '123456'
# 调用login接口,传入usr和psw,代码省略
def test_login_usr_not_exist():
"""
账号不存在
:return:
"""
usr = 'admin1'
psw = '123456'
# 调用login接口,传入usr和psw,代码省略
def test_login_psw_error():
"""
密码错误
:return:
"""
usr = 'admin'
psw = '12345'
# 调用login接口,传入usr和psw,代码省略
def test_login_usr_or_psw_is_empty():
"""
账号或密码为空
:return:
"""
usr = ''
psw = ''
# 调用login接口,传入usr和psw,代码省略
复制
看起来没什么问题,对,确实没什么问题!这是很标准的用例写法,但是我们作为自动化测试工程师,怎么能满足于此。有没有更简便的方式呢?
我们观察可以看到 ,除了省略那部分一模一样的代码,其他不一样的只是usr和psw两个数据而已,我们只需要每次去改变这两个参数的值就行了 。那么这里就需要用到我们的参数化,所以在这里,参数化的作用是什么?提高编写用例的效率!
01
pytest的参数化方式
在pytest中,我们通常有两种参数化方式:
使用@pytest.mark.parametrize装饰器
使用fixture,对没错,又是fixture
@pytest.mark.parametrize是pytest自带的装饰器之一,我们可以直接使用。而fixture也是很神奇,上篇文章介绍过,fixture是测试固件,可以用来帮助我实现类似setUp,tearDown这些函数,实际上fixture同样可以用来处理参数化。
02
使用parametrize实现参数化
@pytest.mark.parametrize可以直接装饰在任何的测试函数上面,非常的方便,我们改写一下之前的例子,如下:
import pytest
test_data = [
{
'usr': 'admin', # 正常登入
'psw': '123456'
},
{
'usr': 'admin1', # 账号不存在
'psw': '123456'
},
{
'usr': 'admin', # 密码错误
'psw': '12345'
},
{
'usr': '', # 账号或密码为空
'psw': ''
},
]
@pytest.mark.parametrize('param', test_data)
def test_login(param): # 这个param需要和上面的'param'一致
print(param) # 打印
if __name__ == '__main__': # 定义主函数
pytest.main() # 调用pytest
复制
这时候我们尝试执行一下:

可以看到,现在的测试用例变成了四条,也就是test_data的列表长度,并且这时候的param就是test_data中的每个字典!那我们想要使用字典里的数据那就非常简单了,稍微改写一下代码:
@pytest.mark.parametrize('param', test_data)
def test_login(param):
usr = param.get('usr') # 获取字典中的usr
psw = param.get('psw') # 获取字典中的usr
print(f'usr: {usr} , psw: {psw}')
# 调用login接口,传入usr和psw,代码省略
复制
执行结果:

这样就取到了usr和psw两个字段,非常的简单。但是好像还是没满足我们的需求,少了测试用例的名称,这个pytest也给我们考虑到了,我们可以使用@pytest.mark.parametrize中有个ids字段,这个字段可以设置测试用例的标题,所以我们改造下代码:
test_data = [
{
'case': '登入成功',
'usr': 'admin', # 正常登入
'psw': '123456'
},
{
'case': '账号不存在',
'usr': 'admin1', # 账号不存在
'psw': '123456'
},
{
'case': '密码错误',
'usr': 'admin', # 密码错误
'psw': '12345'
},
{
'case': '账号或密码为空',
'usr': '', # 账号或密码为空
'psw': ''
},
]
@pytest.mark.parametrize('param', test_data, ids=[data.get('case') for data in test_data]) # ids需要传入一个列表,我们利用列表推导式
def test_login(param):
usr = param.get('usr')
psw = param.get('psw')
print(f'usr: {usr} , psw: {psw}')
# 调用login接口,传入usr和psw,代码省略
if __name__ == '__main__': # 定义主函数
pytest.main() # 调用pytest
复制
呆莫,执行的结果不如人意:

可以看到这时候出现了一些乱码,所以我们需要对乱码进行处理:
新建一个conftest.py文件
在里面加入下面一段代码:
def pytest_collection_modifyitems(items):
"""
修改用例名称中文乱码
:param items:
:return:
"""
for item in items:
item.name = item.name.encode('utf-8').decode('unicode_escape')
item._nodeid = item.nodeid.encode('utf-8').decode('unicode_escape')
复制
这里利用的实际上是pytest中的Hook机制,这块内容暂且先不介绍,后续会给大家介绍。总的来说,这就是个处理乱码的Hook函数。
加了之后,我们再去执行一下,发现这个时候已经没有问题了:

03
使用fixture实现参数化
那么下面我们再看一下fixture是如何实现参数化的?它为什么能实现参数化?
fixture提供了这么一个机制,fixture装饰的函数拥有一个内置的对象request,同时fixture中还有一个params参数是用来传递参数化数据的,直接上代码:
import pytest # 导入pytest
test_data = [
{
'case': '登入成功',
'usr': 'admin', # 正常登入
'psw': '123456'
},
{
'case': '账号不存在',
'usr': 'admin1', # 账号不存在
'psw': '123456'
},
{
'case': '密码错误',
'usr': 'admin', # 密码错误
'psw': '12345'
},
{
'case': '账号或密码为空',
'usr': '', # 账号或密码为空
'psw': ''
},
]
@pytest.fixture(params=test_data) # 给params传入参数化数据
def param_data(request):
return request.param # 返回request对象中的param,这里存放的就是参数化数据
def test_login(param_data): # 测试函数传入fixture
usr = param_data.get('usr')
psw = param_data.get('psw')
print(f'usr: {usr} , psw: {psw}')
# 调用login接口,传入usr和psw,代码省略
if __name__ == '__main__': # 定义主函数
pytest.main() # 调用pytest
复制
在上面的代码中,我们定义了一个fixture,把参数化的数据传入了params,同时给fixture设置了返回request.param。在测试函数中,我们把定义好的fixture传入即可。通过fixture我们就能拿到每次迭代的参数化数据了!
那么?fixture支不支持传入用例名称呢?当然是支持的!fixture也给我们提供了ids的参数,如下:
@pytest.fixture(params=test_data, ids=[data.get('case') for data in test_data]) # 给params传入参数化数据,ids传入case名称列表
def param_data(request):
return request.param # 返回request对象中的param,这里存放的就是参数化数据
复制
执行结果如下:

可以看到左侧的用例标题和右侧的输出都很完美,没有问题!不过要记得conftest.py中的Hook函数同样也是需要的呢。
那么本期内容到此结束,有什么问题可以及时联系小提莫,会及时进行解答,谢谢关注!
关注我们
扫二维码
