- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
本指南以 .agents/skills/test-review/SKILL.md 为骨架,面向 NG-ZORRO(ng-zorro-antd)组件库的贡献者与维护者,讲解如何评审仓库中已有测试的价值:判断某条测试应当保留(Keep)、重写(Rewrite)还是删除(Remove)。读完本篇,你将掌握以"公共契约"(public contract)为核心的评审方法——用可观察的外部行为表述测试目标、识别与实现细节耦合的断言、查重已有规范,并能在不运行测试、不修改生产代码的前提下完成一次结构化的测试评审。
评审的本质:这是一次审阅,不是写测试
该 Skill 开篇就划定了边界:测试评审是一项 review 任务,不是新增测试或修改生产代码的请求。评审者的职责是判断"这条测试是否守护了公共契约",而不是动手补测试、重构实现或改断言。
评审时可以(也应该)查阅以下材料来形成判断:
- 相关的 issue(测试要守护的回归场景)
- 组件文档与 demo(components 目录下各组件自带的
doc/与demo/) - 组件实现源码
- 邻近的 Vitest 规范文件(
*.spec.ts)
同时要注意:除非用户要求执行证据,否则不要运行测试。也就是说,评审结论应建立在代码阅读与契约推导之上,而非依赖跑测试的输出来"猜"测试是否有价值。
第一步:用可观察的措辞陈述被守护的契约
评审的核心产出之一,是把这条测试"声称守护的契约"翻译成可观察的表述:
给定某条件(given a condition),使用者应当观察到某结果(a consumer should observe a result)。
这种 "given → observe" 的句式有严格的价值来源要求:一个有意义的期望值必须有独立来源,例如:
- 已报告的回归(reported regression)
- 公共 API 定义(public API)
- 文档化的行为(documented behavior)
- 无障碍语义(accessibility semantics)
- 浏览器原生行为(browser behavior)
- 用户可见的结果(user-visible result)
换句话说:期望值不能"从实现里现抄"。如果一条测试的期望值只能从被测代码本身推导出来,它就缺乏独立基准,容易滑向"证明代码存在"的无效测试。
仓库中的可观察契约示例
NG-ZORRO 的规范文件大量体现了这种"可观察结果"的写法。例如 checkbox.spec.ts 中的 a11y 用例:
it('should click input a11y correct', async () => { const inputElement = checkbox.nativeElement.querySelector('input'); ... inputElement.checked = true; inputElement.dispatchEvent(new Event('change', { bubbles: true })); await stabilize(fixture); expect(testComponent.checked()).toBe(true); expect(inputElement.checked).toBe(true); expect(testComponent.modelChange).toHaveBeenCalledTimes(1); });这里守护的是"用户通过原生 checkbox 输入触发的变更,会反映到组件模型并发出输出事件"——输入是浏览器原生事件,输出是公开的modelChange,期望值完全独立于实现细节。
再看 alert-marquee.spec.ts 中的无障碍断言:
it('should set aria-hidden="true" on the second track for accessibility', () => { expect(tracks[1].getAttribute('aria-hidden')).toBe('true'); });这条测试守护的契约是"重复轨道对辅助技术隐藏"——来源是无障碍语义(ARIA),属于独立规范,而非实现偶然产物。
第二步:优先断言外部可观察层,警惕实现耦合断言
Skill 明确给出了断言层的优先级:
优先断言渲染后的 DOM、ARIA 语义、发射的输出事件、公共 API 行为和交互结果。
相应地,以下类型的断言应被视作与实现耦合(implementation-coupled),除非该测试存在独立的视觉或兼容性契约:
- 私有辅助函数的断言(private helpers)
- 中间状态的断言(intermediate state)
- 临时 class 的断言(temporary classes)
- CSS 自定义属性的断言(CSS custom properties)
- 孤立样式声明的断言(isolated style declarations)
举例来说,checkbox.spec.ts 中断言ant-checkbox-wrapper、ant-checkbox、ant-checkbox-input、ant-checkbox-inner等 class 是否存在的用例,需要结合契约背景判断:如果这些 class 是主题系统、样式定制或视觉契约的一部分(NG-ZORRO 的样式体系见 components/style 目录),它们就有独立的视觉/兼容契约;如果只是实现偶然引入的临时标记,则属于实现耦合。
segmented.spec.ts 中大量断言nz-selected、nz-disabled等属性渲染与键盘方向键行为、thumb 动画状态的用例,则分别对应"键盘可操作性与选中态"这一可观察契约,以及"动画视觉结果"这一独立契约——两者都有外部可观察性。
判断技巧:问自己"这条断言若失败,使用者真的会看到问题吗?"若失败只会暴露内部实现细节,而用户界面与交互毫无变化,这条断言很可能就是实现耦合的。
第三步:查重——已有规范是否已守护同一条件
在给出结论前,必须检查现有 spec 是否已经守护了相同的条件。这需要:
- 搜索同一组件目录下的全部
*.spec.ts与相邻组件的规范 - 对照 issue、文档与 demo,确认"同一回归场景"是否已被另一条测试覆盖
- 特别留意跨文件的重复守护(NG-ZORRO 的测试运行环境是共享的,见下文"环境事实")
重复覆盖本身不是罪,但若两条测试守护的契约完全相同、断言层也相同,就应合并或删除冗余。
第四步:给出分类结论——Keep / Rewrite / Remove
每一条被评审的测试都应归入三类之一,且先亮结论,再给最有力的几条理由:
| 分类 | 判定标准 | 典型场景 |
|---|---|---|
| Keep(保留) | 独立指定(independently specified)、外部可观察(externally observable)、不重复(non-duplicative) | 守护已报告回归、公共 API 行为、ARIA 语义、浏览器原生行为的测试 |
| Rewrite(重写) | 想守护的回归本身是有效的,但断言与实现耦合 | 例如:想验证"禁用状态下不可选",却断言了某个内部状态字段或临时 class |
| Remove(删除) | 期望值从同一实现推导而来、只证明"存在"、或与已有契约重复 | 例如:断言私有字段初值、断言内部 flag 翻转、与已有测试守护同一条件 |
此外,Skill 特别说明:只有在被要求时才给出重写方向(rewrite direction)。默认输出应聚焦分类与理由,不要越界给出完整重写方案。
结合仓库事实:测试评审发生的环境
为了让评审结论更贴合 NG-ZORRO 的实际,这里补充几个仓库级的测试基础设施事实(评审时理解它们有助于判断"重复"与"耦合"):
- 测试运行器是 Vitest。测试脚本见 package.json(
test/test:watch通过 Nx 运行ng-zorro-antd-lib测试),配置见 vitest.config.mjs。 - 串行且隔离的执行模型。vitest.config.mjs 中
fileParallelism: false与isolate: true的注释说明:部分历史遗留 spec 仍通过 overlay、viewport mock、timers 和原型 spy 共享浏览器级状态,因此文件串行执行、逐文件隔离,直到这些 spec 不再依赖全局浏览器状态。 - 共享的全局环境重置。vitest-setup.ts 在每个用例前后统一处理:
beforeEach配置provideZonelessChangeDetection()与provideNzDateFnsAdapter();afterEach清理 fake timers、恢复 mock(vi.restoreAllMocks())、复位视口与 rAF、清理 body 中遗留的 overlay 与测试根节点。这意味着评审时若看到某测试"依赖另一个 spec 留下的全局状态",它很可能在隔离环境下并不稳定——这类测试的契约表述往往也有问题。 - 可用的测试辅助工具。components/core/testing 提供了
testDirectionality、updateNonSignalsInput、stabilize(见 zoneless-helpers.ts)、dispatch-events、type-in-element、mock-ng-zone等工具;checkbox.spec.ts 中的testDirectionality, updateNonSignalsInput即来自该目录。评审中若发现测试用手写的setTimeout/detectChanges序列替代这些稳定化工具,可作为"重写方向"的候选理由。 - 覆盖率报告。
coverage配置输出 html / text-summary / lcovonly / cobertura 到coverage-report(见 vitest.config.mjs),但要注意:覆盖率是度量指标,不是测试价值的来源——"这行没被覆盖"不等于"该补一条实现耦合的测试"。
一个完整的评审示例(方法论演示)
假设你在components/xxx/xxx.spec.ts中评审这样一条测试(示意):
it('should disable the item', () => { component.disabledState = true; expect(component.isDisabledInternal).toBe(true); });按本文方法逐步评审:
- 表述契约:该测试想守护的契约是"当
nzDisabled为 true 时,使用者不能通过交互选中该项"——这是一个有效的用户可见契约(可观察结果应为:点击后nzSelect/modelChange不触发,或渲染出 disabled 语义,如aria-disabled)。 - 检查断言层:
isDisabledInternal是内部字段断言,属于实现耦合;应优先断言aria-disabled属性、点击后的输出事件或键盘不可达性。 - 查重:搜索同组件 spec,确认是否已有测试守护"disabled 下点击不 emit"这一条件。
- 分类:若已有覆盖 →Remove(重复);若没有 →Rewrite(回归有效、断言耦合),并在被要求时给出改为断言输出事件/ARIA 的方向。
评审清单(可直接复用)
- 该测试守护的契约能否用 "given → observe" 表述?
- 期望值是否有独立来源(回归、公共 API、文档、ARIA、浏览器行为、用户可见结果)?
- 断言是否落在渲染 DOM / ARIA / 输出事件 / 公共 API / 交互结果上?
- 是否存在对私有 helper、中间状态、临时 class、CSS 自定义属性、孤立样式声明的断言?
- 同一条件是否已被现有 spec 守护?
- 分类是否清晰(Keep / Rewrite / Remove),理由是否只保留了最强的几条?
- 是否只在被要求时才给出重写方向?
掌握这套方法后,你可以在 NG-ZORRO 的任意组件规范文件(如 components/table 下数十个 spec、components/select 的规范集)上执行结构化评审:先分类、再给理由、必要时给方向,让每个测试都明确回答"它守护了什么用户看得见的契约"。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
5分钟上手PowerToys文本提取器:一个能从屏幕任意位置提取文字的OCR工具
5分钟上手PowerToys文本提取器:一个能从屏幕任意位置提取文字的OCR工具 PowerToys文本提取器是微软开源套件PowerToys中的一个模块,它基
桌面应用开发工具用黑盒契约测试守护 Manifest Gateway:读懂 contracts/gateway 的公共 API 稳定性保障
用黑盒契约测试守护 Manifest Gateway:读懂 contracts/gateway 的公共 API 稳定性保障 Manifest 是一个开源 LLM
AI 应用LLMOps可观测性ppf-contact-solver高级技巧:5个优化接触检测性能的实用方法
ppf contact solver高级技巧:5个优化接触检测性能的实用方法 ppf contact solver 是一款强大的物理模拟接触求解器,专门用于处理
物理引擎高性能计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考