1、pytest可以在多个地方定义参数化
pytest.fixture
fixture可以定义参数化pytest.mark.parametrize
可以让测试函数和类定义多组参数和fixturepytest_generate_tests
可以定义自定义参数化方案或扩展
2、参数化场景
测试数据和期望结果不一样,但是操作步骤都一样的测试用例可以使用参数化。举个例子:
- 未参数化代码
def test_01(): assert 3 + 5 == 9 def test_02(): assert 2 + 4 == 6 def test_03(): assert 2 + 9 == 42
- 利用参数化优化后的代码,执行效果同上
@pytest.mark.parametrize("input, expected"), [["3+5", 9], ["2+4", 6], ["2+9", 42]]) def test_04(input, expected): assert eval(input) == expected
实际Web UI自动化的开发场景,比如一个登录框
- 你肯定需要测试多种情况:账号空、密码空、账号密码都为空、账号不存在、密码错误、密码正确等
- 以上多种情况的区别在于 输入的测试数据 和 对应的交互结果 不一样
- 这种情况下,我们就可以只写一条登录测试用例,把多组测试数据和期望结果参数化
2、源码分析
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"
- 列表
["arg1", "arg2", "arg3"]
- 元组
("arg1", "arg2", "arg3")
- 字符串
- 代码示例:
@pytest.mark.parametrize("name, passwd", [["ZhangSan", "zs123"], ["LiSi", "ls123"], ["WangEr", "we123"]]) @pytest.mark.parametrize(["name", "passwd"], [["ZhangSan", "zs123"], ["LiSi", "ls123"], ["WangEr", "we123"]]) @pytest.mark.parametrize(("name", "passwd"), [["ZhangSan", "zs123"], ["LiSi", "ls123"], ["WangEr", "we123"]])
argvalues
- 源码解析:
- The list of argvalues determines how often a test is invoked with different argument values.【argvalues列表确定使用不同参数值被测试用例调用的频率】
- If only one argname was specified argvalues is a list of values.【如果只声明了一个参数,那么argvalues是一个列表】
- If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname.【如果声明了N个参数,那么argvalues必须是一个存放元组的列表,元组中有N个值,每个值对应每个argname】
- 含义: 参数值列表
- 格式: 必须是一个列表,列表中的每个值代表一组测试数据
- 如果只有一个参数,直接使用列表存放参数值即可,可以这么表示:
@pytest.mark.parametrize("username", ["test01", "test02", "test03"])
- 如果有需要传入多个参数,则需要用元组来存放参数的值,一个元组代表一组参数的值,然后用列表来存放这些元组,可以这么表示:
@pytest.mark.paramatrize("username, passwd", [("ZhangSan", "zs123"), ("LiSi", "ls123"), ("WangEr", "we123")])
- 如果只有一个参数,直接使用列表存放参数值即可,可以这么表示:
ids
- 含义: 用例的ID
- 格式: 传一个字符串列表,长度需要与测试数据列表的长度一致
- 作用: 可以用来标识每一个测试用例,自定义每条测试数据执行结果的显示内容
indirect
- 作用: 如果设置成True,则会把传进来的参数当函数执行;否则就会当成参数处理。
3、常用场景
场景一:装饰测试类
- 代码:
data = [ (4, 5, 9), (1, 2, 3), (2, 4, 6) ] @pytest.mark.parametrize("a, b, expect", data) class TestParamatrize: def test_paramatrize_01(self, a, b, expect): print(f"测试函数为: 01, 测试数据为: {a}+{b}, 期望值为: {expect}") assert a + b == expect def test_parametrize_02(self, a, b, expect): print(f"测试函数为: 02, 测试数据为: {a}+{b}, 期望值为: {expect}") assert a + b == expect
- 结果:
============================= test session starts ============================== collecting ... collected 6 items test_01.py::TestParamatrize::test_paramatrize_01[4-5-9] PASSED [ 16%] 测试函数为: 01, 测试数据为: 4+5, 期望值为: 9 test_01.py::TestParamatrize::test_paramatrize_01[1-2-3] PASSED [ 33%] 测试函数为: 01, 测试数据为: 1+2, 期望值为: 3 test_01.py::TestParamatrize::test_paramatrize_01[2-4-6] PASSED [ 50%] 测试函数为: 01, 测试数据为: 2+4, 期望值为: 6 test_01.py::TestParamatrize::test_parametrize_02[4-5-9] PASSED [ 66%] 测试函数为: 02, 测试数据为: 4+5, 期望值为: 9 test_01.py::TestParamatrize::test_parametrize_02[1-2-3] PASSED [ 83%] 测试函数为: 02, 测试数据为: 1+2, 期望值为: 3 test_01.py::TestParamatrize::test_parametrize_02[2-4-6] PASSED [100%] 测试函数为: 02, 测试数据为: 2+4, 期望值为: 6 ============================== 6 passed in 0.08s ===============================
场景二:多个参数化装饰器,“笛卡尔积”
- 代码:
data_1 = [1, 2, 3] data_2 = ['a', 'b'] @pytest.mark.parametrize("a", data_1) @pytest.mark.parametrize("b", data_2) def test_parametrize_01(a, b): print(f"笛卡尔积,测试数据为: {a}-{b}")
- 结果:会产生3 x 2 = 6组结果
collecting ... collected 6 items test_01.py::test_parametrize_01[a-1] PASSED [ 16%] 笛卡尔积,测试数据为: 1-a test_01.py::test_parametrize_01[a-2] PASSED [ 33%] 笛卡尔积,测试数据为: 2-a test_01.py::test_parametrize_01[a-3] PASSED [ 50%] 笛卡尔积,测试数据为: 3-a test_01.py::test_parametrize_01[b-1] PASSED [ 66%] 笛卡尔积,测试数据为: 1-b test_01.py::test_parametrize_01[b-2] PASSED [ 83%] 笛卡尔积,测试数据为: 2-b test_01.py::test_parametrize_01[b-3] PASSED [100%] 笛卡尔积,测试数据为: 3-b
场景三:使用@pytest.param(values, marks, id)
标记数据
- 代码:
@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_02(test_input, expected): assert eval(test_input) == expected
- 结果:
=========================================================================== test session starts ============================================================================ collected 4 items test_01.py::test_parametrize_02[3+5-8] PASSED [ 25%] test_01.py::test_parametrize_02[2+4-6] PASSED [ 50%] test_01.py::test_parametrize_02[6*9-42] XFAIL [ 75%] test_01.py::test_parametrize_02[6*6-42] SKIPPED (unconditional skip) [100%] ================================================================= 2 passed, 1 skipped, 1 xfailed in 0.08s ==================================================================
场景四:参数化ids,增加可读性
- 代码:
data_3 = [ (1, 2, 3), (4, 5, 9) ] ids = [f"a: {a} + b: {b} = expect: {expect}" for a, b, expect in data_3] @pytest.mark.parametrize("a, b, expect", data_3, ids=ids) def test_parametrize_03(a, b, expect): assert a + b == expect
- 结果:
============================= test session starts ============================== collecting ... collected 2 items test_01.py::test_parametrize_03[a: 1 + b: 2 = expect: 3] PASSED [ 50%] test_01.py::test_parametrize_03[a: 4 + b: 5 = expect: 9] PASSED [100%] ============================== 2 passed in 0.04s ===============================
参考文章:https://www.cnblogs.com/poloyy/p/12675457.html