CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件测试入门/课时/覆盖率与质量门禁
RawBlame
课时目录12 节

软件测试入门

  1. 01测试金字塔
  2. 02JUnit 5 基础
  3. 03Mockito 与 Mock
  4. 04Spring Boot 测试
  5. 05MockMvc 接口测试
  6. 06集成测试
  7. 07TDD 与可维护性
  8. 08前端单元测试
  9. 09E2E 与 Playwright
  10. 10覆盖率与质量门禁
  11. 11CI 中的测试
  12. 12综合练习

覆盖率与质量门禁

JaCoCo、覆盖率误用警示。

25 分钟

覆盖率与质量门禁

这一课你将能做到

  1. 给 backend 配置 JaCoCo,本地生成并读懂 HTML 覆盖率报告(行覆盖 vs 分支覆盖)
  2. 指出「高覆盖仍漏 bug」的典型成因,识别三种虚高覆盖
  3. 起草一条团队可落地的门禁规约:阈值、排除名单、禁止假绿条款

为什么要学

覆盖率统计「测试执行时碰到了哪些行 / 分支」。Java 侧工具是 JaCoCo,前端是 V8 coverage / Istanbul。人话:覆盖率是地图,标出没人走过的黑区;它不是目的地——assertTrue(true) 也能刷出行覆盖,绿灯数字从来不证明行为正确。

它真正的用法是导航:改完 SubmissionService,打开报告看这次改动的分支有没有被测到;看 AuthService 哪些校验分支从没红过。而**质量门禁(quality gate)**把报告变成「能不能合并」:关键包分支覆盖低于阈值就让 mvn verify 失败。门禁要温和且可执行——一上来全仓 95% 只会逼人写空测、乱加排除。

面试高频:「覆盖率多少合适?」——本课给你一个能背、能自圆其说的答案。

先建立图像

代码行 ────────────────► 被测试跑到了吗? → line %
   └─ if / switch 分支 ─► 真假两边都跑过吗? → branch %

高 line + 弱断言 = 假安全感
有意义断言 + 关键模块下限 = 可用门禁
指标含义局限
Line行是否执行到不保证断言正确
Branch分支两边是否都走到仍可能漏状态组合
变更覆盖PR 改动行是否被测对遗留债最务实

金字塔提醒:提覆盖的主战场在单元与薄 MockMvc 层;逼 E2E 刷覆盖又慢又脆。

跟着做

步骤 1:给 backend 挂上 JaCoCo

backend/pom.xml 的 <build><plugins> 里加:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals><goal>prepare-agent</goal></goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>verify</phase>
            <goals><goal>report</goal></goals>
        </execution>
    </executions>
</plugin>

人话:prepare-agent 在测试运行时插桩统计;report 在 verify 阶段生成 HTML。注意它挂在 verify——这就是为什么 CI 用 mvn -B verify 而不是只 test。

步骤 2:生成并打开报告

cd backend
mvn verify
# 报告位置:
# target/site/jacoco/index.html

浏览器打开 index.html,固定浏览顺序:先看你刚改的包 → 再看 branch 最低的类 → 最后才看全仓均值。均值会安慰人,局部黑洞才害人。点进 SubmissionService:submit 的语言白名单、代码长度、课时校验应该都是绿的菱形(分支两边都走到)或至少有一条已测——红的分支就是盲区。

步骤 3:补一条有意义的分支测,再对比数字

假设报告显示「课时参数不合法」分支全红:

@Test
void submit_课时非法时拒绝() {
    assertThatThrownBy(() -> service.submit(3L, 0L, "java", "code"))
        .isInstanceOf(BusinessException.class)
        .hasMessageContaining("课时参数");
}

再跑 mvn verify,看该类 branch % 上升。笔记写一句:数字动了,是因为断言碰到了失败分支——不是因为多写了一个空测。这个因果链是覆盖率课程的心法。

步骤 4:前端 coverage

cd platform
npx vitest run --coverage

首次使用先 npm install -D @vitest/coverage-v8。看终端表格:SubmissionList.tsx 的 error 分支若有 % Stmts 空缺,正是「只测了成功路径」的信号。原则同后端:遗留全仓 100% 不现实,**「你这次改动的逻辑有测」**才是可执行要求。

步骤 5:开启 check 规则,把报告变成门禁(团队共识后)

