
在使用 python `unittest.mock` 进行单元测试时,当模拟一个方法抛出异常并期望通过 `call_count` 验证其调用次数时,可能会遇到计数为零的现象。这通常是由于断言了类本身的 mock 对象,而非其返回的实例 mock 对象上的方法。本文将深入探讨这一问题的原因,并提供正确的断言方法,确保即使在异常场景下也能准确验证方法的调用。
理解 unittest.mock 中的类与实例模拟
在 Python 单元测试中,unittest.mock 是一个强大的工具,用于隔离被测代码与外部依赖。当我们需要模拟一个类时,通常会使用 patch 装饰器或上下文管理器。例如,with patch("module.Class") as mocked_class: 会将 module.Class 替换为一个 MagicMock 对象。
关键在于,这个 mocked_class 实际上是 Class 这个 类 的 Mock。当我们实例化这个类,例如 instance = mocked_class() 时,mocked_class 会返回另一个 MagicMock 对象,这个对象代表了 Class 的一个 实例。所有对 instance 的方法调用,实际上都是发生在 mocked_class.return_value 这个 Mock 对象上。
异常场景下的 call_count 误区
考虑以下场景:我们有一个 UploadService 类,其中 upload 方法内部会调用 Blob 类的实例方法 upload_from_string。我们希望测试当 upload_from_string 方法抛出异常时,UploadService 的行为。同时,我们还想验证 upload_from_string 确实被调用了一次。
原始代码示例:
# upload_service.py
import json
import logging
# 假设 GoogleCloudError 和 Blob 是外部库的类
class GoogleCloudError(Exception):
pass
class Blob:
def __init__(self, name, bucket):
self.name = name
self.bucket = bucket
def upload_from_string(self, data, content_type):
print(f"Uploading data: {data} to {self.name} in {self.bucket}")
# 模拟实际的上传逻辑,这里简化
if "error" in data: # 示例:模拟特定数据触发异常
raise GoogleCloudError("Simulated upload error")
return True
class UploadService:
def __init__(self, name, gcs_bucket):
self.name = name
self.gcs_bucket = gcs_bucket
def upload(self, data):
try:
gcs_blob = Blob(self.name, self.gcs_bucket)
gcs_blob.upload_from_string(data=json.dumps(data), content_type="application/json")
return "Upload successful"
except GoogleCloudError as e:
logging.exception("Error uploading file")
return f"Upload failed: {e}"
# test_upload_service.py
import unittest
from unittest.mock import patch
from upload_service import UploadService, GoogleCloudError, Blob # 导入实际的Blob和GoogleCloudError
class TestUploadService(unittest.TestCase):
def test_upload_failure(self):
us = UploadService("my_file", "my_bucket")
with patch("upload_service.Blob") as mocked_blob_class:
# mocked_blob_class 是 Blob 类本身的 Mock
# gcs_blob 是 Blob 实例的 Mock
gcs_blob_instance = mocked_blob_class.return_value
gcs_blob_instance.upload_from_string.side_effect = GoogleCloudError("Google Cloud error")
result = us.upload({"status": "error"}) # 调用会触发异常
self.assertIn("Upload failed", result)
# 错误的断言方式:
# self.assertEqual(1, mocked_blob_class.upload_from_string.call_count) # ❌ 实际会是 0
在上述 test_upload_failure 示例中,如果尝试断言 mocked_blob_class.upload_from_string.call_count,测试将会失败,因为其值为 0。这是因为 upload_from_string 方法是作用在 Blob 的 实例 上,而
不是 Blob 类 本身。当 us.upload() 内部调用 Blob(...) 时,mocked_blob_class 返回了一个 MagicMock 对象作为实例,即 gcs_blob_instance。真正被调用的方法是 gcs_blob_instance.upload_from_string,因此其调用计数应该记录在 gcs_blob_instance 上。
Openflow
一键极速绘图,赋能行业工作流
88
查看详情
正确的 call_count 断言方法
要正确验证 upload_from_string 方法的调用次数,我们应该断言在 mocked_blob_class.return_value(即 gcs_blob_instance)上的 upload_from_string 方法。
修改后的测试代码:
# test_upload_service.py (续)
class TestUploadService(unittest.TestCase):
def test_upload_failure_corrected(self):
us = UploadService("my_file", "my_bucket")
with patch("upload_service.Blob") as mocked_blob_class:
gcs_blob_instance = mocked_blob_class.return_value
gcs_blob_instance.upload_from_string.side_effect = GoogleCloudError("Google Cloud error")
result = us.upload({"status": "error"})
self.assertIn("Upload failed", result)
# 正确的断言方式:
self.assertEqual(1, gcs_blob_instance.upload_from_string.call_count)
# 或者直接通过 mocked_blob_class().upload_from_string.call_count 访问
self.assertEqual(1, mocked_blob_class().upload_from_string.call_count) # 这两种方式等价通过将断言目标从 mocked_blob_class.upload_from_string 更改为 gcs_blob_instance.upload_from_string (或 mocked_blob_class().upload_from_string),测试将如预期般通过。即使 side_effect 导致方法抛出异常,unittest.mock 仍然会正确记录该方法的调用。
注意事项与最佳实践
- 区分类Mock与实例Mock: 在使用 patch 模拟类时,务必清楚你是在与类 Mock 交互,还是与它返回的实例 Mock 交互。实例方法(非静态方法、类方法)的调用总是发生在实例 Mock 上。
- side_effect 的作用: side_effect 属性可以用于模拟异常、返回序列值或调用真实函数。无论 side_effect 行为如何,只要方法被调用,其 call_count 都会被正确记录在对应的 Mock 对象上。
- 明确断言目标: 总是断言在实际接收到调用的那个 Mock 对象上。如果被测代码调用的是 obj.method(),那么你应该断言 obj.method.call_count。
- 可读性: 为了提高测试的可读性,建议将 mocked_blob_class.return_value 赋值给一个有意义的变量(如 gcs_blob_instance),这样在后续断言时能更清晰地表达意图。
总结
当在 unittest.mock 中模拟一个方法抛出异常,并希望验证其调用次数时,核心在于正确识别并断言在接收到调用的 Mock 对象上。对于被 patch 的类,其实例方法的调用计数应在 类Mock.return_value.方法名.call_count 上进行验证,而不是 类Mock.方法名.call_count。理解这一区别是编写健壮、准确的 Python 单元测试的关键。
以上就是Python unittest.mock 中异常方法调用计数问题详解与解决方案的详细内容,更多请关注其它相关文章!
# js
# python
# 多线程
# 重启
# 当我们
# 这一
# 抛出
# 区别
# google
# ai
# 工具
# app
# go
# json
# 网站推广平台功能
# 张家口搜狗关键词排名
# 美术展营销推广案例分析
# 鹤壁优化推广营销费用
# 宁夏问答营销推广多少钱
# seo女面试衣着
# seo推广平台费用
# 网站建设参考书
# 同城营销推广招商
# 阜新湖南网站建设
# 是一个
# 都是
# 而不是
# 的是
# 如何用
相关栏目:
【
企业资讯168 】
【
行业动态20933 】
【
网络营销52431 】
【
网络学院91036 】
【
运营推广7012 】
【
科技资讯60970 】
相关推荐:
c++ 获取系统当前时间 c++时间戳获取方法
抖音DOU+怎么投最有效 抖音付费推广的ROI提升技巧
AO3官方在线访问地址 Archive of Our Own最新镜像合集
Excel如何用迷你图显趋势_Excel用迷你图显趋势【趋势小图】
痛风发作了怎么办? 快速止痛和后期饮食调理
steam官方入口大全 steam账号注册及操作指南
Mac怎么锁定备忘录_Mac备忘录加密设置教程
我的世界mc.js免费游戏直接能玩 我的世界mc.js小游戏免费秒玩入口
excel怎么制作工资条 excel快速生成工资条的方法
JUnit5/Mockito:优雅测试内部依赖与异常处理的实践
在J*a中如何开发简易电子商务商品管理系统_商品管理系统项目实战解析
Pandas DataFrame:高效添加条件计算列
Go Martini框架:动态服务解码后的图片内容
c++如何使用TBB库进行任务并行_c++ Intel线程构建模块
qq浏览器如何查看和导出已保存的密码 qq浏览器密码管理器数据备份教程
qq音乐在线播放入口_qq音乐电脑版登录链接
Python类型检查:优化关联可选属性的Mypy推断策略
win11专注助手在哪 Win11免打扰模式设置与自动化规则【指南】
微信群消息显示延迟如何解决 微信群消息刷新优化方法
ArrayList与LinkedList操作复杂度详解:遍历与修改
CSS布局:解决全屏元素100%尺寸与外边距导致的页面溢出问题
Golang如何使用buffered channel提高性能_Golang buffered channel优化技巧
极兔快递快件信息查询系统 极兔快递官网运单号追踪
HTML5原生日期选择器与jQuery UI:实现日期选择器的联动与程序化控制
Win11怎么开启高性能模式_Windows 11电源计划优化设置
Android Studio计算器C键逻辑错误排查与修复:条件判断优化指南
基于动态规划的房屋花卉种植最小成本算法详解
Python getattr() 异常处理深度解析:避免程序意外退出
狙击外星人小游戏开始_狙击外星人小游戏立即开始
抖音创作助手登录入口_抖音创作辅助工具官网直达
《刺客信条:影》PS5 Pro和Switch 2画面对比
利用Bokeh CustomJS动态控制DataTable列可见性
J*a TimerTask中HashMap意外清空的深层原因与解决方案
Golang如何实现状态模式管理对象状态_Golang State模式实现技巧
限制HTML日期输入框的日期选择范围
蛙漫漫画免费阅读入口_蛙漫官方正版无广告纯净版
b站怎么删除评论_b站评论管理与删除操作
Composer中的^和~符号代表什么_精通Composer版本号语义化约束
微信网页版官方快速登录入口 微信网页版网页版账号直达
京东单号查询入口_京东快递订单追踪入口
mc.js官网登录入口 mc.js官方登录入口最新版
免费抖音短视频入口_抖音网页版短视频免费通道
铁路12306卧铺选择攻略 铁路12306下铺座位预定技巧
QQ邮箱网页版入口登录 QQ邮箱在线邮箱官方通道
AO3网页版合集入口 Archive of Our Own同人作品浏览指南
拼多多视频播放卡顿如何处理 拼多多视频播放优化技巧
Golang如何实现简单的Web表单_Golang表单提交与验证处理方法
TikTok国际版官网直达_TikTok国际版官网直达进入在线观看
Golang如何通过reflect操作map_Golang reflect map操作与遍历技巧
html怎么运行外部js文件中的函数_运html外js文件函数法【技巧】


