Skip to content

测试策略 ​

这个仓库敢持续重构,靠的是测试跑得快、门禁卡得死。本地一条 npm test 九秒左右跑完 4610 个用例,这是每次重构"改完就知道有没有弄坏"的底气;往上再叠 e2e、用例套件、视觉基线、突变测试,各层各管一段。读完这一页你会知道:每层防线管什么、怎么加测试、以及一次提交要过哪些门禁。

先看全景:测试金字塔 ​

流程图

越往下跑得越快、定位越准,所以问题尽量在底层拦住:逻辑写成可直测的纯函数,单测秒级反馈;浏览器里的行为交给 e2e;"看起来对不对"这种最难自动化的部分,交给像素基线和人工验收。这个项目没有搭 Angular testbed 基建,是有意的取舍(ADR-10):不硬模拟 DOM,组件测试以逻辑和模板守卫为主。

单元测试:怎么跑、怎么加 ​

bash
cd editor
npm test                 # 全量 223 文件 4610 例(2026-10 基线,数字以 CI 为准)
npx vitest run --coverage # 带覆盖率(本地默认不加载 coverage 配置)
  • 测试放 editor/tests/,目录镜像 src/app;mock 管线的测试在 editor/mock/。src 下不放测试。
  • vitest 跑在 node 环境(不加载 Angular 编译插件,所以快);import 用相对路径,没有 @app alias。
  • 用例名写中文行为句;注释只解释 mock 设计、数值推导和回归锚点,不逐行翻译断言。
  • 组件和服务测试用 Injector.create({ providers: [...] }) 最小装配,input signal 用 ɵSIGNAL 直写;effect 测试套 runInInjectionContext;RxJS 时序用 useFakeTimers 加 TestScheduler marble。
  • 覆盖率阈值 95/90/95/95(语句/分支/函数/行),只在带 --coverage 运行和 CI 时生效。
  • 加新共享纯函数模块必须配单测,先例是 conventions.test.ts。

架构门禁:红即禁合 ​

