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综合练习

测试金字塔

单元/集成/E2E 分层;为何需要测试。

30 分钟

测试金字塔

这一课你将能做到

  1. 画出单元 / 集成 / E2E 三层金字塔,说清每层「测什么、不测什么、多快、多贵」
  2. 把 CodeForge backend 现有的 SubmissionServiceTest、MemoryRequestRateLimiterTest 等测试归到正确的层
  3. 本地跑通 mvn test 记录耗时,并写出「改动类型 → 补哪一层」的三条决策规则

为什么要学

想象一个真实场景:你改了 SubmissionService.submit 的语言白名单,把 "sql" 从支持列表里移除。没有测试时你只能手点:登录、进课时、切换语言、提交代码、看报错——五分钟过去你不确定测全了没有,十分钟后你开始敷衍。人话:测试是可无限次重按的验证按钮,按一次毫秒级,结论永远一致;手点是人肉测试,慢、漏、不可重复。

CodeForge 是 backend(Spring Boot + MySQL + Redis + sa-token)加 platform(Next.js)的双端系统,任何一次改动的爆炸半径都比单文件大。.github/workflows/ci.yml 里 backend 跑 mvn -B verify、platform 跑 npm test,正是金字塔在持续集成里的落地:底层多、顶层少,反馈快、成本可控。

面试角度:「你们项目的测试怎么分层?为什么不全写 E2E?」是后端高频题,本课给的就是标准答案的骨架。学完你应能解释「只写 E2E 为什么是反模式」,并知道下一课从哪里动手写第一个单元测试。

还有一层直觉:测试分层不是「职称高低」,而是反馈回路长短。单元失败通常指向一个类;E2E 失败可能指向后端、前端、数据、环境四者之一。所以健康团队本能地「多写能快速红绿的测」——这不是偷懒,是工程理智。

先建立图像

        /\          E2E(少):真实浏览器走用户路径,分钟级,易 flaky
       /  \
      /----\        集成(中):多组件 + 真 MySQL/Redis,秒级
     /      \
    /--------\      单元(多):单类纯逻辑 + mock 依赖,毫秒级
   /__________\
层级测什么典型工具速度CodeForge 例子
单元一个类的分支与边界JUnit 5 + Mockito毫秒SubmissionServiceTest(mock Mapper)
集成SQL、事务、Bean 装配@SpringBootTest + Testcontainers秒UserMapper 对真库的 insert/select 往返
E2E用户关键路径Playwright分钟注册 → 登录 → 提交代码看到「排队中」

金字塔底部宽、顶部窄:Service 单测应占大多数;浏览器冒烟只守「注册、登录、提交判题」这类命门。人话:用便宜的探针量体温,别每次都做全身 CT。

跟着做

步骤 1:给现有测试归类

打开 backend/src/test/java/com/codeforge。对每个 *Test.java 问两句:

  • 有没有 @SpringBootTest 或真数据源?→ 偏集成
  • 只有 @ExtendWith(MockitoExtension.class) + @Mock?→ 单元

你会看到 SubmissionServiceTest、、 全是第二种——它们正是健康的金字塔底部。把归类结果记成三行笔记:「N 个 Service 单测、0 个集成、0 个 E2E」。

RoadmapServiceTest
MemoryRequestRateLimiterTest

步骤 2:跑一次,用秒表建立直觉

cd backend
mvn test

看 Surefire 汇总:Tests run、Failures、总耗时。纯 Mockito 用例应在几秒到几十秒内结束。再对比:

mvn -B verify

verify 多走打包与报告阶段,记下两者耗时差。人话:日常改代码用 test,提交前用 verify 对齐 CI。

步骤 3:亲手写一个「最底层」的测试

新建练习类 PasswordRules.java(规则镜像本站注册逻辑:密码 6–64 位):

public class PasswordRules {
    public static boolean isValid(String password) {
        return password != null && password.length() >= 6 && password.length() <= 64;
    }
}

再写测试:

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

class PasswordRulesTest {

    @Test
    void 六位密码合法() {
        assertThat(PasswordRules.isValid("123456")).isTrue();
    }

    @Test
    void 五位与空值都不合法() {
        assertThat(PasswordRules.isValid("12345")).isFalse();
        assertThat(PasswordRules.isValid(null)).isFalse();
    }
}

运行 mvn test -Dtest=PasswordRulesTest,应秒级绿。这就是底层测试的体验:无容器、无网络、无数据库,红绿之间只隔一次保存。

步骤 4:画本站金字塔草图

纸上画三层并填入:

层你放的内容
顶注册→登录→提交判题;访客打开课时页
中UserMapper / SubmissionMapper 对真 MySQL 的往返;Redis 限流读写
底现有全部 *ServiceTest、PasswordRulesTest 这类纯函数测

对照原则:改校验分支 → 先补底;改 SQL/迁移 → 中层;改整页路由体验 → 才考虑顶层。

