简介
unittest 和 pytest参数化对比:
pytest与unittest的一个重要区别就是参数化,unittest框架使用的第三方库ddt来参数化的
而pytest框架:
- 前置/后置处理函数fixture,它有个参数params专门与request结合使用来传递参数,也可以用parametrize结合request来传参
- 针对测试方法参数化就直接使用装饰器
@pytest.mark.parametrize
来对测试用例进行传参
参数化目的
- 参数化,就是把测试过程中的数据提取出来,通过参数传递不同的数据来驱动用例运行。其实也就是数据驱动的概念
- 当测试用例只有测试数据和期望结果不一样,但操作步骤是一样的时,可以用参数化提高代码复用性,减少代码冗余
参数化实际应用举例
Web UI自动化中的开发场景,比如是一个登录框:
1、需要测试账号空、密码空、账号密码都为空、账号不存在、密码错误、账号密码正确等情况
2、这些用例的区别就在于输入的测试数据和对应的交互结果
3、所以我们可以只写一条登录测试用例,然后把多组测试数据和期望结果参数化,节省很多代码量
Pytest参数化
语法
@pytest.mark.parametrize(args_name,args_values,indirect=False, ids=None,scope=None)
参数列表
- args_name:参数名称,用于参数值传递给函数
- args_values:参数值:(列表和列表字典,元组和字典元组),有n个值那么用例执行n次。
- indirect:默认为False,代表传入的是参数。如果设置成True,则把传进来的参数当函数执行,而不是一个参数。
- ids:自定义测试id,字符串列表,ids的长度需要与测试数据列表的长度一致,标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
- scope:如果指定,则表示参数的范围。
范围用于按参数实例对测试进行分组。
它还将覆盖任何夹具功能定义的范围,允许使用测试上下文或配置设置动态范围。
使用方法
1、单个参数
@pytest.mark.parametrize()
装饰器接收两个参数,一个参数是以字符串的形式标识用例函数的参数,第二个参数以列表或元组的形式传递测试数据
import pytest
#待测试数据
def add(a,b):
return a+b
# 单个参数的情况
@pytest.mark.parametrize("a",[1,2,3,4])
def test_add(a): # 作为用例参数,接收装饰器传入的数据
print(" a的值:",a)
assert add(a,1) == a+1
执行结果
2、多个参数
多个参数,@pytest.mark.parametrize()
第一个参数依然是字符串, 对应用例的多个参数,用逗号分隔,实际是一个解包的过程。
import pytest
def add(a,b):
return a+b
@pytest.mark.parametrize("a,b,c",[(1,2,3),(4,5,9),('1','2','12')])
def test_add(a,b,c):
print(f"\n a,b,c的值:{a},{b},{c}")
assert add(a,b)==c
执行结果
3、整个测试类参数化
测试类的参数化,其实际上也是对类中的测试方法进行参数化。
当装饰器 @pytest.mark.parametrize
装饰测试类时,会将数据集合传递给类的所有测试用例方法
import pytest
data= [(1,2,3),(4,5,9)]
@pytest.mark.parametrize("a,b,c",data)
class TestStudy:
def test_parametrize_one(self,a,b,c):
print(f"\n 测试函数111 测试数据为 {a}-{b}")
assert a+b ==c
def test_parametrize_two(self, a, b, c):
print("\n 测试函数222 测试数据为 {}-{}".format(a,b))
assert a + b == c
执行结果
4、多个参数叠加相乘
多个参数组合时使用的是笛卡尔积。
笛卡尔积:
- 一个函数或一个类可以装饰多个 @pytest.mark.parametrize
- 这种方式,最终生成的用例数是nm,比如上面的代码就是:参数a的数据有3个,参数b的数据有2个,所以最终的用例数有32=6条
- 当参数化装饰器有很多个的时候,用例数都等于n * n * n* n *…
# 笛卡尔积,组合数据
data_1=[1,2,3]
data_2=['a','b']
@pytest.mark.parametrize('a',data_1)
@pytest.mark.parametrize('b',data_2)
def test_parametrize_one(a,b):
print(f"笛卡尔积 测试数据为:{a},{b}")
执行结果
参数化,传入字典
# 字典
data_1=(
{
'user':1,
'pwd':2
},
{
'user':3,
'pwd':4
}
)
@pytest.mark.parametrize('dic',data_1)
def test_parametrize_1(dic):
print(f"测数据为 \n {dic}")
print(f"user:{dic['user']},pwd:{dic['pwd']}")
执行结果
参数化,标记数据
# 标记参数化
@pytest.mark.parametrize("test_input,expected",[
("3+5",8),
("2+4",6),
pytest.param("6 * 9",42,marks=pytest.mark.xfail),
pytest.param("6 * 6",42,marks=pytest.mark.skip)
])
def test_parametrize_mark(test_input,expected):
assert eval(test_input) == expected
执行结果
5、ids 自定义测试id
-
@pytest.mark.parametrize()
提供了 ids 参数来自定义显示结果,主要是为了更加清晰看到用例的含义 -
ids的长度需要与测试数据列表的长度一致,与fixture的ids参数是一样的,可参考fixture的ids。
# 增加可读性
data_1=[(1,2,3),(4,5,9)]
ids = ["a:{} + b:{} = expect:{}".format(a,b,expect) for a,b,expect in data_1]
@pytest.mark.parametrize("a,b,expect",data_1,ids=ids)
class TestParametrize(object):
def test_parametrize_1(self,a,b,expect):
print(f"测试函数1测试数据为 {a}--{b}")
assert a + b ==expect
def test_parametrize_2(self,a,b,expect):
print("测试函数2测试数据为 {}--{}".format(a,b))
assert a + b == expect
执行结果
request 与parametrize 结合使用给 Fixture传参
-
fixture自身的params参数可以结合request来传参,详见fixture的其他参数介绍章节,当然也可以用parametrize来参数化代替params
-
indirect=True参数,目的是把传入的data当做函数去执行,而不是参数
-
如果测试方法写在类中,则
@pytest.mark.parametrize
的参数名称要与@pytest.fixture
函数名称保持一致
应用场景:
- 为了提高复用性,我们在写测试用例的时候,会用到不同的fixture,比如:最常见的登录操作,大部分的用- 例的前置条件都是登录
- 假设不同的用例想登录不同的测试账号,那么登录fixture就不能把账号写死,需要通过传参的方式来完成登录操作
1、单个参数
@pytest.fixture()
def test_demo(request):
name = request.param
yield name
print(f"==测试数据是:{name}==")
data=["ceshi","qianduan"]
ids=[f"test_name is:{name}" for name in data]
@pytest.mark.parametrize("test_demo",data,ids=ids,indirect=True)
def test_name(test_demo):
print(f"测试用例的数据是:{test_demo}")
执行结果
知识点:
- 添加 indirect=True 参数是为了把 test_demo 当成一个函数去执行,而不是一个参数并且将data当做参数传入函数
- def test_name(test_demo) ,这里的 test_demo 是获取fixture返回的值
2、多个参数
@pytest.fixture()
def logins(request):
param = request.param
yield param
print(f"账号是:{param['username']},密码是:{param['pwd']}")
data =[
{"username":"张三","pwd":"123456"},
{"username":"李四","pwd":"12345"}
]
@pytest.mark.parametrize("logins",data,indirect=True)
def test_name_pwd(logins):
print(f"账号是:{logins['username']},密码是:{logins['pwd']}")
执行结果
如果需要传多个参数,需要通过字典去传
3、多个fixture(只加一个装饰器)和多个parametrize(叠加装饰器)
# 多个fixture
@pytest.fixture()
def login_user(request):
user = request.param
yield user
print("账号:%s" % user)
@pytest.fixture()
def login_pwd(request):
pwd = request.param
yield pwd
print("密码:%s" % pwd)
data =[
{"username":"张三","pwd":"123456"},
{"username":"李四","pwd":"12345"}
]
@pytest.mark.parametrize("login_user,login_pwd",data,indirect=True)
def test_more_fixture(login_user,login_pwd):
print("fixture返回的内容:",login_user,login_pwd)
执行结果
@pytest.fixture(scope="function")
def login_user(request):
user = request.param
yield user
print("账号:%s" % user)
@pytest.fixture(scope="function")
def login_pwd(request):
pwd = request.param
yield pwd
print("密码:%s" % pwd)
name= ["张三","李四"]
pwd = ["123456","12345"]
@pytest.mark.parametrize("login_user",name,indirect=True)
@pytest.mark.parametrize("login_pwd",pwd,indirect=True)
def test_more_fixture(login_user,login_pwd):
print("fixture返回的内容:",login_user,login_pwd)
执行结果
[参考文章] 小菠萝测试笔记