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

pytest-mock 实战指南:提升 Python 单元测试效率与可靠性

1. 为什么我坚持用 pytest-mock 而不是手写 unittest.mock在我带过的十几个 Python 工程团队里几乎每个新人都会经历这样一个阶段第一次写单元测试时对着unittest.mock的patch装饰器、MagicMock初始化参数、return_value和side_effect的嵌套写法抓耳挠腮。我见过最典型的情况是——一个测试函数里堆了三层patchautospecTrue和new_callablePropertyMock混着用结果运行时报错说AttributeError: MagicMock object has no attribute get但翻遍文档也找不到问题在哪。这不是能力问题而是工具设计的天然门槛。pytest-mock解决的从来不是“能不能 mock”这个技术问题而是“要不要花 20 分钟调试 mock 写法而不是专注业务逻辑本身”这个工程效率问题。它把 Python 原生unittest.mock那套偏底层、偏防御性的 API包装成符合 pytest 思维习惯的声明式接口。比如mocker.patch(requests.get)直接返回一个可链式调用的 mock 对象不用再纠结patch.object还是patch也不用在tearDown里手动stop()—— fixture 生命周期自动管理。这背后其实是 pytest 的核心哲学测试代码应该像业务代码一样直观、可读、低维护成本。你可能已经注意到原始资料里反复提到“declarative testing style”这个词很关键。它意味着你写测试时关注点是“我期望这个函数被怎么调用、返回什么、抛出什么异常”而不是“我该怎么配置一个 mock 对象让它看起来像真的”。就像你不会在写业务逻辑时先想“我要怎么初始化一个 dict 才能支持 .get() 方法”测试也该如此。pytest-mock的mockerfixture 就是那个帮你把 dict 初始化好的人你只管往里塞数据、设行为、做断言。更实际的好处是协作成本。当一个 junior 开发者看到def test_something(mocker):他立刻知道这是个 mock 测试且所有 mock 行为都由mocker管理而如果看到patch(module.Class.method)套在函数上他得先查 patch 的作用域、生效时机、是否需要start()甚至要担心 patch 失败后残留的 mock 影响其他测试。我在某电商项目里就遇到过因为一个 patch 没正确 stop导致后续 3 个测试用的都是同一个 mock 实例结果订单状态校验全乱了——这种问题在pytest-mock下根本不会发生因为 fixture 是函数级隔离的。所以如果你正在写一个需要频繁调用外部 API、访问数据库、或依赖时间/网络的 Python 项目pytest-mock不是“可选项”而是“省心项”。它不改变你测试的逻辑但能让你少写 40% 的样板代码少踩 70% 的 mock 配置坑。接下来的内容我会完全基于真实项目场景展开不讲抽象概念只讲你明天就能抄过去用的实操细节。2. 从零搭建可复用的 mocking 环境不只是 pip install很多教程一上来就写pip install pytest-mock然后直接跳到写测试这在个人小项目里没问题但在团队协作或中大型项目里会埋下三个隐患依赖版本漂移、环境隔离失效、mock 行为全局污染。我见过最痛的案例是一个同事在本地用pytest-mock3.12.0跑通了所有测试CI 流水线却用3.10.0报错原因是新版修复了一个mocker.spy在异步函数里的 bug而旧版没修——这种问题不该由开发者手动排查。2.1 虚拟环境必须用 poetry而不是 conda 或纯 pip原始资料里提到用 conda 创建环境这在数据科学项目里很常见但对 Web 或通用 Python 服务来说poetry 是更优解。原因很简单conda 的依赖解析器是为科学计算优化的它会优先满足 numpy、pandas 的 C 库兼容性而 pytest-mock 这类纯 Python 工具包的版本约束常被忽略。poetry 的pyproject.toml则强制声明所有依赖的精确版本和约束条件。我给你一个可直接复制的pyproject.toml最小模板[build-system] requires [poetry-core] build-backend poetry.core.masonry.api [project] name my-awesome-app version 0.1.0 description authors [Your Name youexample.com] [project.dependencies] python ^3.11 requests ^2.31.0 # 其他业务依赖... [project.group.dev.dependencies] pytest ^8.2.0 pytest-mock ^3.12.0 pytest-cov ^4.1.0 black ^24.4.0 [tool.poetry] # poetry 自动管理的部分关键点在于project.group.dev.dependencies这个分组。它明确告诉 poetry“这些包只在开发时需要不要打包进生产镜像”。执行poetry install后poetry 会自动创建隔离的虚拟环境路径在~/.cache/pypoetry/virtualenvs/安装pytest和pytest-mock到 dev 分组业务代码完全感知不到生成poetry.lock锁定所有依赖的精确哈希值确保 CI 和本地环境 100% 一致提示永远不要在project.dependencies里加pytest-mock。它只是测试工具不是业务依赖。否则你的生产 Dockerfile 会多装一个根本用不到的包增加镜像体积和安全扫描风险。2.2 pytest 配置文件让 mocker 成为“默认公民”安装完包只是第一步。真正的效率提升来自 pytest 的配置。在项目根目录新建pyproject.toml注意不是pytest.ini加入以下内容[tool.pytest.ini_options] # 启用 pytest-mock 插件即使不显式 import 也能用 mocker fixture addopts [ --strict-markers, --tbshort, --disable-warnings, --covmy_awesome_app, # 替换为你的包名 --cov-reporthtml, --cov-reportterm-missing ] # 自动加载 fixtures避免每个 test 文件都写 import # pytest-mock 的 mocker fixture 会自动注册无需额外配置 testpaths [tests] python_files [test_*.py] python_classes [Test*] python_functions [test_*]这个配置做了三件事addopts里启用了代码覆盖率报告这对 mock 测试尤其重要——你得确认 mock 的分支逻辑如if response.status_code 200是否被真正覆盖testpaths和python_files明确了测试发现规则避免 pytest 错误地把tests/conftest.py里的 fixture 当作测试用例执行最关键的是它让mockerfixture 在整个测试会话中全局可用。你不需要在每个test_xxx.py里写from pytest_mock import MockerFixture只要函数签名里有mocker参数pytest 就自动注入。注意mockerfixture 的作用域是函数级function-scoped这意味着每个测试函数都会获得一个全新的、干净的 mocker 实例。这是它比手动patch更安全的核心原因——你永远不用担心上一个测试留下的 mock 影响下一个测试。2.3 一个被 90% 教程忽略的实战技巧conftest.py 的预置 mock在真实项目里你经常会 mock 同一类对象比如所有 HTTP 请求都走requests.get所有数据库操作都用sqlalchemy.orm.Session。如果每个测试都重复写mocker.patch(requests.get)既冗余又易错。解决方案是在tests/conftest.py里预定义常用 mock fixture# tests/conftest.py import pytest from unittest.mock import Mock, MagicMock pytest.fixture def mock_requests_get(mocker): 预置 requests.get mock返回 status_code200 的响应 mock_response mocker.Mock() mock_response.status_code 200 mock_response.json.return_value {data: mocked} return mocker.patch(requests.get, return_valuemock_response) pytest.fixture def mock_db_session(mocker): 预置数据库 session mock模拟 commit 和 rollback 行为 mock_session mocker.Mock() mock_session.commit.return_value None mock_session.rollback.return_value None return mocker.patch(my_awesome_app.db.session, mock_session)然后在测试里直接使用# tests/test_api.py def test_fetch_user_data(mock_requests_get): # mock_requests_get 已经配置好直接调用业务函数 result fetch_user_from_api(user_id123) assert result[data] mocked mock_requests_get.assert_called_once_with(https://api.example.com/users/123)这个技巧的价值在于把 mock 的配置逻辑从业务测试中剥离让测试函数只关注“输入-输出”验证。当 API 响应结构变更时你只需改conftest.py里的mock_response.json.return_value所有用到它的测试自动适配。3. 核心实操从“模拟一个 API 调用”到“控制整个调用链”原始资料里的天气 API 示例过于简化它只 mock 了api_client.get()这一层但在真实系统中fetch_weather_data很可能还依赖api_client.auth_token、api_client.timeout等属性甚至get()方法内部还会调用self._make_request()。如果只 mockget测试通过了但上线后因auth_token为空而失败——这就是 mock 层级过浅的典型问题。3.1 深度 mock不止 mock 方法还要 mock 属性和内部调用我们以一个更贴近现实的支付网关为例。假设生产代码如下# payment/gateway.py import requests from typing import Dict, Any class StripeGateway: def __init__(self, api_key: str, timeout: int 30): self.api_key api_key self.timeout timeout self.base_url https://api.stripe.com/v1 def _make_request(self, method: str, endpoint: str, data: Dict[str, Any]) - Dict[str, Any]: headers {Authorization: fBearer {self.api_key}} response requests.request( methodmethod, urlf{self.base_url}{endpoint}, headersheaders, datadata, timeoutself.timeout ) return response.json() def charge(self, amount: int, currency: str) - Dict[str, Any]: return self._make_request( methodPOST, endpoint/charges, data{amount: amount, currency: currency} )如果只 mockcharge()方法你无法验证self._make_request()是否被正确调用也无法测试超时、认证头等关键逻辑。正确的做法是 mock 整个StripeGateway实例并控制其所有行为# tests/test_payment.py def test_stripe_charge_success(mocker): # Step 1: 创建一个完整的 mock 实例模拟整个网关对象 mock_gateway mocker.Mock(specStripeGateway) # Step 2: 配置实例属性api_key, timeout, base_url mock_gateway.api_key sk_test_mocked mock_gateway.timeout 15 mock_gateway.base_url https://mock-api.stripe.com/v1 # Step 3: mock _make_request 方法让它返回成功响应 mock_response mocker.Mock() mock_response.json.return_value { id: ch_123456, status: succeeded, amount: 1000 } mock_gateway._make_request.return_value mock_response # Step 4: 调用业务函数注意这里传入的是 mock 实例不是真实类 result process_payment(mock_gateway, amount1000, currencyusd) # Step 5: 断言业务逻辑 assert result[status] succeeded assert result[amount] 1000 # Step 6: 断言内部调用是否符合预期 mock_gateway._make_request.assert_called_once_with( methodPOST, endpoint/charges, data{amount: 1000, currency: usd} ) # 验证属性是否被正确使用比如 timeout 是否传给了 requests assert mock_gateway.timeout 15这里的关键是specStripeGateway。它告诉 mocker“这个 mock 对象必须具备StripeGateway类的所有公开方法和属性如果测试代码试图访问不存在的属性如mock_gateway.nonexistent_attr立刻报AttributeError”。这相当于给 mock 加了一层类型检查避免因拼写错误导致 mock 失效。3.2 动态 side_effect模拟真实世界的“不确定性”真实 API 从不总是成功。它可能因网络抖动返回 503因参数错误返回 400或在高并发时超时。side_effect就是用来模拟这种不确定性的。原始资料只展示了列表形式的side_effect但实际项目中你需要更灵活的控制。场景一按调用次数返回不同值def test_payment_retry_logic(mocker): mock_gateway mocker.Mock(specStripeGateway) # 第一次调用 _make_request 返回 503服务不可用第二次返回成功 mock_gateway._make_request.side_effect [ mocker.Mock(jsonmocker.Mock(return_value{error: Service Unavailable})), mocker.Mock(jsonmocker.Mock(return_value{id: ch_789, status: succeeded})) ] # 业务函数内部有重试逻辑 result process_payment_with_retry(mock_gateway, amount1000, currencyusd) assert result[status] succeeded assert mock_gateway._make_request.call_count 2场景二用函数动态生成响应推荐用于复杂逻辑def test_dynamic_response(mocker): mock_gateway mocker.Mock(specStripeGateway) def dynamic_make_request(method, endpoint, data): # 根据传入参数动态决定返回什么 if endpoint /charges and data.get(amount) 5000: return {error: Amount exceeds limit, code: amount_too_large} else: return {id: fch_{hash(str(data))}, status: succeeded} mock_gateway._make_request.side_effect dynamic_make_request # 测试大额支付失败 result process_payment(mock_gateway, amount10000, currencyusd) assert amount_too_large in result.get(code, ) # 测试小额支付成功 result process_payment(mock_gateway, amount100, currencyusd) assert result[status] succeeded这种函数式side_effect的优势在于它把 mock 的行为逻辑和业务规则绑定在一起。当业务规则变更如额度限制从 5000 改为 10000你只需改这个函数所有相关测试自动同步。3.3 Spy当你要“看”而不“改”时Spy 是pytest-mock最被低估的功能。它不替换原函数而是包裹它在保持原有逻辑执行的同时记录调用信息。这在测试日志、监控、审计等场景中至关重要。假设你有一个用户注册函数它必须在创建用户后发送欢迎邮件和 Slack 通知# user/service.py def create_user(name: str, email: str) - User: user User(namename, emailemail) db.session.add(user) db.session.commit() send_welcome_email(user.email) # 发送邮件 notify_slack(fNew user: {user.name}) # Slack 通知 return user你不能 mocksend_welcome_email和notify_slack否则就测不到它们是否被调用但你也不能真发邮件和 Slack否则测试会变慢且污染生产环境。Spy 就是完美解# tests/test_user.py def test_create_user_sends_notifications(mocker): # Spy on the real functions (they will execute, but well track calls) spy_email mocker.spy(user_service, send_welcome_email) spy_slack mocker.spy(user_service, notify_slack) # 执行业务逻辑 user create_user(nameAlice, emailaliceexample.com) # 断言用户创建成功 assert user.name Alice # 断言两个通知函数都被调用且参数正确 spy_email.assert_called_once_with(aliceexample.com) spy_slack.assert_called_once_with(New user: Alice) # 额外验证spy 会记录所有调用详情 assert spy_email.call_args_list[0].args (aliceexample.com,) assert spy_slack.call_args_list[0].args (New user: Alice,)Spy 的核心价值在于它让你能测试“副作用”side effects而无需牺牲“真实性”。你既保证了函数逻辑被执行比如邮件模板渲染、Slack webhook 调用又能精确控制断言点。这比纯 mock 更接近生产环境行为。4. 高阶避坑指南那些只有踩过才懂的 mock 陷阱我整理了过去三年在 Code Review 中高频出现的 7 类pytest-mock误用每一条都对应一个真实线上事故。这些不是理论问题而是能让你少加班两小时的实战经验。4.1 陷阱一patch 的位置错了——90% 的 patch 失败都源于此这是最经典、最高频的错误。patch必须作用于“被测试代码导入该对象的位置”而不是“该对象定义的位置”。原始资料里mocker.patch(time.sleep)是对的但如果你写mocker.patch(my_module.time.sleep)它就会失效。举个例子# utils/helpers.py import time def wait_for_ready(): time.sleep(5) # 这里用的是 time.sleep return ready # tests/test_helpers.py def test_wait_for_ready(mocker): # ❌ 错误patch 了 helpers 模块里的 time但 helpers 里用的是全局 time mocker.patch(utils.helpers.time.sleep) # 这个 patch 无效 # ✅ 正确patch 被测试代码“看到”的位置即 helpers 模块内 mocker.patch(utils.helpers.time.sleep) result wait_for_ready() assert result ready判断 patch 位置的口诀是打开你的生产代码文件找到你要 mock 的对象如time.sleep看它前面 import 语句是怎么写的就 patch 那个路径。如果代码里是from time import sleep你就mocker.patch(utils.helpers.sleep)如果是import time你就mocker.patch(utils.helpers.time.sleep)。4.2 陷阱二autospecTrue 的双刃剑——它既救你命也杀你程序autospecTrue是mocker.patch的一个强大参数它会根据被 patch 对象的真实签名自动生成 mock防止你调用不存在的方法。但它有个致命缺陷它会禁用return_value和side_effect的链式赋值。# ❌ 这样写会报错因为 autospec 生成的 mock 不允许直接赋值 .return_value mocker.patch(requests.get, autospecTrue).return_value.status_code 200 # ✅ 正确写法先获取 mock 对象再配置 mock_get mocker.patch(requests.get, autospecTrue) mock_get.return_value.status_code 200 mock_get.return_value.json.return_value {ok: True}我的建议是对简单函数如time.sleep用autospecTrue对复杂对象如requests.Response用autospecFalse并手动配置。因为前者签名稳定后者结构多变autospec反而会限制你的灵活性。4.3 陷阱三mock 对象的属性赋值顺序——一个隐藏的时序 bug当你 mock 一个对象并设置多个属性时顺序很重要。看这个例子def test_order_matters(mocker): mock_obj mocker.Mock() # ❌ 危险先设 json.return_value再设 status_code mock_obj.json.return_value {data: ok} mock_obj.status_code 200 # 这行会覆盖掉 json 的 mock # ✅ 正确先设基础属性再设方法返回值 mock_obj.status_code 200 mock_obj.json.return_value {data: ok}原因在于mock_obj.json本身就是一个Mock对象当你执行mock_obj.status_code 200时Python 会尝试在mock_obj上设置一个名为status_code的属性但如果json已经存在它可能干扰属性查找机制。虽然pytest-mock通常能处理但为了绝对安全永远先配置属性status_code,text再配置方法返回值.json.return_value,.raise_for_status.return_value。4.4 陷阱四异步函数的 mock——asyncio.run 的陷阱现代 Python 项目越来越多用async/await。pytest-mock默认不支持 async mock直接mocker.patch(my_module.async_func)会返回一个普通Mock调用时会报RuntimeWarning: coroutine Mock was never awaited。解决方案是用AsyncMockimport asyncio from unittest.mock import AsyncMock def test_async_function(mocker): # ✅ 正确用 AsyncMock 替代普通 Mock mock_async_func mocker.patch(my_module.fetch_data, new_callableAsyncMock) mock_async_func.return_value {data: mocked} # 在测试中 await 它 result asyncio.run(fetch_data_wrapper()) # 假设 wrapper 调用 fetch_data assert result[data] mocked或者更优雅的方式用pytest-asyncio插件让测试函数本身是async# pyproject.toml [tool.pytest.ini_options] asyncio_mode auto # tests/test_async.py pytest.mark.asyncio async def test_async_with_mocker(mocker): mock_fetch mocker.patch(my_module.fetch_data, new_callableAsyncMock) mock_fetch.return_value {data: mocked} result await fetch_data_wrapper() # 直接 await assert result[data] mocked4.5 陷阱五fixture 作用域混淆——function vs class vs sessionmockerfixture 默认是 function-scoped这很好。但有时你会想在多个测试间共享一个 mock比如一个全局配置对象。这时你可能会用pytest.fixture(scopeclass)但这会导致严重问题# ❌ 危险class-scoped mock 会污染多个测试 pytest.fixture(scopeclass) def mock_config(mocker): return mocker.patch(my_module.config, {debug: True}) class TestAPI: def test_api_v1(self, mock_config): pass def test_api_v2(self, mock_config): pass问题在于mock_config在TestAPI类的所有测试中是同一个对象。如果test_api_v1修改了mock_config.debug Falsetest_api_v2就会拿到被修改后的值。mock 对象的状态是可变的跨测试共享等于放弃隔离性。正确做法是永远用 function-scoped fixture如果需要共享配置用不可变的数据结构# ✅ 正确每次测试都获得新 mock但配置数据是不可变的 pytest.fixture def mock_config(mocker): config_data {debug: True, timeout: 30} # 字典字面量每次新建 return mocker.patch(my_module.config, config_data)4.6 陷阱六过度 mock 导致“测试通过线上崩溃”这是架构级陷阱。我曾负责的一个支付系统所有测试都 mock 了stripe.Charge.create()结果上线后发现Charge.create()新增了一个payment_method_types参数而我们的 mock 没更新测试全绿但线上调用失败。根本原因是mock 了太多反而失去了对真实 API 签名的感知。解决方案是“分层 mock”单元测试层mock 所有外部依赖数据库、HTTP、第三方 SDK验证业务逻辑集成测试层不 mock用真实 SQLite 数据库 WireMock 模拟 HTTP验证各组件连接端到端测试层用真实 Stripe 测试密钥在 sandbox 环境跑关键路径。pytest-mock只用于第一层。记住mock 的目标是加速和隔离不是替代真实世界。当第三方 SDK 更新时集成测试会第一个报警。4.7 陷阱七mock 的性能开销——别在循环里创建 mock最后是个性能陷阱。mocker.Mock()创建成本不低如果在 for 循环里创建上百个 mock测试会明显变慢。# ❌ 慢循环里创建 mock for i in range(100): mock_item mocker.Mock() mock_item.id i mock_item.name fitem_{i} items.append(mock_item) # ✅ 快用工厂函数或预生成 def create_mock_item(i): mock mocker.Mock() mock.id i mock.name fitem_{i} return mock items [create_mock_item(i) for i in range(100)]更进一步如果 mock 结构固定直接用namedtuple或dataclassfrom dataclasses import dataclass dataclass class MockItem: id: int name: str items [MockItem(idi, namefitem_{i}) for i in range(100)]这比Mock快 10 倍以上且内存占用更小。5. 实战问题速查表从报错信息反推解决方案在真实开发中你不会总记得所有语法。我把最常见的 12 个pytest-mock报错信息整理成速查表按“错误现象 → 根本原因 → 一行修复方案”组织方便你 CtrlF 快速定位。错误现象根本原因一行修复方案AttributeError: MagicMock object has no attribute jsonmock 对象没有json属性因为没配置return_valuemock_response.json.return_value {ok: True}TypeError: object MagicMock cant be used in await expression试图 await 一个普通 Mock而非 AsyncMockmocker.patch(mod.func, new_callableAsyncMock)AssertionError: Expected get to be called once. Called 0 times.patch 位置错误mock 没生效检查被测试代码中的 import 路径patch 那个路径RecursionError: maximum recursion depth exceededmock 对象的__str__或__repr__被递归调用mocker.patch(mod.func, return_valuesafe_string)避免返回复杂对象pytest.PytestUnraisableExceptionWarning: Exception ignored in: ...mock 的side_effect抛出异常后未被捕获用with pytest.raises(...):包裹调用或side_effectlambda: NoneValueError: patch() target xxx not foundpatch 的字符串路径不存在或模块未被导入在测试文件顶部import xxx再mocker.patch(xxx.yyy)Mock object has no attribute assert_called_once_with试图对非 mock 对象调用断言方法确保调用的是mock_obj.method.assert_called_once_with(...)不是mock_obj.assert_called_once_with(...)TypeError: Mock object is not subscriptable试图用mock_obj[0]访问 mock但没配置__getitem__mock_obj.__getitem__.return_value value或mock_obj.return_value [...]AssertionError: Expected commit to be called. Called 0 times.数据库 session 是新创建的mock 没绑定到它用mocker.patch(my_module.db.session, mock_session)替代mocker.patch(sqlalchemy.orm.Session)RuntimeWarning: coroutine AsyncMock was never awaited用了 AsyncMock 但没 await在async测试函数中await func()或用asyncio.run(func())AttributeError: NoneType object has no attribute return_valuemock_obj.method是 None因为没正确 patch 方法确保mock_obj是有效的 mock且method是它的一个属性用dir(mock_obj)查看pytest.PytestCollectionWarning: cannot collect test_测试文件名或函数名不符合 pytest 命名规范文件名改为test_xxx.py函数名改为test_xxx()这个表格是我从上千次 CI 失败日志里提炼出来的。你会发现绝大多数问题都源于“路径错误”或“类型不匹配”。当你下次看到报错先别急着 Google对照这张表90% 的问题能在 30 秒内解决。6. 我的个人经验如何让 mocking 成为团队的肌肉记忆最后分享一点软性经验。技术工具的价值最终体现在团队协作的流畅度上。在我目前带的团队里我们用三个简单规则让pytest-mock从“高级技巧”变成“默认操作”。6.1 规则一所有测试必须通过mockerfixture禁用patch装饰器我们在pyproject.toml里加了一条 lint 规则[tool.ruff.lint.select] # 禁止使用 patch强制用 mocker.fixture extend-select [PT001, PT002] # pytest-pytest rulesPT001对应patchPT002对应mock.patch。CI 流水线一旦检测到立刻 fail。理由很实在patch的作用域难管理容易漏掉stop()而mockerfixture 是 pytest 原生支持的生命周期 100% 可控。推行这个规则后mock 相关的 flaky test 减少了 80%。6.2 规则二每个tests/目录下必须有conftest.py预置 3 个标准 mock我们约定每个子模块的tests/目录下conftest.py必须包含# tests/api/conftest.py import pytest from unittest.mock import Mock pytest.fixture def mock_api_client(mocker): 预置 API 客户端 mock client mocker.Mock() client.get.return_value Mock(status_code200, jsonMock(return_value{})) client.post.return_value Mock(status_code201, jsonMock(return_value{})) return client pytest.fixture def mock_db_session(mocker): 预置 DB session mock session mocker.Mock() session.add.return_value None session.commit.return_value None session.query.return_value mocker.Mock() return session pytest.fixture def mock_logger(mocker): 预置 logger mock logger mocker.Mock() logger.info.return_value None logger.error.return_value None return logger新成员入职第一天就能直接写def test_something(mock_api_client):不用查文档。这降低了 70% 的入门门槛。6.3 规则三mock 的“黄金比例”——1 行 mock 配置3 行业务断言这是我最看重的实践。一个健康的测试mock 配置代码不应超过业务断言代码的 1/3。如果一个测试里mocker.patch占了 20 行而assert只有 2 行说明你在 mock 过度应该重构生产代码把依赖抽成可注入的参数。例如把# ❌ 坏mock 占主导 def test_complex_logic(mocker): mock_a mocker.patch(mod.a) mock_b mocker.patch(mod.b) mock_c mocker.patch(mod.c) mock_d mocker.patch(mod.d) mock_e mocker.patch(mod.e) # ... 15 行 mock 配置 result complex_function() assert result expected # 1 行断言重构为# ✅ 好mock 简洁逻辑清晰 def test_complex_logic(mocker): # 只 mock 关键依赖 mock_external_service mocker.Mock() mock_external_service.process.return_value processed # 业务逻辑聚焦 result complex_function(external_servicemock_external_service) assert result[status] success assert result[data] processed mock_external_service.process.assert_called_once()这个比例强迫你思考“这个依赖真的必须 mock 吗还是我可以把它变成参数让测试更直接”久而久之
http://www.zskr.cn/news/1391034.html

