pytest 两个目录都有 test_api.py,收集报 import file mismatch
两个测试目录中的同名文件一起收集报 import file mismatch,可选择唯一名称、完整包布局或 pytest 6.0 起的 importlib 模式,并检查辅助导入和实际收集范围。
pytest 收集测试时提示 import file mismatch,日志里 imported module 的 __file__ 指向一个目录,而 want to collect 指向另一个目录;两个文件都叫 test_api.py。它们分别单独运行正常,放在一起收集才失败时,先检查测试文件是否被当成同名的顶层模块导入。
默认 prepend,以及 append 导入模式,在测试目录没有包结构时,会把测试模块放进同一个全局导入命名空间。第一个 test_api 已经进入 sys.modules,第二个同名文件就可能与它冲突。日志里的缓存清理提示不能证明原因一定是旧 .pyc 文件。
先看两个路径是否属于真实的同名文件
下面是容易发生冲突的示例布局,两个分组目录都没有 __init__.py:
project/
tests/
unit/
test_api.py
integration/
test_api.py
对照错误中的两个绝对路径,确认它们都是本轮计划收集的文件,并查看实际 pytest 配置、导入模式和启动目录。两个目录确实各有同名文件时,应处理模块身份;切换为 python -m pytest 会增加当前目录到搜索路径,但不自动让这些同名文件拥有不同身份。
若日志指向已删除、移动后的文件,或指向另一份安装目录,应按那个线索检查残留缓存、旧工作副本和搜索路径。只有一个当前测试文件时,不要套用“重复文件名”判断,更不要删除另一个环境的文件来让错误暂时消失。
保留默认导入模式时,给模块不同身份
最直接的做法是把文件命名为 test_api_unit.py 和 test_api_integration.py。变更前检查 CI 的指定文件路径、IDE 配置和测试间的显式导入,重命名后同步调整这些入口。只改磁盘文件名,可能让原有选定测试命令找不到文件。
另一种方式是把测试目录组织成常规 Python 包,例如:
project/
tests/
__init__.py
unit/
__init__.py
test_api.py
integration/
__init__.py
test_api.py
这样 pytest 可以按 tests.unit.test_api 和 tests.integration.test_api 导入,叶文件名相同也有不同的限定名称。这里强调的是完整的包层级与可导入名称,不是给任意一个目录补文件就能解决全部冲突。
包布局会影响搜索路径和现有导入。尤其在测试已安装发布包的任务中,应确认加载的仍是那份安装包,没有因仓库目录进入 sys.path 而改成当前工作副本。同名的顶层 tests 包、辅助模块遮蔽和命名空间包规则,也需要按实际项目检查。
pytest 6.0 起,可以评估 importlib 模式
不希望改文件名或调整包布局时,可先在隔离的测试环境试用下面的收集命令。它用于查看收集结果,不执行测试函数;导入测试模块及插件仍可能有初始化副作用:
pytest --import-mode=importlib --collect-only
importlib 模式不会为了导入测试模块而修改 sys.path,并能根据测试文件位置产生不同模块名,因此不要求非包测试文件的 basename 全局唯一。它从 pytest 6.0 起提供,旧版本需要先评估工具升级,不能照抄一个未支持的选项。
它也有代价:测试模块之间不能依赖这套导入机制自由互相导入,测试目录中的普通辅助模块也不会因此自动可导入。需要直接导入的辅助代码应按项目结构放进可导入的应用或工具包。fixtures 可以放在 conftest.py,由 pytest 的发现机制提供,不必为了共享 fixture 把一个测试模块当库导入。
确认兼容后,可以把下面的片段合并到既有 pytest.ini。已有 addopts 时追加该选项,不覆盖原来的其他参数:
[pytest]
addopts = --import-mode=importlib
仍要核对 CI、IDE 与本地是否读取同一份配置,以及插件有没有自行预先导入测试文件。普通模块的同名遮蔽、安装路径错误或插件额外导入,不保证仅切换 importlib 就全部解决。
成功标准包含两个测试组都被收集
用原来一起运行两个目录的入口复查,确认 import file mismatch 消失,两个分组的预期测试都出现在收集结果中,再按项目正常流程执行测试。只剩一个目录被发现,也可能没有报错,但不算修复完成。
检查应用包和辅助代码的实际加载路径,确保测试目标没有变化。发生新的导入错误时,回到所选方案的包路径与辅助模块边界处理,不用把所有源码目录塞进 PYTHONPATH 来掩盖。
修改前保存文件布局、配置和 CI 入口。需要回退时恢复这一组变更;不能只删新加的 __init__.py,却保留依赖它的导入语句。缓存确需清理时,先确认它属于该工作副本,再按项目清理流程处理,避免误动虚拟环境或其他项目。