步骤 5:写下决策规则

  1. 改 if / 校验 / 白名单 → 先补或改单元(JUnit + Mockito)
  2. 改 SQL / 迁移 / 缓存 TTL / 事务 → 补集成(真库或 Testcontainers)
  3. 改整页路由或跨端联调假设 → 才考虑 E2E 冒烟

若你发现自己「一改校验就去写 Playwright」,停下来:那是把钉子当锤子用。健康团队把 80% 的回归信心放在底部。

讲清楚要点

  1. 测试是可重复的验证,不是上线仪式:改代码的频率远高于发版,验证必须自动化,否则必然漏测。
  2. 反馈回路决定分层:单元失败指向一个类;E2E 失败面横跨前后端与环境。失败要能从底层先收窄,定位成本才低。
  3. E2E 只守关键路径:颜色、边距、每个按钮的文案不适合放在顶层——慢且脆,每多一条就多一份维护税。
  4. 集成层不可省但需克制:mock 挡住了 SQL、事务、唯一索引的真相,这些只能靠真库证明;但数量要少,用命名与 CI 分轨控制成本。
  5. 覆盖率数字不看断言质量:绿测 + 具体断言优先于「百分百行覆盖」,第 10 课展开。
  6. 本地命令与 CI 一致:mvn -B verify 与 npm test,否则「我这能过」合上去红。

你可能会懵的地方

  • 「单元」和「集成」的边界到底在哪? 只测一个类、依赖全是假的 → 单元;至少两个真实组件一起跑(Service + 真 Mapper + 真库)→ 集成。MockMvc 不启浏览器、可 mock Service,是「HTTP 层的快速契约测」,介于两者之间偏下。

  • 为什么「只写 E2E」是反模式? 慢、难定位、对环境敏感。一次失败可能是后端 500、前端路由、还是选择器过期——你不知道。金字塔把细节验证沉到底部。

  • CodeForge 现在集成与 E2E 很少,是不健康吗? 健康状态是底部先厚。现有 Mockito Service 测是正确起点;Testcontainers、Playwright 按风险逐步加,而不是一次堆满。

  • 测试和文档是什么关系? submit_非法语言返回400 这样的测试名 + 断言本身就是不会过期的规格说明,比 Wiki 更可信——Wiki 会忘改,红灯不会撒谎。

  • 前端要不要另建一座金字塔? 要。Vitest 组件测是前端底部,Playwright 是顶部,形状与 Java 侧同构,第 8、9 课展开。

错法对照

错法问题正解
只写 E2E 冒烟慢、脆、难定位底多顶少,E2E 只守命门
只测 happy path边界回归全漏每类至少补一条失败路径
为覆盖率写 assertTrue(true)假绿断言具体状态码 / 字段 / 异常
本地命令与 CI 不一致幽灵红灯对齐 mvn -B verify / npm test
用集成测代替所有单测反馈变慢,没人愿跑纯逻辑单测,SQL 才集成

卡点急救

  1. mvn test 显示 Tests run: 0:测试类没以 *Test 结尾、不在 src/test/java,或类不是包可见以上级别;先对齐 Surefire 默认约定再排查依赖。
  2. 全套件突然要几分钟:有人把 @SpringBootTest 加进了纯逻辑测试。搜日志里的 Started CodeForgeApplication,找到肇事先,把它降级回 Mockito 或切片。
  3. Windows 终端里中文方法名乱码:给 Surefire 配 <argLine>-Dfile.encoding=UTF-8</argLine>,或直接在 IDE 里运行该类。

面试常问

  • 什么是测试金字塔?为什么底部要宽? 单元多、集成中、E2E 少的三层模型。底部宽是因为单元最快最稳、失败定位最准;顶部贵且脆,只放关键路径。倒过来(冰淇淋筒)会让 CI 巨慢且 flaky,团队最终不敢跑测试。

  • 单元测试和集成测试怎么取舍? 问一句:「把依赖 mock 掉之后,我还信不信这个结论?」业务分支、边界校验 → 单元;SQL、事务、缓存 TTL、Bean 装配 → 集成。以 CodeForge 判题接口为例:语言白名单校验用 Mockito 单测,Mapper 对 MySQL 的写入往返用 Testcontainers。

  • 你们项目的测试是怎么落地的? 按 CodeForge 答:backend 每次提交跑 mvn -B verify(JUnit 5 + Mockito 单测为主),platform 跑 npm test;CI 红灯阻断合并,慢测(容器 IT、E2E)分轨到主分支或夜间任务,不让每个 PR 排队。

自测三问

  1. 用一句话解释:为什么金字塔要「底宽顶窄」?
  2. SubmissionServiceTest mock 掉了 Mapper,它属于哪一层?为什么不算集成?
  3. 你改了 Redis 限流的 TTL 逻辑,按决策规则该补哪一层?

小结一句话

多写快测、少写慢测、E2E 只守命门——分层是成本与信心的平衡术。

延伸

  • Martin Fowler · TestPyramid
  • JUnit 5 User Guide
  • 下一课:test-junit 用 JUnit 5 写出第一个完整、可运行的单元测试类。
加载本节练习…
下一课 →JUnit 5 基础