资讯动态

六类高频陷阱与规避方案:单元测试如何从“测实现”到“测行为”

发布时间:2026/10/2 20:38:28 来源:尧图企业网站定制
说句实话在一线写代码这么多年我见过太多把单元测试当成绩效考核应付的项目了——测试覆盖率报表全线飘绿一上线照样出故障随便重构一个方法测试文件立刻红成一片到最后团队受不了干脆把测试整体删除。问题从来不在写测试这件事本身而在于写出来的测试到底在测什么。单元测试的真正定位是给代码行为建立一张安全网让每次改动都有底气地说这块没被改坏。但大部分人把它写成了对代码实现细节的复述也就是用测试把代码当前的写法锁死了而不是验证它应该表现出的行为。这篇文章我根据自己踩过的坑总结了六类高频单测陷阱包含具体的报错现场、排查思路和规避方法。同时结合 Vue 项目的实际测试场景聊聊路径别名、插件依赖、异步更新这些让无数人卡住的报错原因最后再说说基于 LLM 辅助生成单元测试的实践边界。不管你是刚接触单测的新人还是正在为团队测试质量头疼的负责人都应该能从里面拿到一些直接能抄作业的东西。1. 先认清单元测试的本质从为什么总是失效说起1.1 单测失效的根因你在测实现不是在测行为很多人写测试的时候思路是这个方法调了 A、然后调 B、最后返回 C于是测试就一步一步把调用的过程复刻了一遍。这样的测试一旦写出来确实第一次跑就能绿因为它是照着实现描出来的。但问题在于你测的只是代码当前是怎么写的不是这段代码应该提供什么行为。举个非常常见的例子某个订单服务里有一个计算折扣的方法原始实现是先判断用户等级、再判断是否会员日、然后循环累加。你写的测试一步一步去验证调用了等级判断逻辑、进入了会员日分支甚至用 Mockito 的 verify 断言某个私有方法被调用了几次。好了某天产品说折扣规则要调整你把计算逻辑重构成了策略模式所有行为测试全部还是绿的但你的单元测试一夜之间全挂了——挂的原因不是功能坏了而是私有方法调用路径变了。这就是根本矛盾单元测试的第一价值是防止回归不是固化实现。好的测试应该站在使用者的角度去描述给定什么输入、应该得到什么输出而不是站在代码内部的角度去描述哪一行调用了谁。我后来遍历过不少失败的项目发现凡是测试文件里出现大量 verify 内部调用、mock 自己类内部方法、断言实现细节的基本都走在这条歪路上。1.2 陷阱全景图我总结的六类高频问题在实际项目中我总结下来单测失效很少是单一原因通常是几个问题叠加。这里先给一个全景图后面章节逐一展开陷阱类型典型表现核心危害快乐路径综合症只测正常输入不测异常和边界覆盖率看着高线上照样崩Mock 一切把依赖全换成 mock测试在复述实现重构即爆红测试成了负担状态污染测试共享静态变量、数据库、文件单个跑能过全量跑随机挂时间与随机性失控直接调用 Date.now()、Math.random()今天是绿的明天同一套代码变红断言质量差assertTrue(x y)、不写断言、只 assertNotNull绿灯满屏但什么都没验证覆盖率数字崇拜追求行覆盖 90%只看报表安全假象越跑越心虚这六类问题覆盖了我接触过的大多数测试失效场景。你如果发现自己的测试正好命中其中几条别慌下面我给你一整套已经验证过的规避方法。2. 六个高频陷阱拆解与规避方案2.1 快乐路径综合症只测阳光大道不碰异常分支这是最普遍、也最容易被忽视的一个陷阱。我问过不少同事你写完一个接口的测试了吗答写了正常入参能返回正确结果。可一旦我追问参数为空会怎样、参数超长会怎样、依赖服务超时返回什么对方基本就沉默了。我自己早期也犯过这个毛病。当时做一个文件导入功能测试只验证了格式正确的 CSV 能导入成功覆盖率显示 85%信心满满地提交了。结果测试环境一跑用户传了一个带 BOM 头的文件解析直接抛异常。问题就出在我从来没写过文件头带 BOM 时应该跳过这个异常分支的测试。规避方法其实很简单写测试时强制自己过一遍输入矩阵。我在工程里常做一件事拿一个待测函数列出它的参数类型、取值范围、边界值、空值、非法值把每个组合至少写一个测试。不需要穷举笛卡尔积但要保证正常值、边界值、异常值三类都覆盖到。尤其是边界值——比如分页的 pageSize 是 0 或负数、字符串的下划线命名、金额精度的小数点后三位这些地方才是线上事故的高发地带。实操心得每写一个功能测试强制自己再写一个异常分支测试。如果实在想不到异常分支说明你对这段代码的输入约束还没有想清楚先去把契约理清楚再动手。2.2 Mock 一切测试与实现绑定太深Mock 是单测里最重要的工具之一但也最容易成为陷阱。很多人在写测试时有一个直觉把所有外部依赖全部 mock 掉测起来又快又干净。想法本身没错错的是度。当一个测试里出现五六个 mock 对象测试的每一步都在设计好的剧本里走它验证的不再是真实行为而是Mock 对象之间如何互相调用。举个例子你测试一个 UserService.register()如果连 UserRepository 的 save 方法也被 mock然后断言save 被调用了一次这个测试本质上什么都没验证。你既没有确认传入的实体字段是否正确也没有确认数据库层能不能接受这个实体只是确认了代码路径确实走到了 save 这行。一旦以后你在 save 之前加了一层缓存测试就挂了——但真正的注册功能根本没坏。我的做法是分层处理依赖可控制成本低的依赖用真实现比如内存数据库、本地文件系统外部服务、网络调用、时间、随机数这种不可控因素才用替身。真实依赖越多测试的真实性越强替身越少测试的脆弱性越低。还有一个很容易被忽略的点mock 外部服务的返回值时要尽量贴近真实格式不要为了测试顺手写一个简化版返回否则你等于在测一个别人编出来的接口。实操心得每次想新增一个 mock 对象时先问自己这个依赖如果换成真实实现测试还能跑吗。如果答案是能就别 mock如果跑不了说明它确实是外部不可控因素这时候 mock 它才合理。2.3 测试之间的状态污染顺序依赖比你想的更隐蔽状态污染是那种最让人抓狂的测试问题特征非常典型单独跑这个测试文件全绿整个测试套件一起跑随机挂几个用--shuffle随机执行顺序跑一次又出现不同的失败。这种随机失败的问题定位成本极高而且很多时候你重跑一次它又变绿了于是大家选择忍一忍——这是最危险的处理方式。常见的污染源我当时排查下来主要有三类。第一类是静态变量和单例比如一个全局配置对象被某个测试改了后面的测试读到的就是脏数据。第二类是共享的外部资源比如测试共用一个本地数据库一个测试插入的数据没清理另一个测试查数量就多了几条。第三类是文件系统测试写临时文件不删第二次运行就可能撞上残留文件。规避的核心思路就四个字用例隔离。每个测试都要假设自己是第一个跑、也是最后一个跑的。为此我在项目里立了几条硬规矩静态可变字段一律不允许在测试间共享如果有要用BeforeEach或setup()重置测试产生的临时文件必须写进操作系统临时目录并在测试结束后清理数据库操作尽量用事务回滚或者使用内存数据库确保每个用例的数据互不干扰。你要是用过 JUnitBeforeEach和AfterEach就是你的第一道防线。注意事项不要试图靠测试命名排序或配置文件固定顺序来掩盖顺序依赖。你今天保住了顺序下个月换一台机器或者加一个测试顺序就变了问题会换个姿势重新出现。2.4 时间与随机性非确定性测试的噩梦非确定性是单元测试的隐形杀手它最大的特点就是测试结果不可复现。最常见的就是代码里直接调用了Date.now()、new Date()、Math.random()这类方法。比如你写了一个计算订单超时时间的函数里面直接用了当前时间测试为了验证逻辑只能去算当前时间的偏移量。问题是你算偏移量的代码和函数里的代码是同一套逻辑测了半天等于自己在验证自己。更隐蔽的是并发场景。我曾经在一个多线程项目里见过一个测试跑一千次偶发失败一次定位了整整两天才发现失败的原因是测试里两个线程对同一个共享列表的访问顺序不确定。单测默认的断言顺序在单线程下是确定的但一旦涉及多线程一切都变得不可控。这类问题我的规避方案有三板斧第一引入可控时钟在业务代码里不要直接调Date.now()而是通过一个 Clock 接口获取当前时间测试时可以传入固定时间第二随机数使用带固定种子seed的 Random 实例这样每次生成的序列都一致第三如果你测的是并发逻辑优先考虑用并发工具类做确定性验证避免暴露真实线程竞争。说白了就是让一切不确定因素在测试环境里变得可以控制让测试结果只由输入决定。2.5 断言质量差绿灯根本没有意义还有一种很常见的情况测试确实跑了、结果也是绿的但它什么都没验证。我见过最夸张的测试整个方法只有两行调用被测方法、然后assertNotNull(result)。第一个人写的时候可能觉得只要不抛异常就算通过了后面的人看到这个方法跑得挺快也就一直留着。但你想assertNotNull能发现什么发现返回值不是 null。它既不能确认返回值的内容对不对也不能确认异常路径的处理对不对。这种测试跑一百次都是给自己壮胆本质上是在骗自己。断言质量差的另一个表现是把多个事项揉在一个断言里。比如assertTrue(result.getCode() 200 result.getData() ! null result.getMsg().equals(ok))这么写一旦失败你只知道整体为 false根本不知道是哪一项出了问题。正确的做法是一次断言验证一个行为这样失败信息能直接定位到具体条件。我个人的经验是写断言之前先想清楚这个测试要验证的行为契约是什么返回值正确吗异常类型对吗副作用发生了吗对外部依赖的调用参数对吗针对每一种行为选择最精确的断言方法。比如数值断言用assertEquals(expected, actual)集合断言用assertIterableEquals异常断言用assertThrows而不是 try-catch 后fail()。如果你用 AssertJ 这类流式断言库还能写出assertThat(result).isEqualTo(expected).isInstanceOf(...)这种更接近自然语言的链式断言可读性也会高一个台阶。2.6 只追覆盖率数字数字游戏背后的假安全最后说覆盖率。很多团队把覆盖率当作一个 KPI 来管理比如要求行覆盖 90% 以上不达标就不准提交。这个指标单看是很片面的。我见过不少项目覆盖率报表极其漂亮但实际质量堪忧原因很简单一个只断言assertNotNull的测试也能把行覆盖刷上去一个只测快乐路径的测试也能覆盖大部分代码行。我不是说覆盖率没用而是它应该作为发现盲区的工具而不是质量达标的证明。我现在的用法是先把测试写好跑完看覆盖率报告找出那些完全没有被执行到的分支和行反推这里是不是我没想到的边界场景对于覆盖率里偏低的分支逐个判断它是否值得测试——有些防御性代码可能永远不会走到有些巨额成本的分支可以暂时接受不覆盖。重点是让覆盖率帮助你思考而不是让覆盖率替你思考。注意事项给团队定覆盖率目标时与其卡一个全局数字不如按模块重要性分级。核心支付、订单模块可以要求高覆盖一些工具类、纯展示组件可以适当放松。否则你会发现大家为了达标开始在生产代码里写一堆只为测试而存在的分支。3. 构建稳健代码防线的实操清单3.1 写测试前先写行为契约Given-When-Then规避了上面那些陷阱之后我意识到真正让一个团队的单测质量拔高一个档次的不是技巧而是方法论。我现在要求自己在写测试之前先用 Given-When-Then 三段式写清楚这个测试的行为契约Given前置条件包括输入参数、依赖状态、系统配置When执行的操作即调用哪个方法、触发哪个事件Then期望的结果包括返回值、异常、状态变化、对外部依赖的调用这个方法最初是从行为驱动开发里借鉴来的但它作为测试设计思路非常好用。我举个例子要测试订单模块的取消订单功能Given: 用户已经下单且订单状态为待支付 When: 用户调用取消订单操作 Then: 订单状态变为已取消且库存恢复且原支付单被标记为关闭你看这样写完之后测试代码的骨架基本就出来了而且它描述的是行为不会被具体的实现方式绑架。你重构订单模块内部逻辑的时候只要这个行为契约不变测试就不需要大改。这就是从测实现到测行为的关键一步。还有一个实操细节测试方法命名我习惯用三段式比如cancelling_pending_order_should_restore_stock含义非常明确。全团队统一这个命名风格后看测试列表基本等于看需求列表谁用谁知道。3.2 测试替身选型Stub、Mock、Fake 我到底用哪个测试替身Test Double这个概念很多新人容易混淆但它恰恰是解决 Mock 陷阱的关键。替身其实有几种不同类型我简单给你对一下替身类型作用典型使用场景Stub提供预设的返回值外部服务返回固定响应Mock验证方法是否被调用、调用参数确认对外依赖的交互行为Spy包装真实对象记录调用信息在真实对象上顺带记录参数Fake提供轻量级真实实现内存数据库、内存队列我在项目里的选型标准是如果你只关心接口返回什么用 Stub如果你必须验证某个依赖被正确调用用 Mock如果你想要一个行为接近真实的替代实现用 Fake。这三者的成本是递增的Fake 最重但也最可靠。比如测试仓储层时用 H2 内存库这种 Fake比把 Repository 全部 mock 掉要可靠得多因为至少你连 SQL 映射、主键生成这些环节也一起验证了。Mockito 里常用的mock()方法默认就是不调用真实方法的替身配合when(...).thenReturn(...)可以预设行为。而 Spy 可以在真实对象上记录调用适合我想验证真实逻辑里某个方法确实被调用了的场景。但记住一点能用真实实现就不要上替身能用 Stub 解决问题就不要上 Mock。3.3 数据构造与命名规范让测试可读、可维护测试代码写出来是给人看的更是给未来的自己看的。一个常见的痛点测试里动不动就是十几行的对象构造代码看着头大而且字段值写得随意别人根本不知道这个对象为什么要这么构造。我建议的解法是引入测试数据工厂Test Data Factory。针对领域模型写一个专门造数据的工具类比如OrderTestData.createDefault()、OrderTestData.createWithStatus(OrderStatus.PAID)。这样每个测试里只需要一行就能拿到自己需要的对象测试的意图瞬间清晰。如果一个对象的某个字段对当前测试场景不重要那就用默认值不要每个测试里都手工设一遍——那是噪音。命名规范方面前面已经提到三段式命名。除此之外我还建议在测试文件中按被测方法分组用嵌套类或者分组注释把同一个方法的多个场景放一起。我现在写 Java 测试时习惯用Nested类外层类命名方法名内层方法命名场景。跑测试的时候看输出列表导航体验就好很多。3.4 把测试跑得又快又稳并行与隔离测试跑了很久才出结果会让大家越来越不想跑测试。造成测试慢的原因通常是测试里有真实的网络请求、有过于重的数据库初始化、或者每个测试都在重复创建重量级容器。我见过一个极端案例一个中等规模的 Java 项目全量测试跑完要四十分钟大家都受不了于是只在提测前跑一次——每次提测都是一次心跳。优化的思路分几层如果测试里有真实网络调用赶紧换掉网络调用在单测里是绝对禁止的如果是 Spring 容器启动太慢可以考虑用SpringBootTest的最小化 slice比如只加载 Web 层或者只加载 MyBatis 层别把整个容器都拖进来如果是大量测试重复初始化同一个数据源可以复用连接但要注意隔离。在并行方面JUnit 5 提供了Execution(CONCURRENT)这种并行执行能力开起来之后整体耗时有明显下降前提是你前面的用例隔离工作做到位了否则并行一开各种状态污染问题就会集中爆发。实操心得我建议把单测的执行时间当成一个重要指标来管理。每次提交代码跑一次单测如果超过三分钟就要开始寻找可以优化的点。测试跑得快开发才愿意频繁跑这比任何强制制度都有效。4. Vue 项目的单测报错排查实录项目里 Vue 应用的测试覆盖率要上去第一步就是把环境配好。可偏偏这一步就让不少人掉进了vue单元测试报错的泥潭。我整理了几个高频报错现场每个都有对应的排查思路。4.1 报错前的环境准备Vitest Vue Test Utils 的坑现在 Vue 3 项目里我推荐 Vitest 搭配vue/test-utils。Vitest 底层用 Vite 做转换速度和体验都比 Jest 好。但环境准备里有几个坑特别常见。第一个坑是版本匹配。vue/test-utils的版本必须和 Vue 的版本兼容比如 Vue 3 就要用vue/test-utils2.x如果混用了 Vue 2 对应的vue/test-utils1.x启动测试时大概率会报 Cannot find module vue/test-utils 或者 is not a function 之类的错误。我的建议是安装之前先去 npm 上看一眼 peerDependencies别闭着眼npm install就完事。第二个坑是 Vitest 配置文件里的environment设置。Vue 组件测试需要 DOM 环境默认的 node 环境里挂载不了组件。如果你忘了在vitest.config.ts里设置environment: jsdom跑测试时会报类似document is not defined的错误。这就需要在项目里装上jsdom或者happy-dom作为测试环境的依赖。4.2 Cannot find module与路径别名最常见的起步报错Vue 项目里几乎都会配置路径别名比如指向src、components指向src/components。在应用代码里跑得好好的一写测试就报Cannot find module /core/api。原因是 Vitest 默认并不知道 Vue CLI 或 Vite 里的别名配置需要你在 Vitest 的配置里同步一份。我用 Vitest 时的配置是这样的// vitest.config.ts import { defineConfig } from vitest/config import path from node:path export default defineConfig({ resolve: { alias: { : path.resolve(__dirname, src), components: path.resolve(__dirname, src/components), }, }, test: { environment: jsdom, globals: true, }, })如果项目里有tsconfig的paths也可以用vite-tsconfig-paths插件自动同步省得手写。这个报错还有个变体是Cannot find module ./App.vue多半是 Vue 单文件组件没有被正确转换同样要在配置里确认 Vite 的插件是否加载了。4.3 组件挂载后 undefined 报错处理第三方插件依赖另一个高频报错在测试里 mount 组件后调用组件方法时报Cannot read property $router of undefined或者$store、$t、$message这些全局属性找不到。原因很简单测试环境里没有安装 Vue 的全局插件比如 Vue Router、Pinia、Element Plus 等。处理思路有三条路我按推荐顺序排列。第一尽量在测试里只测试组件自身的行为把路由跳转、状态管理这些依赖放在边界之外处理。比如一个组件里用了useRouter()我通常会给组件传一个 mock 的 router 实例或者用vi.mock(vue-router)把整个路由模块 mock 掉。第二如果确实需要完整的插件环境可以在测试挂载时通过global.plugins传入插件实例import { mount } from vue/test-utils import { createPinia } from pinia import { createRouter, createMemoryHistory } from vue-router const pinia createPinia() const router createRouter({ history: createMemoryHistory(), routes: [], }) const wrapper mount(MyComponent, { global: { plugins: [pinia, router], }, })第三如果项目中大量组件都依赖这些全局插件每个测试都写一遍就太重复了。这时候我建议写一个自定义的 mount 辅助函数统一封装。注意事项如果组件里有Element Plus的ElMessage或ElMessageBox这类挂载到 body 上的组件测试环境里它们往往需要特殊处理否则会报「Cannot read property appendChild of null」。这种情况通常要 mock 掉这些 UI 提示组件因为它们不会影响业务逻辑的验证。4.4 异步更新与定时器问题为什么我的断言总是不生效Vue 组件的 DOM 更新是异步的。当你触发一个事件、修改了响应式数据后立刻 assert DOM 内容大概率得到的是更新前的内容。这是 Vue 单测初期的经典坑之一很多人的第一个 Vue 单测就是在这里翻车的。正确的做法是使用await nextTick()等待 Vue 完成 DOM 更新。而如果你用的是 Vue Test Utils更推荐直接用await wrapper.setData()或await wrapper.trigger(click)因为它内部已经帮你处理了 nextTick 的等待。我在项目中总结了一条铁律凡是修改了组件的响应式状态或触发事件之后断言之前必须加上 await。定时器问题属于另一个高频坑。比如组件在onMounted里启动了一个轮询定时器测试写完跑完进程却一直不退出最后超时报错。原因就是定时器没被清理。测试里遇到涉及定时器的场景我建议用 Vitest 的 fake timers 模式import { vi, beforeEach, afterEach } from vitest beforeEach(() { vi.useFakeTimers() }) afterEach(() { vi.useRealTimers() vi.restoreAllMocks() })开启 fake timers 后测试代码里的setTimeout、setInterval就都由 Vitest 接管了你可以手动vi.advanceTimersByTime(3000)来模拟时间流逝。别忘了在afterEach里恢复真实 timer否则下一个测试文件里时间全部被冻结会引出更玄学的错误。5. 基于 LLM 的单元测试效率工具还是幻觉制造机5.1 LLM 能帮我们做什么最近业内讨论比较多的一个方向是用 LLM 辅助生成单元测试。我自己体验下来的真实感受是它确实能大幅提升测试代码的产出速度尤其是那些体力活部分——比如为一个有十几个字段的 DTO 构造测试数据、为一个 CRUD 方法补齐增删改查的基础用例、把一堆接口文档转换成断言模板这些事情 LLM 干得非常快。我曾在一个老项目上做技术验证挑出一个包含 30 多个方法的服务类让 LLM 根据方法签名、注释和部分业务文档生成测试骨架。五分钟之内生成了一份覆盖了正常路径和几个边界场景的测试文件整体框架可以直接用比我手写至少省了一小时。这是实打实的效率提升。除了一键生成LLM 还可以做增量补盲。我常用的一种方式是手写核心测试之后把当前代码和已有测试文件一起扔给 LLM让它分析哪些分支还没覆盖到并建议补充用例。这比自己盯覆盖率报告一行行看代码顺滑得多尤其适合几个模块之间的交叉边界——这是人类最容易漏掉的。5.2 用 LLM 生成测试的正确姿势要让 LLM 生成可用的测试关键不是你让它写而是你给它什么。我踩过不少坑之后总结出三要素给足背景信息、给足约束条件、给足风格样例。所谓背景信息指的是被测方法的输入输出定义、用到的依赖接口、异常约定。你不能只丢一个方法名就让它猜它会编出来一个看起来合理但实际并不存在的接口。比如它可能虚拟一个repository.findByUserId()可你项目里根本没有这个方法只会让你多一轮改代码的工作。我的做法是把接口定义粘贴进去必要时附上相关的实体类结构。所谓约束条件是告诉它不要用不存在的 API不要 mock 自己类内部的方法必须用项目里已有的测试工具类。LLM 默认会用最流行的库和它自己见过的模式来生成代码如果你项目里用的是 JUnit 5它可能偏偏给你生成 JUnit 4 的导入你用 AssertJ它给你写 JUnit 自带的断言。提前说明这些约束能少改很多代码。所谓风格样例是从你项目里抽两个手写测试作为示例明确告诉它模仿这种风格。LLM 对格式和风格的模仿能力非常强我试过给了一次样例之后生成出来的测试命名、断言方式、注释风格都和团队原有代码保持一致PRE 审查顺畅得多。5.3 LLM 生成的测试会引入哪些新陷阱LLM 虽然快但它引入的新陷阱也必须重视。最大的一个问题是幻觉断言。LLM 会出现一本正经地断言一个它想象中的行为而这个行为实际代码里根本不存在。比如方法明明返回空集合时不做任何特殊处理它却在测试里断言空集合时返回默认错误码。你要是测试只顾着催绿这些错误的断言就会被当成需求固化下来比没有测试还糟糕——至少没有测试时你心里知道没测过。第二个问题恰好和前面讲的快乐路径综合症反过来——LLM 容易生成防御性过剩的测试把大量无效边界当成真实边界用一些看似覆盖一切、实际上只是重复实现细节的用例堆体积。这类测试跑起来很绿、看起来丰满但核心要验证的业务行为往往反而被淹没了。所以我坚持LLM 做初稿、人类做剪裁生成完必须逐条确认每个测试对应哪条真实业务规则。还有一个不可忽视的问题安全与合规。项目里如果包含内部 API 地址、密钥信息或敏感字段名直接把代码粘贴给外部 LLM 工具是存在信息泄露风险的。我的建议是内部敏感项目要么用私有化部署的模型要么在喂数据前做好脱敏处理。这事看起来和写测试无关但出问题就是大问题。实操心得现在我的标准流程是先用 LLM 生成测试骨架然后针对关键业务行为手写补几个核心用例再把整个测试文件送给 LLM 做一次挑战测试——让它以评审者身份找出测试里可能的盲区和错误假设。这一来一回比自己从零写要高效但核心的测试意图始终由人来把控。写到这里我回想了一下其实单测写得好的团队和写得差的团队差距往往不在技术深度而在于对测试到底为谁服务的理解。写测试不是给 KPI 贡献数字也不是给代码上个保险心安它最真实的价值是让你在改代码的时候不心虚。我个人现在每写一个功能都会顺手把正常路径、边界路径、异常路径三组用例补上跑测试跟喝水一样频繁。刚开始你会觉得多了不少工作量但等你在一个晚上重构完模块、一键跑完所有测试、第二天功能完好上线的时候你就知道这道代码防线有多值了。如果你团队现在正被测试问题困扰我的建议是从删掉那些复述实现的测试开始重新用行为契约写一遍。这个动作做完你会打开新世界。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