pytest使用@pytest.mark.parametrize()实现参数化

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和数据源上使用参数化功能。

最后,我们还了解了如何在不同的范围中控制参数化测试用例的执行次数。这是一种非常有用的技术,可以帮助我们在单元测试中更加灵活地使用参数化。

后端开发标签