pin_drop当前位置:知识文库 ❯ 图文
Python 测试夹具完全指南 - 高效管理测试资源
一、测试夹具概述
测试夹具(Test Fixture)是测试框架中用于管理测试前置准备和后置清理的机制。它的核心作用是确保每个测试都在一致、干净的环境中执行,避免测试之间的相互影响。
常见的测试夹具场景包括:
-
数据库连接:测试前创建连接,测试后关闭连接
-
临时文件:测试前创建临时文件,测试后删除
-
测试数据:提供标准化的测试数据集
-
Mock 服务:模拟外部 API 或第三方服务
-
Web 客户端:测试前初始化 HTTP 客户端或浏览器实例
二、unittest 中的 setUp/tearDown
1. 方法级别的夹具
代码示例
import unittest
class TestDatabase(unittest.TestCase):
def setUp(self):
"""每个测试方法执行前调用"""
self.db = {
"connection": "connected",
"tables": {}
}
print(f"\nsetUp: 初始化数据库连接")
def tearDown(self):
"""每个测试方法执行后调用"""
self.db["connection"] = "disconnected"
print(f"tearDown: 关闭数据库连接")
def test_create_table(self):
self.db["tables"]["users"] = []
self.assertIn("users", self.db["tables"])
def test_insert_data(self):
self.db["tables"]["users"] = [{"id": 1, "name": "张三"}]
self.assertEqual(len(self.db["tables"]["users"]), 1)2. 类级别的夹具
代码示例
import unittest
class TestSharedResource(unittest.TestCase):
"""类级别夹具 - 整个测试类只执行一次"""
@classmethod
def setUpClass(cls):
"""测试类开始前执行一次"""
cls.shared_resource = {"data": "shared across all tests"}
print("\nsetUpClass: 初始化共享资源")
@classmethod
def tearDownClass(cls):
"""测试类结束后执行一次"""
cls.shared_resource = None
print("tearDownClass: 清理共享资源")
def test_one(self):
self.assertEqual(self.shared_resource["data"], "shared across all tests")
def test_two(self):
self.assertIn("data", self.shared_resource)3. 执行顺序
代码示例
# unittest 夹具执行顺序:
# setUpClass() → 整个类执行前(1次)
# setUp() → 每个测试方法前(N次)
# test_method() → 测试方法本身
# tearDown() → 每个测试方法后(N次)
# tearDownClass() → 整个类执行后(1次)三、pytest 的 Fixture 系统
1. 基本 Fixture
代码示例
import pytest
@pytest.fixture
def sample_user():
"""最简单的 fixture - 返回测试数据"""
return {
"username": "testuser",
"email": "test@example.com",
"role": "user"
}
def test_user_data(sample_user):
"""通过参数名自动注入 fixture"""
assert sample_user["username"] == "testuser"
assert "@" in sample_user["email"]2. 带 Setup 和 Teardown 的 Fixture
代码示例
import pytest
@pytest.fixture
def database():
"""使用 yield 实现 setup 和 teardown"""
# setup 阶段
print("\n[Setup] 创建数据库连接")
db = {
"connection": True,
"data": {}
}
# yield 给测试使用
yield db
# teardown 阶段
print("[Teardown] 关闭数据库连接")
db["connection"] = False
def test_insert(database):
database["data"]["key1"] = "value1"
assert database["connection"] is True
assert database["data"]["key1"] == "value1"3. Fixture 依赖其他 Fixture
代码示例
import pytest
@pytest.fixture
def db_connection():
"""数据库连接 fixture"""
conn = {"status": "connected"}
yield conn
conn["status"] = "disconnected"
@pytest.fixture
def user_table(db_connection):
"""依赖 db_connection 的 fixture"""
assert db_connection["status"] == "connected"
table = {"name": "users", "rows": []}
return table
def test_insert_user(user_table):
user_table["rows"].append({"id": 1, "name": "张三"})
assert len(user_table["rows"]) == 1四、Fixture 作用域详解
pytest 的 fixture 支持5种作用域,控制 fixture 的执行频率:
代码示例
import pytest
@pytest.fixture(scope="function")
def func_fixture():
"""每个测试函数执行一次(默认)"""
print("\n[function] setup")
yield
print("[function] teardown")
@pytest.fixture(scope="class")
def class_fixture():
"""每个测试类执行一次"""
print("\n[class] setup")
yield
print("[class] teardown")
@pytest.fixture(scope="module")
def module_fixture():
"""每个测试模块执行一次"""
print("\n[module] setup")
yield
print("[module] teardown")
@pytest.fixture(scope="session")
def session_fixture():
"""整个测试会话只执行一次"""
print("\n[session] setup")
yield
print("[session] teardown")
class TestScopes:
def test_1(self, func_fixture, class_fixture, module_fixture, session_fixture):
assert True
def test_2(self, func_fixture, class_fixture, module_fixture, session_fixture):
assert True提示:选择合适的作用域可以显著提升测试性能。对于耗时的资源初始化(如数据库连接),使用 session 或 module 级别避免重复创建。
五、高级用法与实战技巧
1. conftest.py 共享 Fixture
代码示例
# conftest.py
import pytest
@pytest.fixture
def mock_api_client():
"""所有测试文件都可以使用的共享 fixture"""
class MockAPIClient:
def get(self, url):
return {"status": 200, "data": {}}
def post(self, url, data):
return {"status": 201, "id": 1}
return MockAPIClient()
# tests/test_user.py
# 无需任何导入,直接使用 mock_api_client
def test_get_user(mock_api_client):
response = mock_api_client.get("/users/1")
assert response["status"] == 2002. 参数化 Fixture
代码示例
import pytest
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db_type(request):
"""参数化 fixture,会自动运行多次"""
return request.param
def test_database_connection(db_type):
"""这个测试会执行3次,每次使用不同的数据库类型"""
print(f"\n测试连接 {db_type}")
assert db_type in ["sqlite", "postgresql", "mysql"]3. 自动使用 Fixture
代码示例
import pytest
@pytest.fixture(autouse=True)
def log_test_execution(request):
"""自动应用的 fixture,无需在测试函数中声明"""
print(f"\n开始执行: {request.node.name}")
yield
print(f"执行完成: {request.node.name}")
def test_auto_1():
# log_test_execution 自动执行
assert True
def test_auto_2():
# log_test_execution 自动执行
assert True练习1
创建一个临时目录管理的 fixture,在每个测试前创建临时目录,测试后自动清理。使用 pytest 的 tmp_path 内置 fixture 实现。
练习2
为一个 Web 应用测试编写夹具系统,包含:数据库连接(session级别)、认证用户(function级别)、测试客户端(function级别),并在测试中组合使用它们。
六、课程小结
-
夹具保证测试独立性:每个测试在干净、一致的环境中执行
-
unittest 使用 setUp/tearDown:方法级别和类级别的夹具机制
-
pytest Fixture 更灵活:通过依赖注入,支持 yield 实现 setup/teardown
-
作用域控制执行频率:function/class/module/package/session 五种级别
-
conftest.py 共享配置:在测试文件间复用 fixture
常见问题
fixture 的 scope 选择有什么原则?
优先使用最小作用域(function)保证测试独立性。对于创建成本高的资源(数据库连接、浏览器实例),可以考虑 module 或 session 级别。但要注意高作用域的 fixture 可能导致测试间状态污染。
如何在 fixture 中处理异常?
在 yield 前使用 try/finally 确保资源被正确清理。pytest 也支持在 fixture 中使用 addfinalizer 注册清理函数,即使 setup 阶段抛出异常也会被调用。
autouse=True 的 fixture 有什么使用场景?
autouse 适用于全局性的操作,如测试日志记录、性能计时、数据库事务回滚等。它会在匹配范围内的每个测试自动执行,无需手动声明。
pytest 有哪些内置的 fixture?
常用内置 fixture 包括:tmp_path(临时目录)、tmpdir(旧版临时目录)、capfd(捕获标准输出)、monkeypatch(修改环境变量)、pytestconfig(访问配置)等。运行 pytest --fixtures 可查看所有可用 fixture。
本文涉及AI创作
内容由AI创作,请仔细甄别