有测试问题请微信联系作者,备注来意(点击此处添加)
240
一名普通的测试打工人;专注自动化测试技术研究、实践、总结、分享、交流。
用我多年的经历,给大家带来更多实用的干货。
人若有志,就不会在半坡停止。

【自动化测试基础】Pytest前后置处理

Pytest的前后置(固件、夹具)处理

有一些初始化配置和测试之后的收尾,只需要处理一次,这个时候我们就要用到夹具。

Pytest提供了以下几种setup和teardown方法:

  • setup_functionteardown_function: 用于每个测试函数
  • setup_methodteardown_method: 用于每个测试方法(类级别)
  • setup_module 和 teardown_module: 用于每个模块
  • setup_class 和 teardown_class: 用于每个类
  • setupteardown: 用于每个测试用例(一般通过fixture实现)
"""
# 测试用例前后置固件setup和teardown使用
"""
def setup_module():
    print("module前置")
def teardown_module():
    print("module后置")

# def setup_function():
#     print("函数前置")
# def teardown_function():
#     print("函数后置")
# 
# def test_03():
#     print("测试333")

class TestSetupTeardown:

    def setup(self):
        print("所有用例前置,一般通过fixture实现")
    def setup_class(self):
        print("类前置")
    def setup_method(self):
        print("方法前置")

    def test_01(self):
        print("测试111")
    def test_02(self):
        print("测试222")

    def teardown_method(self):
        print("方法后置")
    def teardown_class(self):
        print("类后置")
    def teardown(self):
        print("所有用例前置,一般通过fixture实现")
 
if __name__ == '__main__':
    pytest.main()       

执行结果:

继承方式实现前后置

# setup_teardown.py文件

"""封装setup、teardown前置后置"""
class SetupTeardown:
    def setup(self):
        print("所有用例前置,一般通过fixture实现")
    def setup_class(self):
        print("类前置")
    def setup_method(self):
        print("方法前置")

    def teardown_method(self):
        print("方法后置")
    def teardown_class(self):
        print("类后置")
    def teardown(self):
        print("所有用例前置,一般通过fixture实现")
# 测试用例.py文件

"""类继承方式"""
from setup_teardown import SetupTeardown
class TestSetupTeardown(SetupTeardown):

    def test_01(self):
        print("测试111")
    def test_02(self):
        print("测试222")

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

执行结果:

使用fixture装饰器来实现部分用例的前后置

import pytest

@pytest.fixture(scope="function")
def function_fixture():
    print("函数级fixture setup")
    yield
    print("函数级fixture teardown")

@pytest.fixture(scope="class")
def class_fixture():
    print("类级fixture setup")
    yield
    print("类级fixture teardown")

@pytest.fixture(scope="module")
def module_fixture():
    print("模块级fixture setup")
    yield
    print("模块级fixture teardown")

@pytest.fixture(scope="session")
def session_fixture():
    print("会话级fixture setup")
    yield
    print("会话级fixture teardown")

def test_one(function_fixture):
    print("执行test_one")
    assert True

class TestClass:
    def test_two(self, class_fixture):
        print("执行test_two")
        assert True

Pytest之Fixture前后置处理详解

使用pytest_generate_tests允许定义自定义参数化方案或扩展

通过数据驱动测试——Fixture参数化我们知道fixture可以使测试数据和测试用例分离,达到数据驱动测试的效果,
但是这样会有有个问题,测试数据集是直接写死在代码里的,
然而更多的时候测试数据的内容依赖于测试条件而变化,
所以固定的数据是无法满足我们的要求的。
好在pytest提供一个钩子函数pytest_generate_tests,它会在collection时执行,利用传入的参数metafunc为fixture赋值参数

param metafunc共有五个属性值

  • metafunc.fixturenames:参数化收集时的参数名称
  • metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象
  • metafunc.config:测试用例会话
  • metafunc.function:测试用例对象,即函数或方法对象
  • metafunc.cls: 测试用例所属的类的类对象

根据metafunc.config.getoption("ip_type")获取到命令行参数值,并根据不同的输入利用metafunc.parametrize对test_data赋值参数,达到了动态参数化的功能。

# conftest.py

# 自定义参数
def pytest_addoption(parser):
    parser.addoption("--ip_type", action="store", default="loopback",
                     help="ip type includes loopback, domain and local_network")

# pytest_generate_tests钩子函数
def pytest_generate_tests(metafunc):
    """
    根据测试配置或定义测试函数的类或模块中指定的值生成测试用例, 在测试用例参数化收集前调用此钩子函数
    :param metafunc:共有五个属性值
         metafunc.fixturenames:参数化收集时的参数名称 --- test_开头的测试用例方法的形参列表,如:test_case(test_data)
         metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象
         metafunc.config:测试用例会话
         metafunc.function:测试用例对象,即函数或方法对象
         metafunc.cls: 测试用例所属的类的类对象
    :return: none
    """
    if 'test_data' in metafunc.fixturenames:  # test_data --测试文件中测试方法的入参名;
        ip_type = metafunc.config.getoption("ip_type")
        if ip_type == "loopback":
            metafunc.parametrize("test_data", ["127.0.0.1"])  # 构造对应测试方法的入参

        elif ip_type == "local":
            metafunc.parametrize("test_data", ["192.168.1.1", "192.168.1.2"])