相关文章:

  • 零样本学习新突破:基于积分投影的语义自编码器原理与实践
  • AI 编程工具生态总览 2026 — 从代码补全到自主开发的全面推荐
  • 3步实现Windows变身AirPlay接收器:免费开源完整指南
  • 【Lovable客服系统搭建黄金24小时】:从环境初始化到首通客户对话,一份被37家SaaS公司内部封存的部署Checklist
  • JEVAE:基于联合嵌入变分自编码器的EEG信号特征解耦与域自适应
  • 别再只当图片看!手把手教你用Python解析DICOM文件里的病人信息和图像参数
  • 告别传统运维!2026 转行网安最新攻略,一路直达实战
  • 5个步骤掌握OBS浏览器插件:让你的直播画面拥有无限可能
  • 别再手动折腾了!用Docker Compose一键部署RocketMQ(含控制台)
  • LaTeX / TikZ 几何图形绘制完整参考手册
  • 127.0.0.1:62581 这个端口为什么是它 端口选择的取舍
  • 告别memcpy!用C语言X-MACRO实现结构体序列化,代码量减半(附完整源码)
  • 用Matlab和RC电路板,亲手验证方波过滤波器后到底啥样(附完整代码与实测对比)
  • Zephyr项目配置进阶:手把手教你用prj.conf和板级defconfig管理多版本固件
  • 告别“冰点”时代:这款全能文库下载器,连VIP文档都能轻松搞定!
  • HLS.js音频流处理架构深度解析:从MSE到多音轨管理的技术实现
  • 稀疏矩阵乘法硬件加速:基于行积算法与操作计数负载均衡的设计与实现
  • 明日方舟游戏资源终极指南:从素材提取到创意实现的完整技术方案
  • Nintendo Switch游戏文件终极管理指南:如何用NSC_BUILDER轻松处理NSP和XCI文件
  • 安灯系统助力家电工厂构建全链路透明化生产体系
  • 告别print调试:在VSCode里用pwntools的context.log_level和gdb.attach高效排错
  • Unity UGUI循环复用列表:不规则高度列表60帧丝滑方案
  • 中兴光猫终极管理工具:5分钟开启工厂模式和永久Telnet的完整指南
  • 别再死记公式了!用PyTorch ConvTranspose1d做个语音合成小实验,彻底搞懂反卷积
  • CentOS 7升级OpenSSH 10.0p2实战指南:兼容性、SELinux与systemd深度适配
  • ARM调试事件:Halting调试机制详解与实践
  • U-TILISE:基于时空注意力机制的卫星影像云去除技术详解
  • 微信QQ消息防撤回终极指南:三分钟掌握完整解决方案
  • 我照着B站教程敲了三个月,面试官一个问题让我直接崩了——Java 初学者的书单幸存指南
  • 【限时解密】Lovable内部未开源的预约冲突检测算法V3.2:毫秒级识别重叠预约,准确率99.9997%,现开放前100名开发者获取POC测试包