夹具函数
夹具函数使用@pytest.fixture 装饰器:
详细解释
1. @pytest.fixture 装饰器的作用
python
@pytest.fixture # 这个装饰器告诉 pytest:这是一个 fixture
def db_connection():
"""创建测试数据库连接"""
conn = sqlite3.connect(':memory:')
yield conn # 暂停点:把 conn 提供给测试函数
conn.close() # 恢复点:测试结束后继续执行
@pytest.fixture 装饰器将普通函数标记为 pytest 的 fixture,使其具有特殊的能力。
2. yield conn 的含义
yield conn 做了两件事:
-
提供资源:将
conn(数据库连接对象)传递给使用这个 fixture 的测试函数 -
暂停执行:函数在
yield处暂停,等待测试函数执行完毕
执行流程演示
python # tests/conftest.py import pytest import sqlite3 @pytest.fixture def db_connection(): """创建测试数据库连接""" print("\n1. 开始创建数据库连接") conn = sqlite3.connect(':memory:') print("2. 数据库连接已创建") yield conn # 在这里暂停,把 conn 交给测试函数 print("4. 测试结束,开始清理") conn.close() print("5. 数据库连接已关闭") @pytest.fixture def db_cursor(db_connection): """创建数据库游标""" print("3. 创建游标") cursor = db_connection.cursor() yield cursor # 把 cursor 交给测试函数 print("6. 关闭游标") cursor.close() python # tests/test_example.py def test_database(db_cursor): print(">>> 测试函数执行中") db_cursor.execute("SELECT 1") result = db_cursor.fetchone() assert result == (1,) print(">>> 测试函数执行完毕")
运行时的输出顺序:
text 1. 开始创建数据库连接 2. 数据库连接已创建 3. 创建游标 >>> 测试函数执行中 >>> 测试函数执行完毕 6. 关闭游标 4. 测试结束,开始清理 5. 数据库连接已关闭
流程解释:
当 db_cursor fixture 声明依赖 db_connection 时,pytest 会:
-
先执行
db_connectionfixture 函数 -
获取
yield conn中 yield 出来的值 -
将这个值作为参数传递给
db_cursor
@pytest.fixture def db_connection(): conn = sqlite3.connect(':memory:') # conn 是 sqlite3.Connection 对象 yield conn # yield 出去的是 sqlite3.Connection 对象 @pytest.fixture def db_cursor(db_connection): # 参数 db_connection 接收的是 sqlite3.Connection 对象 cursor = db_connection.cursor() # 调用 Connection 对象的 cursor() 方法 yield cursor cursor.close()
完整的执行流程
# 步骤分解 @pytest.fixture def db_connection(): # 第1步:执行到这里 conn = sqlite3.connect(':memory:') # 第2步:yield 暂停,把 conn(Connection对象)传递给依赖它的 fixture 或测试 yield conn # 第5步:测试结束后,从 yield 处恢复,执行清理 conn.close() @pytest.fixture def db_cursor(db_connection): # 第3步:db_connection 参数 = conn(Connection对象) # 调用 Connection 对象的 cursor() 方法创建游标 cursor = db_connection.cursor() # 第4步:yield 暂停,把 cursor(Cursor对象)传递给测试函数 yield cursor # 第6步:测试结束后,关闭游标 cursor.close()
夹具函数的作用:
夹具函数的主要作用
1. 代码复用(最直接的作用)
python # 没有夹具:每个测试都要重复 def test_user_create(): conn = sqlite3.connect(':memory:') cursor = conn.cursor() cursor.execute("CREATE TABLE users (id INTEGER, name TEXT)") # ... 测试逻辑 cursor.close() conn.close() def test_user_update(): conn = sqlite3.connect(':memory:') # 重复! cursor = conn.cursor() # 重复! cursor.execute("CREATE TABLE users (id INTEGER, name TEXT)") # 重复! # ... 测试逻辑 cursor.close() # 重复! conn.close() # 重复! # 使用夹具:一次定义,多处使用 @pytest.fixture def db_cursor(): conn = sqlite3.connect(':memory:') cursor = conn.cursor() cursor.execute("CREATE TABLE users (id INTEGER, name TEXT)") yield cursor cursor.close() conn.close() def test_user_create(db_cursor): # 直接使用,无需重复 pass def test_user_update(db_cursor): # 直接使用,无需重复 pass
2. 资源管理和清理
@pytest.fixture def temp_file(): # 创建资源 file = tempfile.NamedTemporaryFile(delete=False) file.write(b"test data") file.close() yield file.name # 自动清理 os.unlink(file.name) @pytest.fixture def mock_server(): # 启动服务 server = start_server() yield server # 停止服务 server.stop()
3. 依赖管理
@pytest.fixture def app(): """基础应用实例""" app = create_app() return app @pytest.fixture def client(app): """测试客户端,依赖 app""" return app.test_client() @pytest.fixture def auth_client(client): """已认证的客户端,依赖 client""" client.login(username="test", password="test") return client
4. 测试隔离
@pytest.fixture def isolated_db(): """每个测试都有独立的数据库""" conn = sqlite3.connect(':memory:') # 内存数据库,自动隔离 yield conn conn.close() def test_case1(isolated_db): isolated_db.execute("INSERT INTO users VALUES (1, 'Alice')") # 只影响自己的数据库 def test_case2(isolated_db): # 看不到 test_case1 的数据 result = isolated_db.execute("SELECT COUNT(*) FROM users").fetchone() assert result[0] == 0 # 表是空的
复用范围的控制
1. 不同作用域的复用
# session 级别:整个测试会话复用一次 @pytest.fixture(scope="session") def expensive_resource(): """耗时操作只执行一次""" print("创建昂贵的资源(只执行一次)") resource = create_expensive_resource() return resource # module 级别:每个测试模块复用一次 @pytest.fixture(scope="module") def module_db(): """模块内所有测试共享一个数据库连接""" conn = sqlite3.connect(':memory:') yield conn conn.close() # function 级别:每个测试函数独立 @pytest.fixture(scope="function") # 默认 def fresh_db(): """每个测试获取新的数据库""" conn = sqlite3.connect(':memory:') yield conn conn.close()
2. conftest.py 的层级复用
project/ ├── conftest.py # 全局夹具,所有测试可用 ├── tests/ │ ├── conftest.py # tests/ 目录的夹具 │ ├── test_user.py │ └── api/ │ ├── conftest.py # 只影响 api/ 目录 │ └── test_api.py
# project/conftest.py - 全局 @pytest.fixture def global_config(): return load_global_config() # tests/conftest.py - tests 目录 @pytest.fixture def db_connection(): return create_db() # tests/api/conftest.py - api 目录专用 @pytest.fixture def api_client(): return APIClient()
夹具复用 vs 普通函数复用
普通函数方式
def create_test_db(): """普通辅助函数""" conn = sqlite3.connect(':memory:') cursor = conn.cursor() return conn, cursor def test_user_create(): conn, cursor = create_test_db() try: # 测试逻辑 pass finally: cursor.close() conn.close() # 手动清理,容易忘记 def test_user_update(): conn, cursor = create_test_db() # 复用了创建逻辑 try: # 测试逻辑 pass finally: cursor.close() # 但清理还是要手动 conn.close()
夹具方式
@pytest.fixture def db_cursor(): """夹具:创建+清理一体""" conn = sqlite3.connect(':memory:') cursor = conn.cursor() yield cursor cursor.close() # 自动清理 conn.close() def test_user_create(db_cursor): # 只需要声明依赖,自动创建和清理 pass def test_user_update(db_cursor): # 同样自动处理 pass
实际案例:不同层级的复用
# 基础夹具:可被多个模块复用 @pytest.fixture def app(): """创建应用实例""" app = create_app(config='test') return app @pytest.fixture def db(app): """基于应用创建数据库""" with app.app_context(): db = create_db(app.config['DATABASE']) yield db db.drop_all() # 用户模块复用 def test_create_user(app, db): user = User(name='Alice') db.session.add(user) db.session.commit() assert user.id is not None # 订单模块也复用 def test_create_order(app, db): order = Order(user_id=1, product='Book') db.session.add(order) db.session.commit() assert order.id is not None # 完全不同的测试也能复用 def test_app_config(app): assert app.config['TESTING'] is True
总结
夹具函数的作用确实是代码复用,但这种复用包含了:
-
创建逻辑的复用:不用重复写初始化代码
-
清理逻辑的复用:自动处理资源释放
-
依赖关系的复用:自动处理夹具间的依赖
-
配置的复用:统一的测试环境配置
夹具让代码复用更加系统化和自动化,而不仅仅是提取公共函数那么简单。

浙公网安备 33010602011771号