一、前言
写用例脚本时,只有测试数据和期望结果不一样,但操作步骤是一样的测试用例,可以用参数化。pytest允许在多个级别启用测试参数化
- pytest.fixture() 允许fixture有参数化功能
- @pytest.mark.parameterize 允许在测试函数或类中定义多组参数和fixtures
- pytest_generate_tests 允许定义自定义参数化方案或扩展
二、未参数化的示例代码
1 def test_one(): 2 assert 3 + 5 == 8 3 4 def test_two(): 5 assert 2 + 4 == 6 6 7 def test_three(): 8 assert 3 *
以上三个用例都是加法计算,然后断言和,重复写3个类似的用例有点冗余
三、参数化的示例代码
1 @pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+4",6),("3*4",11)]) 2 def test_eval(test_input,expected): 3 print(f"测试数据{test_input},期望结果{expected}") 4 assert eval(test_input) == expected
执行结果:
we can see:只有一条用例,但是利用参数化输入三组不同的测试数据和期望结果,执行用例数也是3个,节省很多代码
五、工作思考
1、写登录模块的用例脚本时,根据等价类划分,我们一般会测试账号和密码正确、账号不存在、密码错误、账号为空、密码为空和账号密码都为空
2、这些用例的区别在于输入的测试数据和对应的交互结果
3、我们可以只写一条登录测试用例,然后把多组测试数据和期望结果参数化,节省代码量
六、源码分析
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
argnames
源码解析:a comma-separated string denoting one or more argument names, or a list/tuple of argument strings.
含义:参数名字
格式:字符串"arg1,arg2,arg3"【需要用逗号分隔】
备注:源码中写了可以是参数字符串的list或者tuple,但博主实操过是不行的,不知道是不是写的有问题,大家可以看看评论下
示例
@pytest.mark.parametrize(["name", "pwd"], [("yy1", "123"), ("yy2", "123")])
@pytest.mark.parametrize(("name", "pwd"), [("yy1", "123"), ("yy2", "123")])
@pytest.mark.parametrize("name,pwd", [("yy1", "123"), ("yy2", "123")])
备注
有朋友提出现在上面标注错的两个可以正常使用了
argvalues
源码解析:
The list of argvalues determines how often a test is invoked with different argument values.
If only one argname was specified argvalues is a list of values.【只有一个参数,则是值列表】
If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname.【如果有多个参数,则用元组来存每一组值】
含义:参数值列表
格式:必须是列表,如:[ val1,val2,val3 ]
如果只有一个参数,里面则是值的列表如:@pytest.mark.parametrize("username", ["yy", "yy2", "yy3"])
如果有多个参数例,则需要用元组来存放值,一个元组对应一组参数的值,如:@pytest.mark.parametrize("name,pwd", [("yy1", "123"), ("yy2", "123"), ("yy3", "123")])
备注:虽然源码说需要list包含tuple,但我试了下,tuple包含list,list包含list也是可以的........
ids
含义:用例的ID
格式:传一个字符串列表
作用:可以标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
强调:ids的长度需要与测试数据列表的长度一致
indirect
作用:如果设置成True,则把传进来的参数当函数执行,而不是一个参数
七、装饰测试类
1 import pytest 2 data_1 = [(1,2,3),(4,5,9)] 3 @pytest.mark.parametrize('a,b,expected',data_1) 4 class TestParametrize: 5 def test_parametrize(self,a,b,expected): 6 print("\n测试函数11111 测试数据为\n{}{}.format(a,b") 7 assert a + b == expected 8 9 def test_parametrize_two(self,a,b,expected): 10 print("\n测试函数22222 测试数据为\n{}-{}".format(a,b)) 11 assert a + b == expected
执行结果:
Notes:当装饰器@pytest.mark.parametrize装饰测试类时,会将数据集合(比如上面的data_1)传递给类的所有测试用例方法
八、多个参数化装饰器--举例笛卡尔积
1 import pytest 2 3 # 类似于笛卡尔积,有序组合 4 data_one = [1,2,3] 5 data_two = ["a","b"] 6 @pytest.mark.parametrize("a",data_one) 7 @pytest.mark.parametrize("b",data_two) 8 def test_parametrize_one(a,b): 9 print(f"\n笛卡尔积 测试数据为:{a},{b}")
执行结果:
Notes:
- 一个函数或一个类可以装饰多个 @pytest.mark.parametrize
- 这种方式,最终生成的用例数是n*m,比如上面的代码就是:参数a的数据有3个,参数b的数据有2个,所以最终的用例数有3*2=6条
九、参数化,传入字典数据
1 import pytest 2 # 字典 3 data_one = ( 4 { 5 "user":1, 6 "pwd":2 7 }, 8 { 9 "user":3, 10 "pwd":4 11 } 12 ) 13 14 @pytest.mark.parametrize("dic",data_one) 15 def test_parametrize_one(dic): 16 print(f"测试数据为\n{dic}") 17 print(f'user:{dic["user"]},pwd:{dic["pwd"]}')
执行结果:
十、参数化,标记数据
1 import pytest 2 3 # 标记参数化 4 @pytest.mark.parametrize("test_input,expected",[("3+5",8), 5 ("2+4",6), 6 pytest.param("3*4",11,marks=pytest.mark.xfail), 7 pytest.param("3*5",16,marks=pytest.mark.skip) 8 ]) 9 def test_mark(test_input,expected): 10 assert eval(test_input) == expected
执行结果:
十一、参数化,增加可读性
1 import pytest 2 3 # 增加可读性 4 data_one = [(3,5,8),(2,4,6)] 5 6 # ids 7 ids = ["a:{} + b:{} = expected:{}".format(a,b,expected) for a,b,expected in data_one] 8 @pytest.mark.parametrize("a,b,expected",data_one,ids=ids) 9 class Testparametrize: 10 def test_parametrize(self,a,b,expected): 11 print("\n测试函数1 测试数据为\n{}-{}".format(a,b)) 12 assert a + b == expected
执行结果:
Notes:多少组数据,就要有多少个id,然后组成一个id的列表,作用主要是为了更加清晰看到用例的含义。
参考链接:https://www.cnblogs.com/poloyy/p/12675457.html