1. 概述
在软件开发中,经常需要测试不同参数下的函数执行结果是否符合预期。在Python中,我们可以使用pytest进行单元测试。pytest是一种基于Python的单元测试框架,提供了一系列测试工具,其中包括参数化功能。该功能允许我们针对同一个测试用例,传入不同的参数进行测试,从而减少我们编写大量重复测试用例的工作。本文主要介绍pytest中的参数化功能及其使用方法。
2. 参数化使用方法
在pytest中,参数化主要通过pytset.mark.parametrize()实现。下面是该函数的定义:
def pytest.mark.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None, **kwargs):
pass
函数的参数说明如下:
argnames: 字符串或字符列表,用于指定每组参数的名称。多个参数名用逗号分隔。
argvalues: 参数值列表,每个元素是一组参数值列表,它们分别对应参数名称。多个参数值列表用元组或列表包裹。
indirect: 如果为True,则将argnames中的参数视为fixture名称,并从fixture中获取其值。
ids: 为其设置的每个元素或每组参数值列表创建ID。
scope: 参数化的范围,可选值是"function"、"class"和"module"。
3. 简单示例
让我们通过一个简单的示例来了解参数化功能的使用方法。假设我们要测试一个函数add(),该函数接收两个参数,返回两个参数的和。我们首先编写一个不使用参数化的测试用例:
import pytest
def add(x, y):
return x + y
def test_add():
assert add(1, 2) == 3
assert add(0, 0) == 0
assert add(-1, 1) == 0
上述测试用例通过三个assert语句对函数add()进行了测试,每个assert语句都针对不同的输入参数。使用参数化功能,我们可以将这几个测试用例合并为一个:
import pytest
def add(x, y):
return x + y
@pytest.mark.parametrize("x,y,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0)
])
def test_add(x, y, expected):
assert add(x, y) == expected
参数化函数pytest.mark.parametrize()接收三个参数,其中argnames="x,y,expected"指定了输入参数的名称,argvalues=[(1, 2, 3), (0, 0, 0), (-1, 1, 0)]表示输入参数的取值。
在测试用例函数test_add()中,我们使用了三个参数x、y和expected,它们与参数化函数中的argnames对应。然后,我们使用断言语句来验证函数add()的结果是否符合预期。
4. fixture参数化
在实际测试中,有时我们需要使用多个fixture来协同工作,但fixture本身也可能需要参数。在pytest中,可以使用参数化功能来提供fixture的参数:
import pytest
@pytest.fixture(params=["a", "b", "c"])
def input_value(request):
return request.param
def test_mytest(input_value):
assert isinstance(input_value, str)
上面的代码中,我们定义了一个名为input_value的fixture,并在fixture定义中使用了参数化。fixture接收params参数,并以request.param的形式返回当前参数值。在我们的测试用例test_mytest()中,我们使用了input_value fixture来生成输入参数。我们还使用了isinstance()函数来验证input_value的类型是否为字符串。
5. 参数化数据源
在前面的示例中,我们使用了一个硬编码数据源,将测试用例中的多个值直接放在参数化函数中。但是,在实际情况下,数据源可能以csv、json或数据库的形式存储。在这种情况下,我们可以使用pytest.mark.parametrize()处理这些数据源。
例如,我们有一个csv文件包含如下数据:
username,password
John,pass123
Jane,secret456
Bob,topsecret789
我们可以使用如下的代码进行测试:
import pytest
import csv
def read_csv():
with open("users.csv") as f:
return [{k: v for k, v in row.items()} for row in csv.DictReader(f)]
@pytest.mark.parametrize("user", read_csv())
def test_login(user):
assert user["password"] != ""
在上述代码中,我们编写了一个名为read_csv()的函数,该函数从users.csv文件加载数据并将数据转换为一个字典列表。我们将这个函数传递给参数化函数,从而将csv中的每行数据转换为一个测试用例。最后,我们使用断言语句来确保加载的密码不为空。
6. 参数化范围
pytest.mark.parametrize()函数中的scope参数指定了参数化的范围,可选值是“function”(默认值)、“class”和“module”。
我们可以通过设置参数化范围来控制测试用例的执行次数。
当使用“function”范围参数化时,pytest为每组参数分别运行测试用例。当使用“class”范围时,同样如此,但每个测试类只被实例化一次。当使用“module”范围时,所有测试用例都将使用同一组参数运行一次。
我们可以使用scope参数将测试用例拆分为多个不同范围的测试用例:
import pytest
users = [("user1", "password1"), ("user2", "password2")]
@pytest.mark.parametrize("username, password", users, scope="module")
def test_login(username, password):
assert password != ""
@pytest.mark.parametrize("username, password", users, scope="function")
def test_login_2(username, password):
assert username != ""
在上述代码中,我们指定了两个不同范围的测试用例程序。对于第一个test_login()函数,我们使用了“module”范围参数化,这意味着它只会运行一次,并适用于所有测试用例。对于第二个test_login_2()函数,我们使用了“function”范围,这意味着它将在每个测试用例上运行。
7. 总结
在本文中,我们学习了pytest中的参数化功能,它可以帮助我们减少编写重复测试用例的工作。
我们已经看到了如何使用参数化函数pytest.mark.parametrize()来传递多组参数值并运行相同的测试用例。我们还看到了如何在fixture和数据源上使用参数化功能。
最后,我们还了解了如何在不同的范围中控制参数化测试用例的执行次数。这是一种非常有用的技术,可以帮助我们在单元测试中更加灵活地使用参数化。