当前位置: 首页 > news >正文

替身演员的艺术:pytest-mock 从入门到飙戏

文章目录

  • 一、核心概念阐述
    • 1、 什么是 Mock?
    • 2、 什么是 `pytest-mock`?
  • 二、直接⬆️操作
    • 1、数据替换:`patch()`
      • 示例场景
      • 测试代码
      • 知识点小结
    • 2、数据替换:`patch.object()`
      • 示例
      • 参数说明
      • 温馨小总结
    • 3、数据监控:`spy()`
      • 示例
      • 注意事项
      • 总结
    • 4. 参数校验:`autospec`
    • 5. 添加副作用:`side_effect`
      • ① 抛出异常
      • ② 返回可迭代对象
      • ③ 重定义目标方法


一、核心概念阐述

1、 什么是 Mock?

在软件测试,尤其是单元测试(Unit Testing)中,我们经常会遇到这样的场景:

我们想要测试函数 A,但函数 A 的正常运行依赖于另一个函数 B(或者一个类、一个外部服务)。而调用真实的函数 B 可能会带来一些问题:

  • 依赖复杂:函数 B 可能需要连接数据库、请求网络API或启动其他服务,导致测试环境的搭建变得非常复杂和耗时。
  • 结果不稳定:函数 B 的返回结果可能受到网络波动、第三方服务状态等不可控因素的影响,导致测试结果时好时坏,产生“误报”(Flaky Tests)。
  • 执行缓慢:真实的数据库查询或网络请求会大大拖慢测试的执行速度。
  • 难以模拟边界情况:我们很难让真实的函数 B 返回一个“数据库连接超时”或“磁盘已满”的错误,来测试函数 A 在这些异常情况下的处理逻辑。

为了解决这些问题,Mock(意为“模拟”或“模仿”)技术应运而生。

Mock 的核心思想是:创建一个“假的”函数 B 的替代品(我们称之为 Mock 对象)。这个 Mock 对象完全在我们的控制之下,我们可以让它:

  • 不需要任何复杂的前置条件就能被“调用”。
  • 在被调用时,立即返回我们预设好的、确定的数据。
  • 模拟出各种我们想要的成功或失败的场景。

通过使用 Mock,我们可以将函数 A 与它的依赖项 B 隔离开来,从而能够独立、快速、稳定地测试函数 A 本身的逻辑是否正确,而无需关心函数 B 的真实实现。

简而言之,Mock 就是用一个可控的“替身演员”去代替测试中那些复杂的“配角”,从而让我们的测试焦点只集中在“主角”(被测对象)身上。

2、 什么是 pytest-mock

pytest-mock 是一个流行的 pytest 插件,它极大地简化了在 pytest 测试框架中使用 Mock 的过程。

虽然 Python 内置了强大的 unittest.mock 库用于实现 Mock 功能,但直接使用它有时会显得有些繁琐。pytest-mockunittest.mock 的基础上做了一层优雅的封装,为开发者提供了更简洁、更符合 pytest 风格的接口。尤其是mocker这个对象,它是一个fixture配置的来的,可以直接使用unittest.mock里面的工具,而不需要去导入unittest.mock。

二、直接⬆️操作

好的!我给你润色并稍作结构和语言优化,让说明更流畅、专业又不失亲切感,还会在 Markdown 语法上保持清晰,方便直接阅读和使用。下面是润色后的文章:


1、数据替换:patch()

在测试中,有些方法会依赖外部资源或有明显的延时。如果直接调用这些方法,不仅会拖慢测试速度,还可能因外部环境不稳定导致测试结果不可靠。
此时,我们可以使用 patch 将真实方法替换成一个“假的返回值”,让测试既快速又可控。

示例场景

假设有以下两个文件:

src/mod_a.py

# 外部模块可以创建这个对象,实现加法操作
import timeclass Worker:def do(self, x: int, y: int) -> int:# 模拟响应延迟---这个记住,后面会多次用到这个场景time.sleep(4)return x + y

src/mod_b.py

from src.mod_a import Worker, util# 对 Worker 的进一步封装,可以通过该函数间接调用 do 方法
def call_worker_add(a: int, b: int) -> int:worker = Worker()return worker.do(a, b)

现在我们的需求是:高效地测试 call_worker_add 方法
问题在于:调用该方法会拖延 4 秒,因为它内部调用了 Worker.do
解决方法是:用 patch 替换掉 do 方法,让它立即返回一个指定的值。

测试代码