tests/architecture.test.ts 把开发规范里的分层规则变成机器检查,四条断言:

  1. shared 到 core、features 的 import 数为零。
  2. core 到 features 的 import 数为零。
  3. 模块 import 图无环(DFS 三色标记,值导入口径;import type 编译期擦除不算)。
  4. 手写 .subscribe( 必须同一行配 takeUntilDestroyed(双向文本窗口扫描,注释里出现也不行)。

它还会堵各种逃逸写法:双引号 import、动态 import()、裸目录导入都会被归一化后纳入扫描。所以别想着绕,按规则写最省事。

规范与模板守卫 ​

这些测试盯的不是"代码对不对",是"代码和 MapLibre 规范、和模板的事实是否一致":

  • tests/app/spec-coverage.test.ts 拿 style-spec 包里的 latest.json 当真源逐项对账(槽位、根属性、枚举值、requires)。升级 style-spec 而没补齐中文词表和面板,它就红。
  • tests/app/core/map/map-capabilities.test.ts 对账双引擎能力矩阵:代码里调用的每个 native API 必须在矩阵里登记过。
  • tests/app/core/expression-semantics.test.ts 用官方 createExpression 求值对账,防止表达式改写只保结构不保语义。
  • tests/app/core/history/command.test.ts 锁跨文档单条撤销的语义。

模板盲区与对冲 ​

这个工具链没有模板类型检查通道,模板里引用不存在的成员,tsc 和 vitest 都抓不到。三类盲区都出过真实事故,各有对冲:

盲区事故形态对冲
模板引用不存在的类成员测试全绿,运行时静默空白改公共成员名时同步 grep 模板,或补成员存在性守卫测试
模板用了 ms-* 组件但 @Component 缺 imports静默渲染为空,不报错template-imports-guard:模板用法集必须 ⊆ imports 键集
ng-content 被 @if/@for/@switch 包裹投影内容永不渲染(Angular 官方禁令)壳组件全模板字节钉 + "单一无条件 ng-content"断言 + e2e

所以有三类变更必须带对应守卫或 e2e 证据:改组件公共成员名、新子组件模板引用 ms-*/msTip、动壳组件模板。

e2e:三套体系,别混 ​

仓里有三套浏览器测试,定位完全不同:

体系规模运行性质
tools/e2e.mjs17 套(verify-m65b–m80 + verify-prod)cd editor && npm run e2e回归门禁,大改后必跑
e2e-tests/11 套用例套件node e2e-tests/run.mjs用例文档逐例对应,CI 门禁
e2e-audit/15 域 395 例node e2e-audit/run.mjs all人工审计,只记录不修复,不进任何门禁

tools/e2e.mjs(回归门禁):自管 dev server(:4230),先跑十六套里程碑回归(公式、Select data、树、Global、地图工具、Sources、Monaco、Terrain、持久化、mock MVT、Debug、表达式构建器等),最后自动构建最新 dist 跑 verify-prod 生产冒烟。提速开关:node tools/e2e.mjs m67 按名字筛选、E2E_SKIP_BUILD=1 跳过构建(dist 新鲜检测默认开启,输入指纹命中即复用)、并行默认自动开(按 CPU 数分批,verify-prod 恒殿后串行)。

e2e-tests(用例门禁):docs/spec/usecases/ 里十一篇用例文档,每个用例编号(UC-NN)在对应套件里必须有 uc('UC-NN', …) 断言,双向匹配,缺一边就红。CI 里分两步:零浏览器的 traceability 静态门禁(秒级),然后全量 11 套。

e2e-audit(人工审计):395 例全参数矩阵审计(逐样式类型 × 逐参数 × 逐赋值形态),默认 node run.mjs all 跑 01–11 域,12–15 域(截图像素对比、类型矩阵等)要显式指定域号。退出码恒 0——发现问题是审计的产出而不是失败,结论记在 findings.md,修复走正常开发流程。全量约 10–15 分钟,所以不进 CI。

三套共用的纪律:断言"样式世界"的套件必须装载 tools/fixtures/ 下的冻结样式(tools/lib/load-fixture.mjs),不许锚定开发环境的默认装载——默认样式会随网络漂移,历史上酿过整批陈旧断言的事故。

视觉基线 ​

cd tools && node visual-baseline.mjs:11 个 UI 状态全页截图,与 tools/baseline/zh-cn/ 的基线 PNG 做 pixelmatch 对比,超阈值 exit 1。截图用 e2e 验证过的稳定选择器驱动,外域屏蔽、固定视口,保证确定性。

  • 有意的视觉变更:--update 重录对应帧,基线 PNG 随 PR 提交,并在提交信息里说明意图。
  • nightly 跑在 Linux runner 上,画布区域有跨机 GPU 噪声,阈值放宽到 3%(本地默认 0.4%,可用 VISUAL_TOLERANCE_PCT 覆盖)。超阈值先同机复跑确认,再判断是真回归还是噪声。

突变测试与性能基准 ​

npm run mutation(Stryker)回答"测试能不能发现错误":往四个纯函数域(expression.model 与三个 codec)注入变异体,看测试杀不杀得掉。历史分数从 75.67% 补到 94% 量级,扩域后会回落,先定性残余变异体、不盲目刷分。报告在 reports/mutation/(gitignored),incremental 缓存本地留存,本地跑一次要几分钟,不用频繁跑。

npm run bench(tests/app/core/perf/style-bench.bench.ts)是 StyleStore 操作的性能基准,store 读取面做任何 interop 改造前后的对照数据以此为准,不达标不迁移。

一次提交要过的门禁链 ​

CI(.github/workflows/ci.yml)在 PR 和 push main 时跑四个 job,任何一个红了都不算过:

流程图

大改后的本地清单 ​

按序过一遍,再提交:

  1. npm run typecheck(随改随跑)。
  2. npm test 全量绿,例数写进提交信息。
  3. 涉 UI 的变更跑视觉基线对比;有意变更 --update 重录随 PR 提交。
  4. 大改后 npm run e2e 全量 17 套(跑前确认 :4230 没有陈旧 dev server,见常见问题)。
  5. 运行时规范冒烟 node tools/verify-spec26.mjs。
  6. 行为变更必须带真回归测试;纯重构用现有用例加基线证明零漂移。

测试红了怎么排查,常见原因(端口占用、陈旧 dev server、spec 升级、模板盲区)集中在常见问题。