parameterized:Python强大的参数化测试功能

parameterized库介绍

parameterized是一个Python库,提供了简洁而强大的接口来实现参数化测试。支持多种Python测试框架,包括nosepytestunittest。这意味着无论你使用哪种测试框架,都可以轻松集成parameterized来享受参数化测试带来的便利。

安装parameterized

使用pip来安装:

pip install parameterized

使用示例

下面是一些使用parameterized进行参数化测试的示例。

使用@parameterized装饰器

假设你有一个简单的函数add(a, b),你希望测试它在不同输入下的行为。你可以使用@parameterized装饰器来定义一个参数化测试:

from parameterized import parameterized
import unittest

class TestMathFunctions(unittest.TestCase):

    @parameterized.expand([(1, 2, 3), (4, 5, 9), (-1, -1, -2)])
    def test_add(self, a, b, expected):
        self.assertEqual(a + b, expected)

if __name__ == '__main__':
    unittest.main()

示例中,@parameterized.expand装饰器接受一个包含输入参数和预期结果的列表。对于列表中的每组参数,测试框架都会生成一个名为test_add_xxx的测试用例,其中xxx是参数的哈希值或索引(取决于parameterized的版本)。然后,测试框架会运行这些测试用例,并验证a + b的结果是否与expected相等。

使用param()函数

有时,你可能希望为参数提供更具描述性的名称,或者在参数之间建立更复杂的依赖关系。这时,可以使用param()函数来定义参数:

from parameterized import parameterized, param
import unittest

class TestMathFunctions(unittest.TestCase):

    @parameterized.expand([
        param(a=1, b=2, expected=3, id='test_add_positive_numbers'),
        param(a=4, b=-2, expected=2, id='test_add_positive_and_negative'),
        param(a=-1, b=-1, expected=-2, id='test_add_negative_numbers')
    ])
    def test_add_with_params(self, a, b, expected):
        self.assertEqual(a + b, expected)

if __name__ == '__main__':
    unittest.main()

示例中,param()函数允许你为每个参数指定一个名称,并使用id关键字为生成的测试用例指定一个唯一的标识符。这使得测试报告更加清晰,并有助于你快速定位失败的测试用例。

pytest中使用parameterized

虽然parameterized最初是为noseunittest设计的,但它也完全兼容pytest。只需将测试类和方法定义为通常的pytest风格,并使用@parameterized.expand装饰器来添加参数:

import pytest
from parameterized import parameterized, param

@pytest.mark.parametrize('test_input,expected', [
    (param(1, 2, 3, id='test_add_1_2_3'),),  # Note the extra comma for a single-element tuple
    (param(4, 5, 9, id='test_add_4_5_9'),),
    (param(-1, -1, -2, id='test_add_negative'),),
])
def test_add_with_pytest(test_input, expected):
    a, b, result = test_input  # Unpack the param object
    assert a + b == result

# Alternatively, you can use the @parameterized.expand decorator directly:
class TestMathWithPytest:

    @parameterized.expand([
        param(a=1, b=2, expected=3, id='test_add_class_1_2_3'),
        param(a=4, b=5, expected=9, id='test_add_class_4_5_9'),
    ])
    def test_add_in_class(self, a, b, expected):
        assert a + b == expected

请注意,在pytest中使用@parameterized.expand时,需要将参数作为一个元组传递给param()函数(即使只有一个参数),并在测试函数中解包该元组。这是因为pytest的参数化机制期望每个测试用例都接收一个固定的参数列表。然而,从parameterized 0.7.0版本开始,可以直接在pytest测试函数上使用@parameterized.expand装饰器,而无需将参数包装在元组中。这在类方法中尤其有用,因为它允许更直观地传递和访问参数。

另外,虽然上面的示例中使用了pytest.mark.parametrize来演示如何在pytest中手动参数化测试,但通常使用@parameterized.expand装饰器来获得更好的集成和更简洁的代码。

parameterized的高级用法

除了基本的参数化测试之外,parameterized还提供了许多高级功能,以满足更复杂的测试需求。

动态生成测试用例

有时,可能希望根据某些条件动态生成测试用例。这时,可以将参数列表定义为一个返回迭代器的函数,并将其传递给@parameterized.expand装饰器:

import itertools
from parameterized import parameterized
import unittest

def generate_test_cases():
    for a in range(1, 4):
        for b in range(1, 4):
            yield a, b, a * b

class TestMultiplication(unittest.TestCase):

    @parameterized.expand(generate_test_cases())
    def test_multiply(self, a, b, expected):
        self.assertEqual(a * b, expected)

if __name__ == '__main__':
    unittest.main()

示例中,generate_test_cases()函数使用两个嵌套的for循环来生成所有可能的(a, b)组合,并计算它们的乘积作为预期结果。然后,@parameterized.expand装饰器接受这个函数作为参数,并为每组生成的参数运行测试。

自定义测试用例名称

默认情况下,parameterized会为生成的测试用例分配一个基于参数哈希值或索引的名称。但是,可能希望使用更具描述性的名称来更好地反映测试的目的。这时,可以使用name_func关键字参数来自定义测试用例名称:

from parameterized import parameterized
import unittest

def custom_name_func(testcase_func, param_num, param):
    return f"{
      
      testcase_func.__name__}_{
      
      param.args[0]}_{
      
      param.args[1]}"

class TestCustomization(unittest.TestCase):

    @parameterized.expand([(1, 2), (3, 4)], name_func=custom_name_func)
    def test_with_custom_name(self, a, b):
        self.assertTrue(a < b)  # Just an example assertion

if __name__ == '__main__':
    unittest.main()

示例中,custom_name_func函数接受三个参数:testcase_func(要测试的函数)、param_num(参数的索引)和param(一个包含参数值的对象)。然后,它返回一个字符串作为测试用例的名称。在这个例子中,测试用例的名称将是test_with_custom_name_1_2test_with_custom_name_3_4

与其他测试框架的集成

除了unittestpytest之外,parameterized还支持与nose集成。只需按照通常的方式编写测试,并使用@parameterized.expand装饰器来添加参数即可。parameterized会自动处理与nose的集成细节。