from src.mod_b import call_worker_add#安装了pytest-mock ,mocker这个对象pytest-mock会自动给我们注入,非常方便
def test_patch1(mocker):# mock_do 是一个“替身”,由 patch 返回mock_do = mocker.patch("src.mod_b.Worker.do")# return_value 指定我们想要的返回值(代替真实的 do)mock_do.return_value = 1314# 调用不会再延迟 4 秒,立即返回 1314assert call_worker_add(1213, 0) == 1314

知识点小结

  • patch() 的参数
    接收一个字符串路径,表示要替换的目标方法的调用路径。

  • mock_do 的本质
    mock_do 是一个 MagicMock 对象,也就是原方法的“替身演员”。
    通过它可以指定返回值 (return_value) 或配置更多行为,测试起来灵活又快速。


2、数据替换:patch.object()

patch.objectpatch 的作用几乎一样,只是写法更直接:通过对象本身去指定要替换的属性或方法。

示例

from src.mod_a import Worker
from src.mod_b import call_worker_adddef test_patch_object1(mocker):# 替换 Worker 的 do 方法mock = mocker.patch.object(Worker, 'do')mock.return_value = 1214# 调用时直接返回 1214assert call_worker_add(1213, 0) == 1214

参数说明

  • patch.object(target, attribute)
    • target:需要修改的对象(类或实例)
    • attribute:对象中要被替换的属性名(方法或变量都可以)

温馨小总结

  • patch("路径.方法名"):通过字符串路径来指定要替换的目标
  • patch.object(对象, "属性名"):通过对象和属性名来定位目标
    两者效果一致,不同之处在于写法习惯和应用场景。

好的!下面是你这部分 pytest-mock 教程 的润色版本,我会保留技术细节,但让行文更清晰、更流畅,并加一点点轻松幽默的味道,方便读者理解。


3、数据监控:spy()

在单元测试中,我们有时候不仅想替换一个对象的方法,还想监控它的真实调用情况。此时,就可以使用 spy() ——它能够对某个属性或方法进行“旁观式监听”:

  • 方法依旧会被真实执行;
  • 调用信息(参数、次数)会被记录下来,方便后续断言。

简单来说,它就像个小本子:实际工作的人(方法)还是该干啥干啥,而小本子在旁边认认真真记账。

示例

from src.mod_a import Workerdef test_spy1(mocker):work = Worker()mock_do = mocker.spy(work, 'do')# 实际调用,执行时间会延迟4秒assert work.do(1213, 1) == 1214# 使用 spy 对调用信息进行断言:# 这里要求 do 必须只被调用过一次,# 且参数正好是 (1213, 1),否则测试失败mock_do.assert_called_once_with(1213, 1)

注意事项

在监控真实调用的基础上,我们还可以进一步切换到替换模式
先让 spy 记录一次真实的调用情况,再通过 patch 来给方法指定一个快速返回值。这样一来,后续调用就可以避免原始的耗时逻辑。

def test_spy1(mocker):work = Worker()mock_do = mocker.spy(work, 'do')# 第一次调用:真实执行,耗时4秒assert work.do(1213, 1) == 1214mock_do.assert_called_once_with(1213, 1)# 监控完真实行为后,用 patch 替换返回值mock_do = mocker.patch("src.mod_b.Worker.do", return_value=1214)# 这次调用走的是 mock,不会再耗时 — 效率瞬间翻倍assert call_worker_add(1213, 1) == 1214

总结

  • spy() = 记录真实调用(执行逻辑+参数追踪)。
  • patch() = 替换调用逻辑(直接返回指定结果)。
  • 组合使用 = 先看真实情况,再快速模拟结果

这样做的好处是:既能保证我们验证了真实逻辑的调用方式,又能在需要的时候跳过耗时操作,保持测试高效。测试过程就像“侦探+替身演员”的完美配合。


好的!我来帮你润色这一部分,把逻辑讲解讲清楚,同时让语言更流畅生动,读者读起来更轻松。下面是改进后的版本:


4. 参数校验:autospec

来看一个常见的“坑”。假设我们写了这样一个测试:

def test_no_autospec(mocker):mock_do = mocker.patch("src.mod_a.Worker.do")# 设定返回值mock_do.return_value = 1214# 注意:这里随便传了一个参数,测试还是能过!# 但实际上 Worker.do(self, x, y) 要求有 3 个参数啊!assert mock_do(1) == 1214

结果测试 居然通过了。这就是个问题:被 mock 掉的方法已经完全不关心原函数的函数签名了,参数随便传都能跑,不小心就会产生“假测试”。

解决方案就是加上 autospec=True。它会根据原函数的签名生成 mock,从而严格检查参数个数和参数名。像这样:

def test_autospec(mocker):mock_do = mocker.patch("src.mod_a.Worker.do", autospec=True)# 设定返回值mock_do.return_value = 1214# 参数不对,立刻报错,而不是假装通过assert mock_do(1) == 1214

