pytest无内置依赖注入,依赖通过fixture显式提供与组合;避免patch __init__,宜用fixture创建轻量真实依赖或Mock设属性;yield fixture支持安全teardown。

pytest 本身没有内置的“依赖注入机制”。你无法像 Spring 或 Dagger 那样通过注解自动装配依赖;所谓“pytest 中的依赖注入”,实际是靠 fixture 显式提供、组合和复用依赖对象——它本质是函数式、声明式的依赖供给,不是运行时反射注入。
为什么直接 mock 类构造函数容易失败
当你写 MyService(dependency_a, dependency_b, config),而这些依赖本身要初始化数据库连接、读配置文件、启动子进程时,单元测试会变慢、不稳定、难隔离。硬写 mock.patch('__init__') 往往无效,因为 __init__ 被 patch 后实例属性可能未设置,AttributeError 随即出现。
- 不要 patch
__init__,改用return_value控制返回的 mock 实例行为 - 更稳妥的方式:让 fixture 创建真实轻量依赖(如内存数据库),或用
unittest.mock.Mock手动设属性 - 避免在
conftest.py里全局 patch 某个模块——它会影响其他不相关的测试
用 fixture 分层组装依赖比手动传参更可靠
比如 DatabaseService 依赖 ConnectionPool 和 QueryLogger,你可以这样组织 fixture:
@pytest.fixture
def query_logger():
return Mock(spec=QueryLogger)
<p>@pytest.fixture
def connection_pool():
pool = Mock(spec=ConnectionPool)
pool.get_connection.return_value = Mock()
return pool</p><p>@pytest.fixture
def db_service(connection_pool, query_logger):
return DatabaseService(connection_pool, query_logger)
- 每个 fixture 职责单一,可独立复用或覆盖(例如测试异常路径时,重定义
connection_poolfixture 返回抛异常的 mock) -
db_service自动获得已构造好的依赖,无需在每个测试函数里重复写Mock()和.return_value - 如果某依赖需跨测试生命周期(如共享内存 DB),用
scope="session",但要注意状态污染风险
当依赖需要真实 I/O 时,用临时资源 + teardown 更安全
比如类依赖一个本地配置文件或临时目录,别在测试中硬编码路径,也别用 os.remove 手动清理——pytest 的 tmp_path fixture 和 yield fixture 能帮你兜底:
立即学习“Python免费学习笔记(深入)”;
@pytest.fixture
def config_file(tmp_path):
cfg = tmp_path / "config.yaml"
cfg.write_text("host: localhost\nport: 8080")
yield cfg
# yield 后的代码会在测试结束后执行(即使测试崩溃)
-
tmp_path每次测试都新建唯一目录,天然隔离 - 用
yield替代return可插入 teardown 逻辑,比addCleanup更直观 - 若依赖启动后台线程或进程(如 mock HTTP server),务必在 yield 后显式
.shutdown()或.terminate()
真正难的不是怎么注入,而是判断哪些依赖该 mock、哪些该保留真实行为。过度 mock 会让测试失去意义,完全不 mock 又导致不可控。边界常在“是否涉及外部系统”——网络、磁盘、时间、随机性,这些必须隔离;而纯内存计算、简单数据结构转换,通常值得保留真实实现。


















