在做接口或 UI 自动化测试时,pytest 的 fixture(夹具) 是前置/后置管理、数据准备和资源复用的利器。
很多新手最迷惑的就是 scope(作用域),比如:
- scope="function" 是不是每个用例都用同一份?
- scope="class" 为什么模块级函数不共享?
- 怎么保证测试用例互不干扰?
今天我们就用 直觉 + 示例 + 面试回答 三步,把 fixture 作用域讲清楚。
一、fixture 基本语法回顾
python
import pytest
@pytest.fixture
def data_box():
print("\n👉 创建一份新数据")
box = {"count": 0}
yield box
print("👉 清理数据")
拆解
| 语法部分 | 作用 |
|---|---|
@pytest.fixture |
声明这是一个 fixture,可在测试函数中使用 |
def data_box(): |
函数名 = 测试函数里引用 fixture 的名字 |
| yield 前 | 前置操作,测试执行前运行 |
| yield 返回值 | 传给测试函数,作为参数使用 |
| yield 后 | 后置操作,测试执行后运行 |
口诀 :yield 前 = 前置,yield = 给用例,yield 后 = 后置 ✅
二、fixture scope 详解
pytest 提供四种作用域:
| scope | 意义 | 什么时候创建 fixture |
|---|---|---|
| function | 默认 | 每个测试函数/方法都会重新创建一次 |
| class | 类级 | 同一个类内所有方法共用一份 fixture |
| module | 文件级 | 同一个文件内所有测试函数共用一份 |
| session | 会话级 | 整个 pytest 运行过程中共享一份 |
三、scope=function:互不干扰
python
@pytest.fixture
def data_box():
box = {"count": 0}
yield box
python
def test_a(data_box):
data_box["count"] += 1
print("test_a:", data_box)
def test_b(data_box):
print("test_b:", data_box)
执行结果:
创建一份新数据
test_a: {'count': 1}
清理数据
创建一份新数据
test_b: {'count': 0}
清理数据
解释:
- 每个函数都会 重新创建 fixture
- 所以
test_a的修改不会影响test_b - function 作用域天然隔离,用于保证用例互不干扰
直观比喻:
- ❌ 错误:所有用例用同一桶水 → 污染
- ✅ 正确:每个用例接新水 → 独立互不影响
四、scope=class:类内共享
python
@pytest.fixture(scope="class")
def data_box():
box = {"count": 0}
yield box
注意 :class 作用域 只对类内方法生效,模块级函数不会共享。
python
class TestDemo:
def test_a(self, data_box):
data_box["count"] += 1
print("test_a:", data_box)
def test_b(self, data_box):
print("test_b:", data_box)
输出:
创建一份新数据
test_a: {'count': 1}
test_b: {'count': 1}
清理数据
✅ 两个方法共享同一个 data_box,互相影响。
五、scope=session 或 module:全局共享
python
@pytest.fixture(scope="session")
def data_box():
box = {"count": 0}
yield box
所有测试函数,无论是否在同一个文件或类中,都会共用同一份 fixture:
创建全局数据
test_a: {'count': 1}
test_b: {'count': 1} # 被污染
适合登录、数据库连接、全局客户端等场景,但要注意 清理数据。
六、常见踩坑
-
模块级函数 + scope="class"
- class 作用域只在类内方法生效,模块级函数还是每个用例新建。
-
使用全局变量
python
GLOBAL_BOX = {"count": 0}
@pytest.fixture
def bad_fixture():
yield GLOBAL_BOX # 所有用例都共用 GLOBAL_BOX
- 即使 function 作用域,也会污染用例。
- 正确做法:在 fixture 内新建对象。
- 操作数据库不清理
python
@pytest.fixture
def create_order():
order_id = insert_order_into_db()
yield order_id
delete_order(order_id) # ✅ 清理数据