14 · 测试策略与维护方法
这个项目的测试重点不是组件快照,而是模板语言、过滤器、内容转换与高亮恢复等“输入空间大、回归隐蔽”的纯逻辑。
一、测试版图
快照中 *.test.ts 约 4,696 行。最大测试:
| 测试 | 约行数 | 关注点 |
|---|---|---|
renderer.test.ts | 566 | AST 求值、scope、控制流、filters |
shared.test.ts | 455 | variables/frontmatter/selector/format |
parser.test.ts | 444 | AST 与错误恢复 |
tokenizer.test.ts | 382 | 词法边界与位置 |
highlights-manager.test.ts | 253 | 高亮存储管理 |
template-integration.test.ts | 160 | 端到端模板编译 |
highlighter-overlays.test.ts | 151 | quote/overlay 恢复 |
content-extractor.test.ts | 49 | 内容与高亮注入 |
此外多数复杂过滤器有自己的测试文件。
二、Vitest 环境
vitest.config.ts 配置 jsdom,使 DOMParser、document、Element、Range 等浏览器概念可在 Node 测试。
src/utils/__mocks__/webextension-polyfill.ts 提供 storage/tabs/runtime 等 mock,隔离真实浏览器。
适合 jsdom 的部分:
- selector;
- HTML/Markdown 处理;
- text node/Range;
- UI helper 的基本 DOM;
- storage 消息逻辑(配合 mock)。
不适合只靠 jsdom 证明的部分:权限弹窗、service worker suspend、side panel、Safari native host、真实 clipboard 和布局几何。
三、DSL 三层测试
模板语言应分层定位故障。
Tokenizer tests
关注字符如何切 token:
- 普通 text 与
{{/{%; - quotes/escape/newline;
- selector 中
:[]?; - trim markers;
- 数字/boolean/null;
- operator 与 keyword;
- line/column;
- 未闭合字符串/tag。
Parser tests
关注 token 如何形成 AST:
- precedence;
- nested if/for;
- elseif/else;
- set/member;
- filter args;
- stop keyword;
- variable/filter validation;
- 相似名称建议。
Renderer tests
关注语义:
- truthiness;
- scope shadowing;
- loop arrays/objects/JSON;
- nullish;
- contains;
- async resolver;
- deferred variables;
- partial errors;
- whitespace trim。
三层都测的收益是:失败时知道是“没认出 token”“AST 错”还是“执行错”。
四、集成 fixture
utils/fixtures/templates 与 fixtures/expected 包含:
minimal
edge-cases
schema-rich
goodreads
imdb
youtube每组通常有 HTML、template JSON 与 expected Markdown。它们覆盖从抽取、schema、模板到 frontmatter 的完整行为。
Golden file 的价值:格式化回归会直接显示 diff。缺点:预期更新容易被机械接受,因此 review 时必须解释为什么输出变化。
五、过滤器测试模式
一个高质量 filter test 至少覆盖:
正常输入
空字符串/null-like
Unicode/CJK/emoji
带引号、冒号、pipe 的参数
错误参数 validator
JSON array/object 输入
与前后过滤器链组合文件名过滤器还要覆盖 Windows/macOS/Linux 非法字符;日期过滤器要固定时区或 mock time,避免 CI 地区差异。
六、高亮测试的特殊难点
Range 测试不能只比较字符串。应构造:
- 多个 text node;
- 嵌套 inline 标签;
- 重复 exact 文本;
- whitespace 折叠;
- DOM 包装变化后的 quote fallback;
- 跨 block selection;
- element media;
- cache invalidation。
几何 overlay 测试需 mock getClientRects/getBoundingClientRect,并分别验证规划逻辑与真实浏览器渲染。
七、消息与 Storage 测试
WebExtension mock 应能记录:
runtime.sendMessage calls
tabs.sendMessage(tabId, payload)
storage.sync/local get/set
onChanged listeners
scripting injection order
permissions request重点验证“消息协议”,不必把 background 整个加载成黑盒。action string 是跨 bundle API,拼写变化应像 public API 一样测试。
八、浏览器手工矩阵
发布前至少验证:
| 场景 | Chrome | Firefox | Safari |
|---|---|---|---|
| popup clip | ✓ | ✓ | ✓ |
| embedded/side panel | side panel | fallback | fallback |
| reader 原位 | ✓ | ✓ | ✓ |
| reader 独立 fetch | ✓ | permission | native fallback |
| text/element highlight | ✓ | ✓ | ✓ |
| context menu/shortcut | ✓ | ✓ | ✓ |
| clipboard/download | ✓ | ✓ | ✓ |
| template import/export | ✓ | ✓ | ✓ |
| local provider Interpreter | CORS | CORS | CORS/native boundary |
移动 Firefox、iOS/iPadOS Safari 还需触摸、高亮删除、viewport 与 keyboard 单独验证。
九、静态质量检查
当前 package scripts 主要暴露 tests/build/i18n scripts,没有统一 lint script,但仓库有 ESLint/TypeScript 配置。
修改后建议最小验证:
npm test
npx tsc --noEmit
npm run build
npm run build:api
npm run build:cli
npm run check-strings若只改某个 filter,可先跑对应 test,再跑全套。
十、Debug 模式
webpack 用 DEBUG_MODE 全局常量:development 为 true,production 为 false 并被 Terser dead-code elimination。
debugLog(scope, ...) 让开发时能观察抽取、filters、Interpreter、storage 等中间状态,生产不带大量日志。
调试复杂模板时建议记录:
原始模板
tokens / tokenizer errors
AST / parser errors
validation errors
variables keys
render output / deferred flag
post-process output比只看最终 Markdown 更容易定位。
十一、修改任务的回归半径
| 修改 | 必测范围 |
|---|---|
| 新变量 | shared + parser validation + renderer + integration |
| 新过滤器 | filter unit + metadata validation + renderer chain |
| 新 DSL 语法 | tokenizer + parser + renderer + formatter |
| 高亮锚点 | highlighter + overlays + extractor + Reader |
| Reader DOM | apply/restore + highlights + transcript + mobile |
| storage 字段 | old fixture migration + save/load + settings UI |
| message action | sender + background router + content receiver |
| manifest 权限 | 三浏览器 build + real install |
| API/CLI | api integration + CLI stdin/file/fetch + build exports |
十二、维护大控制器的方法
对 popup.ts、reader.ts、highlighter.ts 的修改:
- 先画状态转移,不直接移动代码;
- 找到唯一权威状态;
- 列出所有 observer/listener;
- 明确 apply 与 cleanup 对称性;
- 把纯变换抽成可测函数;
- 避免在多个 bundle 重复创建 mutable singleton;
- 用 generation/idempotency 防重复初始化;
- 真浏览器连续 toggle 5–10 次观察泄漏。
十三、性能回归点
重点 profile:
- 大页面
fullHtmlclone/clean; - Defuddle async timeout;
- 大模板 tokenizer/parser;
- highlights normalized text index;
- MutationObserver 引发 overlay reposition;
- highlights library 大列表 render;
- Reader transcript 与 scroll listeners;
- sync storage 高频写。
已有的 debounce/throttle/cache/version 应在重构后保留语义。
十四、本章检查点
- 能按 tokenizer/parser/renderer 三层设计 DSL 测试;
- 能解释 golden fixture 的价值与风险;
- 能区分 jsdom 可测与真浏览器必测;
- 能按修改类型估算回归半径;
- 能列出大控制器的 listener/cleanup/idempotency 检查。