pytest 写了用例却不跑、fixture 不生效、参数化全失效?9 个高频坑对照表
结论先行 :pytest 里 90% 的「用例没执行 / fixture 报 not found / 参数化没效果」,都不是框架 bug,而是命名约定没踩准 或作用域理解错了。本文用 9 组「症状 → 原因 → 修法」对照表,把最常见的坑一次讲清楚。
坑 1:文件名不规范,pytest 一个用例都收集不到
症状 :明明写了 test_login(),运行 pytest 显示 collected 0 items。
原因 :pytest 默认只收集匹配 test_*.py 或 *_test.py 的文件。
| 文件名 | 是否被收集 |
|---|---|
test_login.py |
✅ 是 |
login_test.py |
✅ 是 |
login.py |
❌ 否 |
TestLogin.py |
❌ 否(大小写敏感) |
修法 :改文件名;或在 pytest.ini 里改约定:
ini
[pytest]
python_files = check_*.py
坑 2:类名写了 Test,用例还是不执行
症状:类里有测试方法,但报告里一条都没有。
原因 :三条硬约定缺一不可------类名以 Test 开头、方法名以 test_ 开头、类不能定义 __init__。
python
class TestLogin: # ✅ 类名必须以 Test 开头
def __init__(self): # ❌ 有 __init__ 会导致 pytest 跳过整个类
...
def test_ok(self): # ✅ 方法必须以 test_ 开头
assert True
修法 :删掉 __init__,初始化逻辑改用 fixture。
坑 3:fixture 'xxx' not found
症状 :E fixture 'db' not found。
原因 :fixture 必须定义在测试文件内 或该目录及其父目录的 conftest.py 中。放到兄弟目录、或放到 utils.py 里都不会被自动发现。
| 定义位置 | 可见范围 |
|---|---|
| 测试文件内部 | 仅本文件 |
同级 conftest.py |
本目录 |
父目录 conftest.py |
所有子目录 |
兄弟目录的 conftest.py |
❌ 不可见 |
修法 :把 fixture 提到上层 conftest.py;跨目录复用就往上提一级。
坑 4:fixture 被同名覆盖,你调的不是你以为的那个
症状:fixture 逻辑改了但测试结果没变。
原因 :pytest 的覆盖优先级是 测试文件内的定义 > 同目录 conftest > 父目录 conftest。同名时,近的赢,远的被静默覆盖。
修法 :改名区分,或用 pytest --fixtures 看实际生效的是哪个。
坑 5:@pytest.mark.parametrize 参数名写错
症状 :TypeError: test_xxx() got an unexpected keyword argument。
原因 :装饰器第一个字符串里的名字,必须与函数形参逐一对应。
python
# ❌ 声明了 a,b,形参是 x,y
@pytest.mark.parametrize("a,b", [(1, 2)])
def test_add(x, y):
...
# ✅ 对应
@pytest.mark.parametrize("a,b", [(1, 2)])
def test_add(a, b):
assert a + b == 3
坑 6:mock.patch 路径打错,mock 根本没生效
症状:patch 了函数,但真实代码还是被调用了(跑真实网络请求/写库)。
原因 :patch 的目标是使用处的名字,不是定义处的名字。
python
# mymod.py 里写的是:from utils import send
# ❌ patch 定义处
mock.patch("utils.send")
# ✅ patch 使用处(mymod 命名空间里的 send)
mock.patch("mymod.send")
小结 :一句话记住------在哪用,就在哪打补丁。
坑 7:tmp_path 和 tmpdir 混用
症状 :tmp_path.exists() 报错,或字符串拼接失败。
| fixture | 类型 | 用法 |
|---|---|---|
tmp_path |
pathlib.Path |
tmp_path / "a.txt"(推荐) |
tmpdir |
py.path.local |
tmpdir.join("a.txt")(旧项目) |
修法 :新代码一律用 tmp_path,/ 运算符拼路径,别用 os.path.join。
坑 8:用例之间互相污染
症状:单独跑通过,一起跑就失败;换顺序跑结果还不一样。
原因 :三类共享状态------环境变量 、模块级单例/全局变量 、未清理的临时数据库。
python
# ✅ 用 monkeypatch 改环境变量,用例结束自动还原
def test_env(monkeypatch):
monkeypatch.setenv("MODE", "test")
修法 :优先用 monkeypatch;单例类在 fixture 里重建;数据库用 fixture 建表 + teardown 删表。
坑 9:异常断言写错,断言被吞掉
症状:代码抛异常但测试仍通过,或反过来永远通过。
原因 :把 pytest.raises 写成普通调用,或用了裸 except。
python
# ❌ 没当上下文管理器用,断言不生效
pytest.raises(ValueError, func())
# ✅ 正确写法
with pytest.raises(ValueError, match="invalid"):
func()
速查小结
| 症状 | 第一怀疑对象 |
|---|---|
| collected 0 items | 文件名 / 类名 / 方法名约定 |
| fixture not found | conftest.py 位置 |
| 参数化报错 | parametrize 名字与形参不匹配 |
| mock 不生效 | patch 路径打在了定义处 |
| 单跑过、一起跑挂 | 用例间状态污染 |
记住三句话 :命名约定决定能不能被收集 ;conftest 位置决定 fixture 可见范围 ;patch 打在使用处。踩坑时按这三句倒查,基本都能在两分钟内定位。