测试与故障排查
测试与故障排查 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
「dev 里好好的,build 就报错」是 App Router 最高频的事故。本课建立三层防线:核心路由的冒烟检查、写入与恢复的回环验证、以及一套「先复现再修复」的排查流程——每个修复都留下可复现的证据。
问题场景
你改了一行代码,dev 一切正常,npm run build 却炸了:某个页面在构建期预渲染时去连数据库,而构建环境根本没有库文件。事后你修好了,但下次换个姿势又踩——因为没有测试兜底,也没留下「当时是怎么坏的」的记录。
核心解释
先理解 dev 与 build 的差异:next dev 按需渲染、报错宽松;next build 会把所有没有动态信号的页面在构建环境里预渲染——这时执行的代码一旦访问数据库、读环境变量,都可能当场失败。排查构建问题的第一问永远是:「这个页面为什么会在构建期执行?」
# 三个检查各跑一遍,差异就是线索
npm run lint
npm run typecheck
npm run build
核心路由冒烟:服务起来后挨个请求,断言状态码:
// tests/smoke.mjs(与工程里 scripts/e2e-core.mjs 同思路)
const BASE = "http://localhost:3000";
const checks = [
["/", 200],
["/courses", 200],
["/courses/n07-server-data-access", 200],
["/courses/not-exist", 404],
["/api/notes?page=1", 200],
];
for (const [path, expected] of checks) {
const res = await fetch(BASE + path);
const ok = res.status === expected;
console.log(ok ? "PASS" : "FAIL", path, res.status, "期望", expected);
if (!ok) process.exitCode = 1;
}
写入与恢复回环:冒烟只证明「读没问题」,写链路要单独验证——建一条测试笔记 → 读回字段一致 → 导出备份 → 重建空库 → 导入恢复 → 再读回。任何一步失败都能精确定位是写入、备份还是恢复的问题。
故障要留证据:修复一个 bug 的交付物有三样——复现步骤(最小命令序列)、失败时的报错原文、修复后的验证输出。存进 data/ 或提交信息里,下次同类问题翻的是档案,不是记忆。
示例拆解
完整排一遍本机工程:
npm run build——观察输出表:静态(○)/ 动态(ƒ)各是哪些路由;某个「该动态的页面」被标成静态,就是事故预备役。npm run test(vitest)与冒烟脚本全绿。- 制造一次故障:把笔记页的 force-dynamic 删掉再 build,观察它变静态、预渲染时读库报错——记下报错原文。
- 加回 force-dynamic 修复,把「复现步骤 + 报错 + 修复验证」三件套存档。
关键点:测试不是追求覆盖率数字,而是把「你怕它坏的地方」固化成可自动重放的断言。个人项目优先保三处:核心路由、写操作、备份恢复。
动手任务
在本机工程里完成:
- 跑通 lint / typecheck / build 三连,把 build 输出的路由类型标记存档。
- 给核心路由写冒烟清单(含一个必须 404 的路径),接入 npm script。
- 完整走一次「写入 → 导出 → 重建 → 恢复 → 验证」回环,失败过就留下证据。
常见错误
- 只信 dev 环境绿就交付:构建期预渲染、生产压缩、缓存行为只在 build/start 里暴露。
- 测试断言内部实现:断言「调用了某函数」一重构就废;应断言可见行为(状态码、响应字段)。
- 修完不留证据:同一个坑三个月后再踩,成本翻倍。
- 测试数据混进真实库:冒烟脚本往正式数据里插记录;测试要用独立数据目录(REACTLAB_DATA_DIR 指到临时路径)。
检查与复盘
自查清单:
- build 输出的路由类型逐个过目,动态页没有被误标成静态
- 冒烟清单覆盖核心读、一个 404、一次写回环
- 最近一次修复有复现步骤与前后输出存档
参考资料
- Next.js 文档:Testing(Vitest) —— 官方 Vitest 集成指南
- Next.js 文档:error 文件约定 —— 运行时错误的边界处理
- Vitest 文档 —— 本项目使用的测试运行器
读完这一课了?
标记完成后,工作台会安排接下来的学习。