# test_04.py 测试用例文件

def test_case(test_data):
    print(test_data)
# 执行pytest

pytest.main(["-s", "--ip_type", "local", "testcase/test_04.py"])

执行结果:

高级用法

def pytest_generate_tests(metafunc):
    """
    根据测试配置或定义测试函数的类或模块中指定的值生成测试用例, 在测试用例参数化收集前调用此钩子函数
    :param metafunc:共有五个属性值
                     metafunc.fixturenames:参数化收集时的参数名称
                     metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象
                     metafunc.config:测试用例会话
                     metafunc.function:测试用例对象,即函数或方法对象
                     metafunc.cls: 测试用例所属的类的类对象
    :return: none
    """
    #print(metafunc.fixturenames)
    # 测试用例中有casename、caserequest、caseexpect这三个fixture时,开始为测试用例设置参数化
    if __GetRegionCoutry_testdata:
        return

    if all(list(filter(is_in_fixturename, metafunc.fixturenames))) and (list(filter(is_in_fixturename, metafunc.fixturenames))):
        # 所有测试函数的参数化的测试数据(测试用例名字、测试用例的测试数据、测试用例的预期结果)
        all_case_parametrize = []
        case_parametrize = []
        # 正在运行的测试函数的函数名或方法名
        current_function_name = metafunc.function.__qualname__
        # 正在检测的测试函数所在模块的名字
        current_function_in_module_name = metafunc.module
        # 从yaml中取出测试用例的测试数据
        testdata_dict = __GetRegionCoutry_testdata
        #print(testdata_dict.keys())
        # 从模块中提取不需要参数化的测试函数名字
        testcase = current_function_in_module_name.__dict__
        del testcase[current_function_name]
        if not testcase:
            raise NameError("testcase字典为空")

        not_parametrize_testcase = [key for key in testcase.keys() if "Rightrequest" in key]
        # 不需要参数化的测试函数所需的测试数据
        #__GetRegionCoutry_testdata = [dict((i, testdata_dict[i])) for i in not_parametrize_testcase if i in testdata_dict.keys()]
        if not not_parametrize_testcase:
            raise NameError("not_parametrize_testcase字典为空")

        # print(not_parametrize_testcase)
        for i in not_parametrize_testcase:
            if i in testdata_dict.keys():
                # 不需要参数化的测试数据
                #print(i)
                __GetRegionCoutry_testdata[i] = copy.deepcopy(testdata_dict[i])
                # 需要参数化的测试数据
                del testdata_dict[i]

        #print(__GetRegionCoutry_testdata.keys())
        #print("----------")
        #print(testdata_dict.keys())

        for key, value in testdata_dict.items():
            case_parametrize.clear()
            case_parametrize.append(key)
            case_parametrize.append(value["request"])
            case_parametrize.append(value["expect"])
            all_case_parametrize.append(tuple(case_parametrize))
        else:
            if not all_case_parametrize:
                raise NameError("all_casez_parametrize列表为空")
            # 在测试用例运行前,对测试函数进行参数化设置
            metafunc.parametrize("casename, caserequest, caseexpect", all_case_parametrize)
        print("---")
        # 垃圾回收
        del testdata_dict
        del testcase
        del current_function_name
        del not_parametrize_testcase
        del case_parametrize
        del all_case_parametrize

前置后置实际使用场景

  • 数据库连接和关闭
  • 文件打开和关闭
  • 用例前置和后置步骤、数据处理
# test_database.py
import pytest
import sqlite3

@pytest.fixture(scope="module")
def db_connection():
    print("创建数据库连接")
    conn = sqlite3.connect(":memory:")
    yield conn
    print("关闭数据库连接")
    conn.close()

@pytest.fixture(scope="function")
def db_cursor(db_connection):
    cursor = db_connection.cursor()
    print("创建表")
    cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
    yield cursor
    print("删除表")
    cursor.execute("DROP TABLE test")

def test_insert(db_cursor):
    db_cursor.execute("INSERT INTO test (name) VALUES ('Alice')")
    db_cursor.execute("SELECT * FROM test")
    result = db_cursor.fetchall()
    assert len(result) == 1
    assert result[0][1] == 'Alice'

def test_delete(db_cursor):
    db_cursor.execute("INSERT INTO test (name) VALUES ('Bob')")
    db_cursor.execute("DELETE FROM test WHERE name='Bob'")
    db_cursor.execute("SELECT * FROM test")
    result = db_cursor.fetchall()
    assert len(result) == 0

执行结果:

posted @ 2025-01-03 19:12  三叔测试笔记  阅读(277)  评论(0)    收藏  举报
返回顶部 跳转底部