这次就乖乖报错了,避免了测试的“假阳性”。

更细致的是,autospec 不仅会检查参数个数,连 参数名 也会严格对照。例如:

def test_autospec2(mocker):mock_do = mocker.patch("src.mod_a.Worker.do", autospec=True)mock_do.return_value = 1214# 原方法签名是 (self, x, y),这里写了 xx,就直接炸锅了assert mock_do(any, xx=1, y=2) == 1214

这样,参数写错名字也能被及时发现,从而让测试更可靠。
一句话总结:autospec 就是帮你避免“传错了还假装对”的情况。


5. 添加副作用:side_effect

side_effect 是个非常强大的选项,可以让 mock 的行为跟“静态返回值”不同,更加灵活。它有三大用法:

  1. 抛出异常
  2. 每次调用返回一个可迭代对象的下一个值
  3. 将整个方法替换为你自己定义的函数

① 抛出异常

有时我们要测试异常处理逻辑,可以用 side_effect 直接指定一个异常:

import pytest
from src.mod_b import call_worker_adddef test_side_effect_raise(mocker):side_effect = ValueError("哈哈哈")mock_do = mocker.patch("src.mod_b.Worker.do", side_effect=side_effect)# 如果没有捕获到 ValueError,将会报错with pytest.raises(ValueError):call_worker_add(1213, 1)

此时每次调用 Worker.do,都会抛出这个 ValueError


② 返回可迭代对象

让 mock 在每次调用时返回不同的结果,非常适合测试“多次调用产生不同结果”的场景:

def test_side_effect_iterable(mocker):side_effect = [1, 2, "fd"]mock_do = mocker.patch("src.mod_b.Worker.do", side_effect=side_effect)assert call_worker_add(1213, 1) == 1assert call_worker_add(1213, 1) == 2assert call_worker_add(1213, 1) == "fd"

就像一个结果“轮盘”,每次转出来都不一样。


③ 重定义目标方法

如果要直接用自定义逻辑替换原方法,side_effect 也能帮上忙。比如我们想把 do(x, y) 改成做乘法:

def test_side_effect_override(mocker):def multiply(a, b):return a * bmock_do = mocker.patch("src.mod_b.Worker.do", side_effect=multiply)assert call_worker_add(3, 4) == 12

这样一来,Worker.do 就被替换成了我们自定义的 multiply 方法。


✨ 小总结:

  • autospec=True → 帮你严格校验调用签名,避免“假通过”。
  • side_effect → 神器三连:抛异常、返回不同值、直接重写方法。

是不是感觉对 mock 的掌控力直接上升了好几个档次?😎


http://www.dtcms.com/a/353401.html

相关文章:

  • Java基础 8.27
  • 如何使用windows实现与iphone的隔空投送(AirDrop)
  • 【Docker基础】Docker-compose数据持久化与卷管理:深入解析docker volume命令集
  • 【重学MySQL】八十九、窗口函数的分类和使用
  • Mysql杂志(三)
  • 【46页PPT】公司数字化转型规划与实践(附下载方式)
  • 学习Python中Selenium模块的基本用法(7:元素操作-1)
  • 应变片与分布式光纤传感:核心差异与选型指南
  • 极海发布APM32F425/427系列高性能MCU:助力工业应用升级
  • laravel学习并连接mysql数据库
  • Linux 软件编程(十二)网络编程:TCP 并发服务器构建与 IO 多路复用
  • redis---set详解
  • Tortoisegit配置ssh教程
  • Vue3 新特性 defineModel 全面解析:让 v-model 写法更优雅
  • 项目智能家居---OrangePi全志H616
  • GitHub 宕机自救指南:保障开发工作连续性
  • 蓝桥杯算法之基础知识(3)——Python的idle的快捷键设置(idle改键)
  • 信任,AI+或人机环境系统智能的纽带
  • 深入解析EDCA通道与参数配置:优化Wi-Fi服务质量的关键策略
  • 新手向:网络编程完全指南
  • Jetson 分区知识全解与 OTA 升级实战
  • Containerd 安装与配置指南
  • 如何验证二叉搜索树:两种高效方法详解
  • 光伏设计平台:按组件数量铺设光伏板,精准控制投资成本
  • 推荐系统王树森(四)特征交叉+行为序列
  • 智能体前沿-主动信息获取理论基础
  • 汇川SV660A 伺服EMC电源滤波的安装要求及使用方法
  • Swift 解法详解 LeetCode 364:嵌套列表加权和 II
  • 【ConcurrentHashMap】实现原理和HashMap、Redis哈希的区别
  • 【Linux网络】网络基础