Python 测试用例如何避免相互污染?

测试用例相互污染源于共享可变全局状态,应通过函数级fixture、隔离外部依赖、显式清理和禁用隐式共享来避免。

测试用例相互污染,通常是因为共享了可变的全局状态、类变量、单例对象、缓存、数据库连接或文件系统资源。避免的关键是让每个测试用例运行在独立、可预测、可重置的环境中。

使用函数作用域的 fixture 替代模块/类级状态

pytest 中默认的 function 作用域 fixture 是最安全的选择。避免在 moduleclass 级别初始化可变对象(如列表、字典、Mock 实例),否则前一个测试修改它会影响后一个测试。

  • ✅ 正确:每次测试都新建一个空字典或干净的 Mock
  • ❌ 错误:用 @pytest.fixture(scope="module") 返回一个被多次 .append() 的列表

隔离外部依赖:用 patch 或临时实例代替真实调用

数据库、HTTP 请求、文件读写等容易残留副作用。应统一用 unittest.mock.patch(pytest 可配合 monkeypatch)拦截,或为测试构造轻量临时实例(如 sqlite:///:memory: 数据库)。

  • 对全局配置:用 monkeypatch.setattr 临时修改,退出时自动还原
  • 对模块级函数:用 @patch("module.func") 装饰器确保隔离
  • 避免在 setUp 中直接调用 requests.getopen()

显式清理 + 使用 teardown 机制

即使用了 fixture,涉及资源申请(如启动线程、创建临时目录、注册信号处理器)时,必须配对清理。pytest 推荐用 yield-fixture;unittest 推荐在 tearDown 中释放。

  • yield-fixture 示例:yield tmpdir; shutil.rmtree(tmpdir)
  • 避免只在 setUp 创建资源却不保证 tearDown 执行(可用 addCleanup 注册回调)
  • 对数据库:每个测试用独立 schema 或事务 rollback,而非共用一张表

禁用隐式共享状态:检查常见“陷阱”

有些看似无害的操作实则引入跨测试污染:

  • 日志配置logging.basicConfig() 全局生效,应改用 logging.getLogger().handlers = [] 重置
  • 缓存装饰器:如 @lru_cache

    或自定义缓存字典,测试前需 func.cache_clear()
  • 单例类:测试中若调用 Singleton.get_instance(),需在 teardown 中重置内部实例变量
  • os.environ:用 monkeypatch.setenv / delenv,别直接赋值