<execution>
    <id>check</id>
    <phase>verify</phase>
    <goals><goal>check</goal></goals>
    <configuration>
        <rules>
            <rule>
                <element>BUNDLE</element>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.60</minimum>
                    </limit>
                    <limit>
                        <counter>BRANCH</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.50</minimum>
                    </limit>
                </limits>
            </rule>
        </rules>
        <excludes>
            <!-- 排除要有理由,防止名单无限膨胀 -->
            <exclude>com/codeforge/entity/**</exclude>
            <exclude>com/codeforge/config/**</exclude>
        </excludes>
    </configuration>
</execution>

阈值第一次落地建议从宽松值起步(如 60%/50%),只锁关键包(service 层),防止「一人擅自把门禁拉到无法合并」。数字必须和团队对齐后再开 check——report 给人导航,check 给团队执法,两者别混为一谈。

步骤 6:识别三种「虚高覆盖」

打开报告时主动找这些味道:

  1. 样板命中:大量 getter / equals 被测到,业务方法仍红 → 排除样板或把时间花在规则分支
  2. 无断言执行:测试调用了方法但只 verify 甚至什么都不查,行变绿、行为没锁 → 补具体 assert
  3. 只走 true 支:if (lang 不合法) 的失败支从未进入,line 尚可、branch 露馅 → 补失败路径用例(像步骤 3 那样)

步骤 7:在 PR 描述里写清覆盖变化(习惯)

三行即可:

本 PR:SubmissionService 补课时非法分支;branch +9%
未动:Controller 契约测(已有)
排除:无新增

审查者一眼能判断数字是否来自有意义断言。

讲清楚要点

  1. 执行到 ≠ 断言对:覆盖率不测「对不对」,只测「跑过没」;门禁必须配对「必跑套件 + 有意义断言」才有意义。
  2. branch 比 line 更能暴露漏测:读报告优先看红色菱形——if 只走了 true 的地方就是历史漏洞候选区。
  3. 别为数字测样板代码:getter/setter 刷出的百分比没有回归价值,时间要给业务规则与边界。
  4. 遗留债用变更覆盖偿还:全仓一夜还清不现实;「PR 改动行必须有测」是务实且可执法的下限。
  5. 提覆盖主战场在单元层:Mockito 快测便宜精准;E2E 刷覆盖既慢又脆,方向就错了。
  6. 排除名单要写理由:entity/**、config/** 是合理排除(Lombok 样板与装配代码),但不写理由的排除最终会膨胀成「全排除等于无门禁」。

你可能会懵的地方

  • 为什么 100% 行覆盖还有 bug? 并发与时序、真实数据形状、断言太弱、组合状态没覆盖。覆盖率是必要非充分条件——它只能证明「代码被碰过」,不能证明「碰的时候查得严」。

  • mvn test 怎么没有报告? jacoco:report 挂在 verify 阶段。日常快反馈可以只跑 test,看报告必须 verify——这也解释了 CI 为什么用 mvn -B verify。

  • 覆盖率下降是谁的锅? 两种:新代码没带测试,或删了旧测试。PR diff 一眼可辨。把报告当导航而不是指责工具,团队才愿意看它。

  • jacoco 的 check 和 report 有什么不同? report 只生成给人看的 HTML;check 按规则让构建失败——它才是「门禁」。开 check 的时机在团队对阈值达成共识之后。

  • 前端语句覆盖 100% 值得追求吗? 同样可刷。关注交互分支与错误态(error / loading / 空)有没有测,比看总数有用。

错法对照

错法问题正解
只测 getter/setter数字涨、bug 照漏测业务规则与边界
不断言的调用凑覆盖假绿具体断言返回值 / 状态 / 异常
强求全仓 100%形式主义、排除滥签关键包下限 + 变更覆盖
用 E2E 刷覆盖慢脆、方向错误单元层补盲区
删红测保 CI 绿质量假象修代码或修断言

卡点急救

  1. verify 后找不到报告目录:确认 report execution 绑定了 verify phase 且没有 <skip> 参数;路径是 target/site/jacoco/index.html。
  2. 开了 check 全组无法合并:阈值一步到位太狠。降到当前均值附近起步,只锁 service 包,按迭代逐步上调。
  3. 报告里 Lombok 生成的代码拉低百分比:排除 entity/DTO 包(Lombok 样板),或用 lombok.addLombokGeneratedAnnotation = true 让生成方法带注解再排除。

面试常问

  • 覆盖率多少合适? 给框架化答案:绝对数字不重要,分母才重要——「关键业务包(Service 层)分支覆盖 70% 左右,全仓行覆盖 60% 起步,PR 变更行覆盖不下降」。配一句心法:覆盖率是盲区探测器不是目标,虚高的数字比低数字更危险,因为它制造安全感。

  • 为什么 line 覆盖率高还是会出线上 bug? 覆盖率只统计「执行到」,不统计「断言严不严」;分支只走一边、组合状态没覆盖、并发时序真实数据形状都是它的盲区。所以团队必须配套「有意义断言」的文化 + branch 维度审查。

  • 怎么给遗留项目设计覆盖率门禁? 三步:先全仓 report 摸底;再按模块分层设定起点阈值(避免一刀切卡死交付);最后用变更覆盖要求新代码,让债务只减不增。门禁目标是「不再变坏」,不是「一夜变好」。

自测三问

  1. line 与 branch coverage 差在哪里?举一个 line 高 branch 低的例子。
  2. 为什么说覆盖率是地图不是目的地?
  3. 你给团队的三条门禁里,哪一条最能防止「假绿」?

小结一句话

用 JaCoCo 报告找盲区、用有意义的断言守质量、用变更覆盖还债——覆盖率服务于风险,而不是虚荣心。

延伸

  • JaCoCo 官方文档
  • Maven Surefire 插件
  • 下一课:test-ci 把好测试接进流水线——数字与门禁只有跑在共享 CI 上,才对合并真正有约束力。
加载本节练习…
← 上一课E2E 与 Playwright下一课 →CI 中的测试