Pytest 参数化用例
同学们大家好,这个章节我们来学习 pytest 框架中如何参数化用例。
简介
Pytest 中的参数化是一种非常有用的功能,通过参数的方式传递数据,从而实现数据和脚本分离。允许在同一个测试函数上多次运行相同的测试,使用不同的输入数据进而生成多个测试用例。
参数化就是允许使用不同的输入参数,来多次运行同一个测试方法,从而减少重复代码并提高测试覆盖率。Pytest 中当然也提供了参数化的功能,它允许我们将数据和脚本分离。通过参数化,我们可以用不同的数据多次运行同一个测试函数,这样就能自动生成多个测试用例,提高测试覆盖率,避免重复编写代码。
参数化的优点
-
简化测试代码:使用参数化测试,在测试代码中可以使用单个测试函数来覆盖多种不同的测试情况。这样可以避免编写大量重复的测试代码,并提高代码的可读性和维护性。
-
减少维护成本:如果需要对测试逻辑进行更改或添加更多测试用例,只需修改一个参数化测试案例,就可以覆盖所有相关的测试情况,减少了维护的工作量和成本。
从上面的描述中其实也能感受出来参数化的优点。其中最主要的优点就是简化测试代码。我们可以用一个测试函数来覆盖不同的测试情况,避免写大量重复的代码。第二个优点是减少维护成本,如果需要修改测试逻辑或增加测试用例,只需增加或者修改参数就可以,这样可以轻松扩展和维护。
使用方法
使用 @pytest.mark.parametrize()
装饰器为测试函数提供参数化数据。
@pytest.mark.parametrize(
"变量名",
[变量值],
ids=[用例名]
)
def test_demo(变量名):
result = 变量名
...
那在 pytest 中要如何进行参数化呢?其实只需要在测试函数上加上 @pytest点mark点 parametrize 装饰器,给它提供需要的参数和数据。参数的名字和对应的值会自动传给测试函数。大家可以看一下给出的案例,parametrize 中需要传递的第一个参数是个字符串,里面需要包含所有需要进行参数化的变量名称,如果有多个变量可以用逗号分隔。第二个参数需要传入对应变量的值,值和变量名称顺序要对应,可以用列表或者元组来管理。第三个参数可以通过 ids 来给每组测试数据命名,其实就是每个测试用例的名称,也需要通过列表来管理。然后被装饰的函数形参的位置要传入和第一个参数相同的变量名称,这样,测试方法内部就可以通过这些变量名使用传入的值了。
支持参数类型
Pytest 的参数化功能允许灵活地为测试函数提供不同的输入参数:
- 单参数
- 多参数
- 笛卡尔积
Pytest 中支持多种方式来提供参数化数据,像单参数、多参数以及笛卡尔积,都可以通过参数化来实现。
传入单参数
单参数,可以将数据放在列表或者元组中。
search_list = ['appium','selenium','pytest']
@pytest.mark.parametrize('name',search_list)
def test_search(name):
assert name in search_list
先来看看如何传入单参数。我们可以简单地通过列表或元组来传入一个参数。例如,我们有一个搜索列表 appium, selenium, pytest,可以通过参数化依次检查这些值是否在列表中,避免编写多个重复的测试函数。只需要在被装饰的测试函数上方使用 @pytest点mark点parametrize 装饰器。装饰器的第一个参数是传入数据的变量名称,需要放在字符串中。这个变量名称也需要传入函数的形参列表中。装饰器中第二个参数,就需要把搜索列表传入进去。这样函数内部就可以通过变量名称来使用每一个传入的值了。
运行结果:
接下来我们执行一下参数化的测试用例,可以看到 Pytest 会为每个输入数据生成一个独立的测试用例,这样就能看到每个数据的测试结果,方便检查每种输入的情况。
多参数情况
将数据放在列表嵌套元组或者列表中。
# 数据放在元组中
@pytest.mark.parametrize(
"test_input,expected",
[
("3+5",8),
("2+5",7),
("7+5",12)
]
)
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
# 数据放在列表中
@pytest.mark.parametrize(
"test_input,expected",
[
["3+5",8],
["2+5",7],
["7+5",12]
]
)
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
如果我们的测试需要多个参数,也可以传入多个参数。我们可以将数据放在列表或元组中,Pytest 会分别用不同的参数值来执行测试。比如这个例子中,我们需要传入的参数是 2 个。那在 @pytest点mark点parametrize 装饰器中的第一个参数还是一个字符串,这个字符串要把所有需要传入的变量名称包含进去,不同的变量名称之间用逗号分隔,总之最后是通过一个字符串传入装饰器的。同样,这些变量名也要传入函数的形参列表。装饰器的第二个参数需要用列表或者元组嵌套的方式。也就是说每一组测试数据都要放到一个列表或者元组中,然后这些数据再统一放到一个总的列表或元组中传入装饰器。而且内部的列表中还支持测试简单的数学表达式,比如这个例子中的 3加5,2加5 等。
运行结果:
写好之后执行一下看看。就像上面的例子一样,Pytest 会依次运行每一组数据,确保不同的参数组合都能通过测试,并显示每次运行的结果。
用例重命名 - 添加 ids 参数
通过 ids 参数,将别名放在列表中。注意,ids 列表参数的个数要与参数值的个数一致。
@pytest.mark.parametrize(
"test_input,expected",
[
("3+5",8),
("2+5",7),
("7+5",12)
],
ids=['add_3+5=8', 'add_2+5=7','add_3+5=12']
)
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
从上面的执行结果可以看出来,默认情况下,参数化的用例会使用测试数据为用例命名。如果我们想要单独给测试用例起名,还可以通过装饰器中的 ids 参数。这样测试报告中就能看到更加清晰、易懂的用例名称,方便后续查看和分析。ids 参数接收的是一个列表,里面使用字符串传入每个测试用例的名称就可以。不过这里一定要注意,如果使用了 ids 命名,那么名字和传入的数据比如能对应上才可以,也就是每条用例都必须传入名字,如果数字不匹配就会报错了。
注意:当别名为中文时,会显示为 unicode 编码,需要自定义插件解决。
这里还有个问题大家要注意,ids 中传入的值如果是中文的话,会显示为 unicode 编码,看起来像是乱码,这个需要自定义插件解决。我们后面再介绍。
笛卡尔积
-
两组数据:
- a=[1,2,3]
- b=[a,b,c]
-
对应有几种组合形势 ?
- (1,a),(1,b),(1,c)
- (2,a),(2,b),(2,c)
- (3,a),(3,b),(3,c)
除了上面的情况,参数化还支持笛卡尔积。笛卡尔积是指多个参数组合的所有可能组合。比如,我们有两个数据集,一个是 a 列表,里面包含 1,2,3,另一个是 b 列表,里面包含 a,b,c,那么就会生成 9 种不同的组合情况。Pytest 可以通过两个 @pytest点mark点parametrize 装饰器来实现笛卡尔积参数化。
代码示例:
@pytest.mark.parametrize(
"b",["a","b","c"]
)
@pytest.mark.parametrize(
"a",[1,2,3]
)
def test_param1(a, b):
print(f"笛卡积形式的参数化中 a={a} , b={b}")
比如具体可以这样来编写。这个测试方法上我们就使用两个装饰器,分别传入 a 和 b 参数的值。
运行结果:
在执行笛卡尔积参数化时,Pytest 会遍历每一组数据组合,依次执行对应的测试,并打印出每个组合的测试结果。我们可以看到参数的所有可能组合被测试了。
下面给大家演示一下具体操作。进入项目之后,我们先创建一个测试文件,比如文件名就叫做 test params。在测试文件中,我们可以先来定义一个搜索关键词的列表 search list。然后我们可以在列表中放入一些常见工具的名称,比如 appium selenium pytest 这些。先来看一个单参数的场景。先定义一个测试函数 test search,传入参数 name,它的值通过参数化的方式把 search list 中的值传进来。测试函数中要断言一下 name 的值在 search list 中。接下来使用 @pytest点mark点 parametrize 完成参数化,第一个参数把需要做参数化的变量名放在字符串中传进来。第二个参数需要一个可迭代对象,这里传入 search list。接下来执行一下。可以看到每个数据都生成了一条测试用例,并且都执行成功了。
./assets/Pytest参数化用例.mp4
再来看一个多参数化的场景,这里先使用列表来组织数据。我们还是先来定义一个测试函数,比如说叫做 test mark more。这个测试用例里准备验证一下传入的算数表达式计算之后的结果和预期是否一致。所以需要传入两个参数,比如说这些一个叫 test input,另一个是预期结果叫 expected。因为 test input 是字符串,不能直接计算,所以需要使用一下 eval 方法去把我们传入的这个字符串去进行一个运算。然后断言计算结果能够等于预期结果。接下来完成一下多参数的参数化。使用 pytest点mark点parametrize。然后第一个参数把参数名称给它传过来,然后我们使用列表来组织数据,第一组数据比如说 3 加 5,然后预期结果就是8。然后比如说我们给他改成 2 加 5,预期它等于7,比如说这个 7 加 5 预期它等于12。设置完之后执行一下,,看看三组数据也已经成功执行完毕了。
./assets/Pytest参数化用例.mp4
下面我们再来一组多参数,这次使用元组来组织数据。那还是先来定义一个测试函数,比如就叫 test mark more2。测试场景和上面的一样,所以测试函数中也需要同样的数据,比如 test input 还有预期结果 expected。测试步骤也是一样的。参数化组织数据的形式我们来变一下。使用 pytest点mark点parametrize。然后第一组参数还是先把我们的这个参数的名称放过来。然后下面我们就是用元组来组织我们的数据。第一组把值放进来,可以和上面的用例一致,也是 2 加 5 等于 7,然后这个比如说 7 加 5 还是等于12。大家看这就是用元组来组织数据的方法。我们再来执行一下。大家看也能够成功地实现参数化。然后我们再来给它加上一个命名。要单独命名的话,就需要用到 ids 这个参数。它也需要传入一个列表,那比如说第一个我们就给它起名叫做 3 加 5,等于。这个名字你可以随便起,我们在这儿就统一下格式,比如说这个是 2 加 5 等于 7,然后第三个我们是 7 加 5 等于 12。加上名字之后再执行一下看看,可以看到我们前面的这个名字就是我们指定的这个顺序了。如果说你少了一个名字再执行,它就会报错了。所以说如果要添加命名,名字的数量和测试数据的数量一定要是匹配的。
./assets/Pytest参数化用例.mp4
我们再来看看笛卡尔积的一个场景。那比如说我们定义一个测试函数。函数名就叫做 test params1。它需要接受两个参数 a 和 b。这个函数我们先不做断言,简单把两个参数的值打印一下就可以了。比如打印一下笛卡尔积形式的参数化中,a 等于,我们把 a 放进来,然后 b 等于,把 b 放进来。接下来我们来实现参数化。先用 pytest点mark点parametrize,把 b 的值给它传进来。比如说我们给它传一个列表,里面包含字符串 a、b、c。好,那然后我们再用一个装饰器,用 pytest点mark点parametrize,这回我们再给它把 a 传进来,那 a 的话也传一个列表,里面包含数字 1 2 3。设置好之后下面来执行一下。可以看到我们在这里面一共出现了 9 条测试用例,它们是通过笛卡尔积的形式完成了这样的一个组合。
./assets/Pytest参数化用例.mp4
总结
- 参数化测试用例的优点。
- 参数化测试用例的实现方案。
最后来总结一下,通过参数化,Pytest 提供了更加灵活和高效的测试方式,让我们能够通过少量的代码覆盖更多的测试场景。参数化不仅简化了测试代码,还减少了维护成本。好了,关于参数化的用法先介绍这么多。