6.2 Mock 与 Stub 库选型:原生还是专用


本节摘要:要替身一个外部对象,该用框架自带的替身能力,还是上专门的 Mock 库?本节给出"够用原则"的选型判断——大多数场景原生替身够用,只有高频、复杂、或断言比重极大时才值得上重型库。

替身库不是越多越好

上一节讲框架时提过"隔离与替身是内建还是外接"。这一节专门把它掰开:每个语言体系里,要么测试框架自带一套替身(Mockito、pytest 自带的 mock、Jest 的 jest.fn),要么有独立的专业化替身库(如 Java 的 Mockito、Python 的 mocket、JS 的 Sinon.js)。选型不是"非黑即白上专用库",而是先问需不需要。

原生替身 vs 专用库的对照

先摆一张对照表,把两类"药"的分量称清楚:

维度 原生替身 专用 Mock 库
上手成本 低,随框架即用 中,多一个依赖与语法
覆盖能力 够日常 Stub 与基本行为 更强,管道/时序/调度更细
断言体验 常需手拼 通常更顺滑、可读
适用场景 80% 常规隔离 高频复杂替身才值回票价
维护负担 多一样要升级的东西

要点是:绝大多数场景,原生的就够用。很多团队一上来就把重型库焊进每个测试,往往只是习惯了"标配",而不是真的需要。够用原则省下的,是依赖体积、学习曲线和升级风险。

三段代码感受两者差别

分别用某语言的"原生"与"专用"写法都覆盖"固定返回 + 验证调用"两件事,你能直观体会够了没有。

原生(示例形态):

from unittest.mock import Mock bill = Mock(return_value="ok") charge(user_id=1, amount=5, billing=bill) bill.charge.assert_called_once_with(1, 5) # 够用,两行

专用库(示例形态):

// 专用库示例:更顺滑的交互断言 when(billing.charge(1, 5)).thenReturn("ok"); verify(billing).charge(1, 5); // 更接近口语,但多依赖一层

读起来专用库的断言确实更像人话,但如果你只做"翻一个人为的 Stub、断一次调用",原生的那两行已经把钱付清了。判断标准回到 6.1 的"四件大事"——断言体验不顺,才值得为它上库。

什么时候才真的值得上专用库

争分夺秒的三种情况:第一,替身对象很复杂,需要模拟内部状态流转或多次调用的时序;第二,断言比重极大,团队被"手拼断言"折磨,专用库的顺滑能显著提质;第三,你要做细粒度的调用记录或并发行为模拟,原生能力不够用。除此之外,不妨先拿原生替身把测试写完,真被卡住再引入专用库——零成本试错。

信号 该不该上专用库
只是固定返回一个值 不必
要断言"被调了几次、参数对不对" 原生够
替身内部有状态流转、多次时序 上专用
全仓重依赖断言风格、读起来疼 上专用

与集成测试的边界,别忘

替身库无论多好用,都要记住它是"单元层"的工具,别忘了第 3 章的底线——一旦那段逻辑真正牵扯数据库、网络或真实进程,该验证的就不再是替身能给的,得换上真依赖或契约测试。替身库帮你在单元层把逻辑隔离干净,但绝不替你完成集成层的真验证。这条边界是选型时最容易忘的一课。

替身写太多也是病

选型会上替身库,暗含一种诱惑:因为替身好写,就把所有东西都替身掉,包括那些本就该拿真依赖验的接缝——这就在滑向第 2.5 节的"依赖网络/外部"反模式。所以引入专用库之前,先给自己设一道尺:替身的密度不该无节制增长。若一份测试里替身数量开始逼近被测代码本身,十有八九是这段逻辑已经越界(要么改成了可注入的纯核,要么该把它挪到集成层用真依赖验),而不是替身库选得不够好。

防止替身膨胀有个好用但常被忘的纪律:替身只替"被测对象之外、且非你要验证的那一环"。你要验证订单规则,就把会员等级、存储这两环替掉,但绝不要替掉订单规则本身——替身的作用是隔离边界,不是把被测对象拆成替身的拼图。把这条写进评审口径,能挡掉一大半由替身引发的"测了又好像没测"。

原生与专用混用:留一条"换药"的退路

选型不必"从一而终",原生与专用完全可以混用,但这不等于无纪律地到处乱引。比较克制做法是:默认原生替身起步,把某一类"反复被卡"的高频替身单独抽出,只用专用库补那一类,别让它蔓延到整个代码库。这样既拿到了专用库在痛点上的顺滑,又不会让依赖体积和升级负担滚雪球。

真要这么分区管理,注意两点:一是给"什么时候用原生、什么时候用专用"写一句仓库约定,避免同一块逻辑里两种替身写法混着来、读起来精神分裂;二是专用替身越收敛越好,尽量集中在一个"替身工厂"里维护,别让每个测试各自引一套重型库的写法。替身本身是单元层的配角,别让工具反客为主。

真替身该有的三个"记性"

无论选原生还是专用,替身能派上多大用场,取决于它有没有三个"记性"。一是记得住"返回值"——某个调用该回什么结果,别让你每次都要造一个会出错的神奇壳。二是记得住"被调过没有/被调了几回"——这是验证"这条路径真的走到、且只走该走的次数"的关键。三是记得住"调用参数"——断言时能回看到底带着什么入参。三样记性齐,一个替身才算活得像个能独立工作的替身,否则它只是个占位的哑桩,测不了交互、也挡不住时序类缺陷。

单从这个角度,原生 mock(如 pytest 的 unittest.mock、Jest 的 jest.fn)往往自带"返回 + 记录"两种记性,够日常大多数用例;只有当你还需要"内部状态流转、多次调用时序、并发记录"这些更深度的记性时,才真正轮到专用库登场。判断该不该升级替身武器,就看你缺的是不是这份"深度记性"。

本节要点回顾

  • 够用原则:多数场景原生替身就够,先零成本试写。
  • 对照表:上手低、够日常、维护省是原生;细粒度顺滑是专用。
  • 三段代码看差别:固定返回 + 验证调用,原生两行已付清。
  • 值得上的信号:复杂时序、断言比重极大、细粒度记录。
  • 不越集成界:单元层利器,关键时刻交给真依赖与契约。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U