提升python代码可测试性的五步重构路径:一、提取依赖为参数;二、应用依赖注入容器;三、拆分纯函数与副作用操作;四、使用protocol定义协作接口;五、重构类为函数组合。

如果您发现 Python 代码难以编写单元测试,经常需要启动数据库、调用外部 API 或依赖全局状态才能运行测试,则很可能是由于模块间存在紧密耦合。以下是提升可测试性的具体重构路径:
一、提取依赖为参数
将硬编码的外部依赖(如数据库连接、HTTP 客户端、配置对象)从函数或类内部移出,改为通过参数传入。这使测试时可用模拟对象替代真实依赖,隔离被测逻辑。
1、定位类中直接实例化第三方对象的位置,例如 requests.Session() 或 sqlite3.connect()。
2、将该对象声明为类的初始化参数,并在方法中通过 self 访问,而非在方法体内创建。
立即学习“Python免费学习笔记(深入)”;
3、修改调用方,在构造实例时传入真实依赖;在测试中传入 unittest.mock.Mock() 或 MagicMock 实例。
二、应用依赖注入容器
当多个层级存在嵌套依赖时,手动传递参数易导致构造链冗长。引入轻量级依赖注入机制,可集中管理依赖生命周期与绑定关系,避免测试时重复构造依赖树。
1、安装 dependency-injector 库,定义一个继承自 containers.DeclarativeContainer 的容器类。
2、在容器中使用 providers.Singleton 声明数据库连接、缓存客户端等共享依赖。
3、在测试模块中重写容器的提供者,例如将 DatabaseProvider 替换为内存 SQLite 实例或 Mock 对象。
三、拆分纯函数与副作用操作
将包含 I/O、时间获取、随机数生成等副作用的操作单独封装,与核心业务逻辑分离。纯函数无外部依赖、输出仅由输入决定,天然可预测、易断言。
1、识别函数中调用 time.time()、random.random()、open() 等的位置。
2、将其抽取为独立函数,并在原逻辑中接收该函数为参数,默认值设为原始调用,保持向后兼容。
3、测试时传入固定返回值的 lambda 表达式,例如 lambda: 1720000000 替代 time.time。
四、使用协议(Protocol)定义协作接口
避免对具体类名或模块路径的强引用,改用结构化类型提示描述“需要什么行为”,使不同实现(真实/测试)可互换而不修改被测代码。
1、定义一个继承自 typing.Protocol 的类,仅声明所需方法签名,例如 def fetch_user(self, user_id: int) -> dict: ...。
2、让真实数据访问类和测试用的内存字典类均实现该协议。
3、在业务类中将该协议作为类型注解和构造参数类型,pytest 中使用 mypy 验证协议一致性,确保模拟对象满足契约。
五、重构类为函数组合
当类仅用于组织一组相关函数且无实质状态时,将其拆解为独立函数,并通过闭包或 partial 绑定共享参数。函数粒度更小、复用性更高、测试边界更清晰。
1、检查类中是否所有方法均只读取 self 的不可变字段(如配置项),且无 self._state 变更。
2、将每个方法转为顶层函数,原 self.config 改为显式参数。
3、在入口处使用 functools.partial 预填充配置,生成专用函数,例如 process_order = partial(_process_order_impl, config=prod_config)。










