Plate 测试快慢车道治理实战:docx 与 docx-io 包测试套件回归快速通道
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
Plate 仓库维护着一套"快速测试通道 / 慢速测试通道"双车道体系:默认pnpm test只跑快速套件,凡是依赖真实 DOCX 文件、JSZip 打包或重型 fixture 的用例都被命名为*.slow.ts[x]挪入慢车道,避免拖慢日常开发与 CI。本文以 2026-03-23-docx-fast-lane-reclaim.md 记录的一次"快车道回收(Fast Lane Reclaim)"任务为主线,完整还原 docx 与 docx-io 两个包共 16 个纯函数测试套件如何从慢车道迁回快速通道,以及哪些测试必须继续留在慢车道、背后基于什么判定标准。读完你将掌握 Plate 双车道测试体系的分流规则、迁移操作与验证命令,能够为其他包重复这套"快车道回收"流程。
背景:Plate 的测试快慢车道分流机制
Plate 的测试被拆分为两条互斥的车道,其分流规则定义在 tooling/config/test-suites.mjs 中:
- 快速通道(Fast Lane):匹配
TEST_FILE_PATTERNS,即apps/**/*.spec.{ts,tsx}、packages/**/*.spec.{ts,tsx}、tooling/scripts/**/*.test.mjs,由pnpm test(bun tooling/scripts/test-fast.mjs)执行; - 慢速通道(Slow Lane):匹配
TEST_SLOW_FILE_PATTERNS,即apps/**/*.slow.{ts,tsx}、packages/**/*.slow.{ts,tsx},由pnpm test:slow(bun tooling/scripts/test-slow.mjs)执行。
命名后缀即车道标识:*.spec.ts是快车道,*.slow.ts是慢车道。同目录下可以同时存在两份测试——慢车道版本通常被重命名为*.slow.ts从快车道"摘除"。
快车道并非没有上限:test-suites.mjs定义了硬性阈值与预警区(阈值区分本地与 CI,CI 噪音更大因此放宽一档):
| 阈值 | 本地 | CI |
|---|---|---|
单个用例慢速阈值FAST_TEST_SLOW_CASE_THRESHOLD_MS | 75ms | 90ms |
单文件总耗时慢速阈值FAST_TEST_SLOW_FILE_THRESHOLD_MS | 150ms | 180ms |
预警区用例阈值FAST_TEST_WARN_CASE_THRESHOLD_MS | 60ms | 75ms |
预警区文件阈值FAST_TEST_WARN_FILE_THRESHOLD_MS | 120ms | 150ms |
pnpm test:slowest(tooling/scripts/test-slowest.mjs)会运行快车道套件、解析 JUnit 报告并自动按耗时排序,一旦有用例/文件越过慢速阈值就输出错误并让 CI 失败,提示"把该 spec 移到*.slow.ts[x]"。换句话说:进入慢车道容易,回来难——这也正是本次 docx 快车道回收任务的意义所在。
任务目标:把便宜的确定性测试从慢车道收回
本次任务(文档标题Docx Fast Lane Reclaim,状态completed)的目标非常聚焦:把 docx 与 docx-io 两个包中"便宜且确定性"的 helper 级测试从*.slow.*迁回快速通道。所谓"便宜且确定性",从源码结构看,是指这些测试只对纯函数做单元断言:输入是字符串/HTML,输出是可预测的结构化结果,不读取磁盘 fixture、不构造 OOXML zip 包、不依赖真实 DOCX 样本。
任务边界由 Constraints 明确约束:
- 不做新的覆盖率工作(no new coverage work)——只移动测试文件归属,不新增测试;
- 不做运行时改动,除非重命名测试暴露了真实 bug(no runtime changes unless a renamed test exposes a real bug);
- 不虚假回收fixture-heavy 或 zip-heavy 的套件(no fake reclaim of fixture-heavy or zip-heavy suites)——依赖重型夹具的测试绝不假装它跑得快而硬塞回快车道。
Scope:16 个文件迁回快车道,4 个文件继续留在慢车道
迁回快车道的 16 个文件
docx 包 docx-cleaner utils(8 个),均在 packages/docx/src/lib/docx-cleaner/utils:
cleanDocxImageElements.slow.ts→cleanDocxImageElements.spec.tscleanDocxListElementsToList.slow.ts→cleanDocxListElementsToList.spec.tsdocxListToList.slow.ts→docxListToList.spec.tsgetRtfImageHex.slow.ts→getRtfImageHex.spec.tsgetRtfImageMimeType.slow.ts→getRtfImageMimeType.spec.tsgetRtfImagesByType.slow.ts→getRtfImagesByType.spec.tsgetRtfImagesMap.slow.ts→getRtfImagesMap.spec.tsgetVShapeSpid.slow.ts→getVShapeSpid.spec.ts
docx-io 包(8 个):
preprocessMammothHtml.slow.ts→preprocessMammothHtml.spec.ts(位于 packages/docx-io/src/lib)- color-conversion.spec.ts
- font-family-conversion.spec.ts
- image-dimensions.spec.ts
- image-to-base64.spec.ts
- list.spec.ts
- unit-conversion.spec.ts
- url.spec.ts
继续留在慢车道的文件
packages/docx/src/lib/docx-cleaner/cleanDocx.slow.tspackages/docx/src/lib/docx-cleaner/utils/getVShapes.slow.tspackages/docx-io/src/lib/internal/docx-document.slow.tspackages/docx-io/src/lib/internal/html-to-docx.slow.ts- 以及 app 与包内 fixture-heavy 的
docx/docx-io集成测试文件
为什么这 16 个文件"配得上"快车道
判定一个测试能否迁回快车道,核心看它是否为纯函数级、确定性、零重资产依赖的单元测试。逐一看这些被回收的用例即可印证:
docx-cleaner 的图片与列表清洗函数
cleanDocxImageElements负责把 DOCX 转换 HTML 中的本地图片引用恢复为可用的src。其测试(cleanDocxImageElements.spec.ts)直接构造DOMParser解析出的<img>元素并断言三种典型行为:保留外部 alt URL、用 RTF 中恢复出的 data URI 替换本地file:///引用、删除无法解析的本地图片。输入输出均为字符串/内存 DOM,天然确定性。
getRtfImagesMap(getRtfImagesMap.ts)则是 RTF 图片索引的纯函数:内部调用getRtfImagesByType(rtf, 'i', String.raw\shppict)与getRtfImagesByType(rtf, 's', String.raw\shp)两类 RTF 图片声明,以spid为键合并成映射。从源码结构看,这类函数只做正则扫描与对象合并,没有任何 IO,属于典型的快车道候选。
同一批的getRtfImageHex、getRtfImageMimeType、getRtfImagesByType、getVShapeSpid、cleanDocxListElementsToList、docxListToList同理——它们都在内存中对 RTF 字符串或 HTML 字符串做解析/清洗,单测成本在毫秒级。
docx-io 的纯工具函数
docx-io 是导出 DOCX(HTML → OOXML)方向的包,其 internal/utils 下被回收的七个工具测试同样是纯函数验证:
color-conversion:RGB / HSL / 简写 hex 到 DOCX hex 格式的转换,测试断言rgbRegex、hslRegex、hexRegex、hex3Regex的匹配行为(如rgb(255, 0, 0)匹配而#FF0000不匹配),以及各转换函数输出;font-family-conversion、unit-conversion、url:字符串与数值换算类纯逻辑;image-dimensions、image-to-base64:图片尺寸解析与 base64 编码;list:列表结构映射。
这些测试共同特点是无fs读盘、无JSZip、无真实 DOCX 样本,全部可在一个进程中并行跑完。
preprocessMammothHtml:一次"带着 token 的纯函数"迁移
preprocessMammothHtml.ts 是 mammoth(DOCX → HTML)输出后处理函数,负责把注释从<dl>元素中提取出来、将comment-ref-{id}锚点替换为[[DOCX_COMMENT_REF:id]]这样的 token,返回{ html, commentById, commentIds }三元组。整个处理只依赖DOMParser与正则,属于确定性内存变换,因此它的 spec 也完全适合快车道。
哪些必须继续留在慢车道:判定红线
对照"不虚假回收"约束,四类文件被明确排除在回收之外,各自原因从源码中可以确认:
cleanDocx.slow.ts是 fixture-heavy 的典型:cleanDocx.slow.ts 通过fs.readFileSync读取../docx-cleaner/__tests__/input/*.html与output/*.html成对夹具做全量对比断言(如 whitespaces-1、whitespaces-2 等输入/输出样本),测试过程中存在磁盘 IO 与较大 HTML 解析开销,放回快车道必然超阈值;getVShapes.slow.ts:VML shape 提取涉及较重的 RTF/HTML 解析路径,维持慢车道归属;docx-document.slow.ts:docx-document.slow.ts 直接new JSZip()构造 OOXML zip 包并断言 relationships 的 OOXML 命名空间与自增 id,zip 打包是典型的慢操作;html-to-docx.slow.ts:完整 HTML → DOCX 管线转换,同样依赖 zip 与 OOXML 生成;- docx-io
__tests__下的block_quotes.slow.tsx、headers.slow.tsx、inline_formatting.slow.tsx、links.slow.tsx、tables.slow.tsx:这些是覆盖具体排版场景的集成用例,属于"app 与包内 fixture-heavy 集成文件",保持慢车道。
判定红线可以总结为:只要测试路径里出现fs读夹具、JSZip打包、完整 DOCX 管线或多文件场景组合,就一律不回收;只有纯字符串/内存 DOM 的确定性单元测试才值得迁回快车道。
验证流程:回收后如何证明没有弄虚作假
本次任务在Verification与Result中给出了完整的验证命令序列,逐条说明其作用:
1. 定向验证被重命名的文件
先用 bun 直接跑被回收的 16 个 spec,确认重命名后测试仍全绿:
bun test packages/docx/src/lib/docx-cleaner/utils/cleanDocxImageElements.spec.ts packages/docx/src/lib/docx-cleaner/utils/cleanDocxListElementsToList.spec.ts packages/docx/src/lib/docx-cleaner/utils/docxListToList.spec.ts packages/docx/src/lib/docx-cleaner/utils/getRtfImageHex.spec.ts packages/docx/src/lib/docx-cleaner/utils/getRtfImageMimeType.spec.ts packages/docx/src/lib/docx-cleaner/utils/getRtfImagesByType.spec.ts packages/docx/src/lib/docx-cleaner/utils/getRtfImagesMap.spec.ts packages/docx/src/lib/docx-cleaner/utils/getVShapeSpid.spec.ts packages/docx-io/src/lib/preprocessMammothHtml.spec.ts packages/docx-io/src/lib/internal/utils/color-conversion.spec.ts packages/docx-io/src/lib/internal/utils/font-family-conversion.spec.ts packages/docx-io/src/lib/internal/utils/image-dimensions.spec.ts packages/docx-io/src/lib/internal/utils/image-to-base64.spec.ts packages/docx-io/src/lib/internal/utils/list.spec.ts packages/docx-io/src/lib/internal/utils/unit-conversion.spec.ts packages/docx-io/src/lib/internal/utils/url.spec.ts注意:慢车道 runner 对显式路径做了./前缀归一化(见 test-slow.mjs 的runFiles),而快车道 runner 直接用原始路径,所以定向跑的时候要把路径写成不带./的相对路径,与上例一致。
2. 性能画像:确认回收后仍在阈值内
pnpm test:profile -- --top 25 packages/docx packages/docx-iotest:profile对应bun tooling/scripts/test-slowest.mjs --profile:它先执行快车道 runner 并输出 JUnit 报告,再解析出 Top 25 慢用例与 Top 20 慢文件,标记越过慢速阈值(!)与预警区(~)的条目。--profile只输出画像不使 CI 失败,因此回收后先用它确认这 16 个文件的耗时确实落在快车道预算内,这是"没有虚假回收"的直接证据。文档 Result 中同时记录了pnpm test:slowest -- --top 25 packages/docx packages/docx-io作为强校验版本。
3. 慢车道回归:确认遗留文件依然被正确收集
pnpm test:slow -- packages/docx packages/docx-iotest:slow用TEST_SLOW_FILE_PATTERNS收集*.slow.{ts,tsx}文件;回收后 docx 包内只剩cleanDocx.slow.ts、docx-io 包内只剩docx-document.slow.ts、html-to-docx.slow.ts与__tests__下的五个集成 slow 文件。这条命令确保慢车道仍能正常发现并执行它们,而不是被意外"丢"在两条车道之间。
4. 依赖与构建门禁
pnpm install pnpm turbo build --filter=./packages/docx --filter=./packages/docx-io pnpm turbo typecheck --filter=./packages/docx --filter=./packages/docx-io pnpm lint:fix重命名只改测试文件,不影响库代码,但任务仍要求跑完依赖安装、turbo 构建与类型检查,确认两个包对外产物不受影响;pnpm lint:fix(biome check . --fix)用于清理重命名过程可能引入的格式问题。
双车道基础设施:回收背后 runner 的实现细节
理解回收任务,还需要知道快车道 runner 的两个关键机制(均可在 tooling/scripts/test-fast.mjs 中确认):
路径过滤逻辑:runner 支持静态路径(等于/前缀/包含匹配)与动态 glob(isDynamicPattern)两种过滤;不带参数时默认跑全部*.spec.{ts,tsx}文件。因此pnpm test:profile -- --top 25 packages/docx packages/docx-io实际是把packages/docx、packages/docx-io作为前缀过滤器,只对该目录树内的快车道文件做画像。
mock 隔离与 JUnit 合并:runner 会静态分析测试文件及其本地导入链中是否出现mock.module((通过LOCAL_IMPORT_PATTERN正则递归解析相对导入),凡是依赖mock.module的 spec 被单独隔离成批次运行(isolated-{index}),其余共享批次运行;使用--reporter=junit --reporter-outfile时,多个批次的 JUnit XML 会被合并成单个<testsuites>报告。这正是test-slowest能对全部快车道用例做耗时统计的基础。
这套机制意味着:测试归属(*.spec.ts/*.slow.ts)直接决定其在流水线中的执行频率与性能门禁。快车道每次pnpm test与 CI 都会跑,慢车道只在test:slow/test:all中跑。因此像 docx/docx-io 这样被高频改动的包,把廉价的确定性单测从慢车道回收回来,能显著降低日常验证的等待成本——这正是Fast Lane Reclaim的价值所在。
任务结果总结
本次快车道回收最终落地为:
- 16 个
*.slow.ts测试文件重命名为*.spec.ts迁回快车道(docx 8 个、docx-io 8 个),全部通过定向bun test验证; - 4 个 fixture-heavy / zip-heavy 测试文件(
cleanDocx.slow.ts、getVShapes.slow.ts、docx-document.slow.ts、html-to-docx.slow.ts)及 docx-io 的集成 slow 文件继续留在慢车道,未做任何"虚假回收"; - 通过
test:profile画像确认回收后文件耗时落在快车道阈值内,并通过test:slow、pnpm install、turbo build/typecheck、lint:fix全部门禁。
如果要在其他包复制这套流程,操作顺序是:先用pnpm test:profile -- --top 25 <包路径>找出候选(纯函数、无磁盘夹具、无 zip 依赖、确定性输出),把其 spec 从*.slow.ts重命名为*.spec.ts,定向跑一遍确认全绿,再跑 profile 确认耗时在阈值内,最后补跑慢车道回归与构建/类型检查门禁。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考