一、 pytest自动生成测试类 demo
# -*- coding:utf-8 -*-
# @Author: 喵酱
# @time: 2023 - 08 -15
# @File: test4.py
# desc:
import pytest
import unittest
# 动态生成测试类
def create_test_class(class_name:str, test_cases:list) -> type:
"""
生成测试类
:param class_name: 测试类的类名,是一个字符串
:param test_cases: 是一个可迭代的对象(list),每一个元素case是一个字典
在pytest测试类中,每一个方法,就是一条case,所以这里将多条case数据,生成测试类的多个方法
:return: 测试类,包含多个方法(测试case)
"""
# 使用 type() 函数动态创建一个继承自 unittest.TestCase 的子类
test_class = type(class_name, (unittest.TestCase,), {})
for case in test_cases:
test_method = generate_test_method(case)
# 动态为测试类添加测试方法
setattr(test_class, test_method.__name__, test_method)
return test_class
# 生成测试方法
def generate_test_method(case: dict) -> callable:
"""
生成单个测试方法。
Args:
case (dict): 测试用例字典,包含输入和输出的信息。
Returns:
callable: 生成的测试方法。
"""
def test_method(self):
"""
实际执行测试的方法。
"""
assert case["input"] == case["output"]
# 设置测试方法的名称
test_method.__name__ = f"test_{case['name']}"
return test_method
# 定义测试数据
@pytest.fixture(params=[
{"name": "case1", "input": 1, "output": 1},
{"name": "case2", "input": 3, "output": 3},
])
def test_case(request):
return request.param
# 测试生成的测试类
@pytest.mark.usefixtures("test_case")
def test_generator(test_case):
test_class = create_test_class("DynamicTestClass", [test_case])
# 加载测试类,并创建测试套件
suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
# 运行测试套件
result = unittest.TextTestRunner().run(suite)
# 断言测试结果是否成功
assert result.wasSuccessful()
执行 test_generator 方法
运行顺序:
1、手动触发运行 test_generator 方法
2、在执行test_generator 方法 方法之前,会先执行test_case 方法。
3、test_case 方法,数据有2条数据,则test_case 方法会执行两次
4、test_generator 同样也会执行两次。
最终效果,就是生成了两次名为 DynamicTestClass 的测试类,每个测试类,只生成了一个方法(case)
二、代码详解
2.1 整体运行顺序
1、定义了一个函数 create_test_class,用于动态生成测试类。
2、定义了一个函数 generate_test_method,用于生成单个测试方法。
3、使用了 @pytest.fixture 装饰器定义了一个夹具(fixture) test_case,用于提供测试数据。
4、使用了 @pytest.mark.usefixtures("test_case") 装饰器标记了一个测试函数 test_generator,表示在执行该测试函数之前需要先执行 test_case 夹具。
5、在函数 test_generator 中,调用了 create_test_class 函数,传入一个测试数据。然后加载并运行生成的测试类。
6、执行测试时,先执行夹具函数 test_case 来获取测试数据。
7、根据测试数据,动态生成一个测试类 DynamicTestClass,并为该类添加一个测试方法。
8、创建测试套件,并运行测试套件中的测试用例。
9、使用 unittest.TextTestRunner 运行测试套件,并返回测试结果。
10、最后,根据测试结果判断是否成功,并进行断言。
总结起来,整个代码的执行流程是:先执行夹具函数获取测试数据,然后动态生成测试类并创建测试套件,最后运行测试套件中的测试用例,输出测试结果,并进行断言判断。
2.1 定义测试数据
# 定义测试数据
@pytest.fixture(params=[
{"name": "case1", "input": 1, "output": 1},
{"name": "case2", "input": 3, "output": 3},
])
def test_case(request):
return request.param
1、@pytest.fixture 是 pytest 框架提供的装饰器,用于定义夹具(fixture)。夹具是在测试函数或测试类执行前后进行特定操作、提供数据等的函数。
2、在这段代码中,test_case 是一个夹具函数。通过 @pytest.fixture 装饰器对其进行标记,表示它是一个夹具。
3、params 参数是用来定义多个测试用例数据的列表。每个测试用例数据都是一个字典,包含了三个键值对:name、input 和 output。name 是用来描述测试用例的名称,input 是输入数据,output 是预期输出结果。
4、当执行测试函数时,pytest 会自动调用夹具函数,并根据参数列表中的每个字典生成不同的测试数据。每个测试用例都会独立执行一次测试函数,并且将对应的测试数据作为参数传递给测试函数。
在这段代码中,夹具函数 test_case 返回 request.param,request.param 表示当前测试用例的数据。当执行 test_generator 函数时,会先调用夹具函数 test_case 获取测试数据,然后将该测试数据作为参数传递给 test_generator 函数。
2.2 运行生成的测试类 test_generator
# 测试生成的测试类
@pytest.mark.usefixtures("test_case")
def test_generator(test_case):
test_class = create_test_class("DynamicTestClass", [test_case])
# 加载测试类,并创建测试套件
suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
# 运行测试套件
result = unittest.TextTestRunner().run(suite)
# 断言测试结果是否成功
assert result.wasSuccessful()
1、装饰器: @pytest.mark.usefixtures("test_case")
@pytest.mark.usefixtures("test_case") 是 Pytest 测试框架中一个装饰器标记(decorator marker),用于指定在运行测试用例之前需要先执行的 fixture(夹具)。
fixture 是一种在测试运行之前准备测试环境、数据或者资源的机制。它可以用来为测试用例提供必要的前置条件,例如设置数据库连接、创建临时文件、初始化测试数据等。
在给定的代码中,@pytest.mark.usefixtures("test_case") 将 "test_case" 作为 fixture 的名称进行标记。这意味着在运行使用了该标记的测试用例之前,Pytest 将先执行名为 "test_case" 的 fixture 方法。
使用 @pytest.mark.usefixtures 标记可以方便地应用 fixture,而无需在每个测试用例函数中显式调用 fixture。通过将标记应用于测试用例函数,测试框架将自动处理 fixture 的执行和销毁,并将其提供给测试用例函数作为参数使用。
总之,@pytest.mark.usefixtures("test_case") 允许你在测试运行之前自动执行名为 "test_case" 的 fixture 方法,为测试用例提供必要的准备工作。
2.3 generate_test_method
generate_test_method
函数中的 case
参数是一个字典类型。在示例代码中,test_case
是一个包含测试用例信息的字典,其中包括字段 "name"
、"input"
和 "output"
,用于表示测试名称、输入和期望输出。
传递给 generate_test_method
函数的 case
参数应该具有与示例中定义的测试用例字典相同的结构。例如:
case = {"name": "example", "input": 1, "output": 2}
test_method = generate_test_method(case)
在这个示例中,case
是一个字典,表示一个测试用例,包含 "name"
、"input"
和 "output"
字段。generate_test_method
函数将根据这些字段生成一个测试方法,并为该方法设置名称。
2、create_test_class
在函数 create_test_class
中,test_cases
参数的类型可以是任何可迭代对象,比如列表或元组。
示例代码中使用了迭代循环来遍历 test_cases
,假设 test_cases
是一个列表,每个元素都是一个测试用例字典。所以 test_cases
的结构可以类似以下的形式:
test_cases = [
{"name": "case1", "input": 1, "output": 2},
{"name": "case2", "input": 3, "output": 3},
]
其中,每个 test_cases
列表的元素都是一个包含测试用例信息的字典。
所以,在调用 create_test_class
函数时,你可以传递一个包含测试用例字典的列表作为 test_cases
参数,动态生成相应的测试类。