- @pytest.fixture():允许对 fixture 函数进行参数化
- @pytest.mark.parametrize:允许在测试函数、方法进行参数化
- Pytest_generate_tests:允许定义自定义参数化方案和扩展
前面已经讲过fixture的参数化了,本章再次复习一下!
fixtrue参数化
"""conftest.py""" import pytest user = ('TOM', 'JEMI', 'JEERY') age = (12, 15, 18) @pytest.fixture(params=user) def user_data(request): yield request.param @pytest.fixture(params=age) def age_data(request): yield request.param
"""test_a.py""" def test_user(user_data): print("TEST USER",user_data) def test_age(age_data): print("TEST DATA",age_data) # def test_user_data(user_data,age_data): # print("test_user and test_age",user_data,age_data) """ Case/test_a.py::test_user[TOM] TEST USER TOM PASSED Case/test_a.py::test_user[JEMI] TEST USER JEMI PASSED Case/test_a.py::test_user[JEERY] TEST USER JEERY PASSED Case/test_a.py::test_age[12] TEST DATA 12 PASSED Case/test_a.py::test_age[15] TEST DATA 15 PASSED Case/test_a.py::test_age[18] TEST DATA 18 PASSED """
还有一种情况就是上述代码的注释部分,运行后,它会将各种组合都跑一遍:
Case/test_a.py::test_user_data[TOM-12] test_user and test_age TOM 12 PASSED Case/test_a.py::test_user_data[TOM-15] test_user and test_age TOM 15 PASSED Case/test_a.py::test_user_data[TOM-18] test_user and test_age TOM 18 PASSED Case/test_a.py::test_user_data[JEMI-12] test_user and test_age JEMI 12 PASSED Case/test_a.py::test_user_data[JEMI-15] test_user and test_age JEMI 15 PASSED Case/test_a.py::test_user_data[JEMI-18] test_user and test_age JEMI 18 PASSED Case/test_a.py::test_user_data[JEERY-12] test_user and test_age JEERY 12 PASSED Case/test_a.py::test_user_data[JEERY-15] test_user and test_age JEERY 15 PASSED Case/test_a.py::test_user_data[JEERY-18] test_user and test_age JEERY 18 PASSED
parametrize参数化
入门级
import pytest user = ('TOM', 'JEMI', 'JEERY') @pytest.mark.parametrize('user',user) def test_parametrize(user): print("USER",user)
❝❞
- 第一个参数为名称,在后面的方法中会使用到,如 param1
- 第二个参数为值,任何数据类型都可以,不过有些需要做一些处理,例如字典:
import pytest user = {"name1":'TOM', "name2":'JEMI', "name3":'JEERY'} @pytest.mark.parametrize('user',user.values()) def test_parametrize(user): print("USER",user) """ Case/test_a.py::test_parametrize[TOM] USER TOM PASSED Case/test_a.py::test_parametrize[JEMI] USER JEMI PASSED Case/test_a.py::test_parametrize[JEERY] USER JEERY PASSED """
你大可以理解为parametrize走了一个for循环的过程,将只循环出来然后赋值给了自定义的变量。
多参数
import pytest users = [('TOM',12), ('JEMI',15), ('JEER',18)] @pytest.mark.parametrize('user,age',users) def test_parametrize(user,age): print("USER AND AGE",user,age) """ Case/test_a.py::test_parametrize[TOM-12] USER AND AGE TOM 12 PASSED Case/test_a.py::test_parametrize[JEMI-15] USER AND AGE JEMI 15 PASSED Case/test_a.py::test_parametrize[JEER-18] USER AND AGE JEER 18 PASSED """
与fixture结合
import pytest sex = ['男','女','女'] @pytest.fixture(params=sex) def sex_data(request): yield request.param users = [('TOM',12), ('JEMI',15), ('JEER',18)] @pytest.mark.parametrize('user,age',users) def test_parametrize(user,age,sex_data): print("USER AND AGE",user,age,sex_data)
Case/test_a.py::test_parametrize[\u7537-TOM-12] USER AND AGE TOM 12 男 PASSED Case/test_a.py::test_parametrize[\u7537-JEMI-15] USER AND AGE JEMI 15 男 PASSED Case/test_a.py::test_parametrize[\u7537-JEER-18] USER AND AGE JEER 18 男 PASSED Case/test_a.py::test_parametrize[\u59730-TOM-12] USER AND AGE TOM 12 女 PASSED Case/test_a.py::test_parametrize[\u59730-JEMI-15] USER AND AGE JEMI 15 女 PASSED Case/test_a.py::test_parametrize[\u59730-JEER-18] USER AND AGE JEER 18 女 PASSED Case/test_a.py::test_parametrize[\u59731-TOM-12] USER AND AGE TOM 12 女 PASSED Case/test_a.py::test_parametrize[\u59731-JEMI-15] USER AND AGE JEMI 15 女 PASSED Case/test_a.py::test_parametrize[\u59731-JEER-18] USER AND AGE JEER 18 女 PASSED
❝\u7537以及\u59730是Unicode编码,可以不需要理会。不论时候结合fixture,只要带fixture多参数化作用在同一函数就会带来了不一样的结果,也造成了非预期结果,两个取合适的就行。
❞
编码问题
如果非常介意Unicode编码问题,那么如下的解决方案:
"""conftest.py中修改""" def pytest_collection_modifyitems(items): """ 测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上 :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")
ids取别名
与fixture有些近似,还是那句话,如果有编码问题,采用上面的方式。
import pytest sex = ['男','女','女'] @pytest.fixture(params=sex,ids=["性别1","性别2","性别3"]) def sex_data(request): yield request.param users = [('TOM',12), ('JEMI',15), ('JEER',18)] @pytest.mark.parametrize('user,age',users,ids=["用户1","用户2","用户3"]) def test_parametrize(user,age,sex_data): print("USER AND AGE",user,age,sex_data)
Case/test_a.py::test_parametrize[性别1-用户1] USER AND AGE TOM 12 男 PASSED Case/test_a.py::test_parametrize[性别1-用户2] USER AND AGE JEMI 15 男 PASSED Case/test_a.py::test_parametrize[性别1-用户3] USER AND AGE JEER 18 男 PASSED Case/test_a.py::test_parametrize[性别2-用户1] USER AND AGE TOM 12 女 PASSED Case/test_a.py::test_parametrize[性别2-用户2] USER AND AGE JEMI 15 女 PASSED Case/test_a.py::test_parametrize[性别2-用户3] USER AND AGE JEER 18 女 PASSED Case/test_a.py::test_parametrize[性别3-用户1] USER AND AGE TOM 12 女 PASSED Case/test_a.py::test_parametrize[性别3-用户2] USER AND AGE JEMI 15 女 PASSED Case/test_a.py::test_parametrize[性别3-用户3] USER AND AGE JEER 18 女 PASSED
这样就能比较明白的看到了,不过显然,看如上的结果,每个用户三种性别显然不合适。所以,在用法上,切记注意了。此外,它可以跟fixture一样,在ids中使用推导式哦
多个parametrize作用在同一个用例
与多个fixture作用在同一个函数上类似
users = ['TOM', 'JEMI', 'JEER'] ages = [15, 18, 20] @pytest.mark.parametrize('user', users, ids=["用户1", "用户2", "用户3"]) @pytest.mark.parametrize('age', ages, ids=["年龄1", "年龄2", "年龄3"]) def test_parametrize(user, age): print("USER AND AGE", user, age)
Case/test_a.py::test_parametrize[年龄1-用户1] USER AND AGE TOM 15 PASSED Case/test_a.py::test_parametrize[年龄1-用户2] USER AND AGE JEMI 15 PASSED Case/test_a.py::test_parametrize[年龄1-用户3] USER AND AGE JEER 15 PASSED Case/test_a.py::test_parametrize[年龄2-用户1] USER AND AGE TOM 18 PASSED Case/test_a.py::test_parametrize[年龄2-用户2] USER AND AGE JEMI 18 PASSED Case/test_a.py::test_parametrize[年龄2-用户3] USER AND AGE JEER 18 PASSED Case/test_a.py::test_parametrize[年龄3-用户1] USER AND AGE TOM 20 PASSED Case/test_a.py::test_parametrize[年龄3-用户2] USER AND AGE JEMI 20 PASSED Case/test_a.py::test_parametrize[年龄3-用户3] USER AND AGE JEER 20 PASSED
也是把参数中的每一个值都进行的匹配。所以说,这种近似类似的写法,在不同的时候选择不同的用法。
间接参数化
@pytest.fixture def data(request): return request.param @pytest.mark.parametrize("data",["清安",'拾贰'],indirect=True) def test_data(data): print("name:",data)
Case/test_a.py::test_data[清安] name: 清安 PASSED Case/test_a.py::test_data[拾贰] name: 拾贰 PASSED
有发现这与上面所讲的叠加参数化以及混合使用的区别了吗。这样的方式可以帮助我们更加精准的执行想要的参数用例,而不是存在一些混合的场景参数。此外,你还可以直接在data函数中做数据处理及运算,然后将返回值给到测试用例,一起看看:
@pytest.fixture def data(request): return request.param * 2 @pytest.mark.parametrize("data",["清安",'拾贰'],indirect=True) def test_data(data): print("name:",data) """ Case/test_a.py::test_data[清安] name: 清安清安 PASSED Case/test_a.py::test_data[拾贰] name: 拾贰拾贰 PASSED """
指定间接参数
@pytest.fixture def data(request): return request.param * 2 @pytest.fixture def age(request): return request.param * 2 information = [('清安',8), ('拾贰',9)] @pytest.mark.parametrize("data,age",information,indirect=['age']) def test_data(data,age): print("name:",data) print("age:",age) """ Case/test_a.py::test_data[清安-8] name: 清安 age:16 PASSED Case/test_a.py::test_data[拾贰-9] name: 拾贰 age:18 PASSED """
看到了吗,indirect可以指定某个fixture运行,同时不影响参数传递。
为单个参数化测试设置标记或测试ID
import pytest user = {"username1":"QINGAN",'username2':"清安"} @pytest.mark.parametrize( "data,name",[ ("10+2", 12), pytest.param("清"+"安",user['username2'], marks=pytest.mark.basic), pytest.param("QINGAN",user['username1'],marks=pytest.mark.basic,id="basic_user1") ] ) def test_eval(data,name): assert data == name print(data,name) """ Case/test_a.py::test_eval[清安-清安] 清安 清安 PASSED Case/test_a.py::test_eval[basic_user1] QINGAN QINGAN PASSED ======================= 2 passed, 1 deselected in 0.02s ======================= """
记得在pytest.ini亦或者其他配置文件中注册mark标志。此处是只跑了basic标志的,所以只有两个用例。
❝这里有新的知识点,也就是pytest.param,以及pytest.param中的id。看似写的多,其实真的写的多,实际应用上,很少会这样写。