news 2026/9/25 1:30:59

NG-ZORRO 测试评审指南:用公共契约思维判定测试的 Keep / Rewrite / Remove

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NG-ZORRO 测试评审指南:用公共契约思维判定测试的 Keep / Rewrite / Remove
  • UI组件
  • 前端

【免费下载链接】ng-zorro-antd

Angular UI Component Library based on Ant Design

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载

本指南以 .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); });

按本文方法逐步评审:

  1. 表述契约:该测试想守护的契约是"当nzDisabled为 true 时,使用者不能通过交互选中该项"——这是一个有效的用户可见契约(可观察结果应为:点击后nzSelect/modelChange不触发,或渲染出 disabled 语义,如aria-disabled)。
  2. 检查断言层:isDisabledInternal是内部字段断言,属于实现耦合;应优先断言aria-disabled属性、点击后的输出事件或键盘不可达性。
  3. 查重:搜索同组件 spec,确认是否已有测试守护"disabled 下点击不 emit"这一条件。
  4. 分类:若已有覆盖 →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

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载

相关推荐

上一篇:SecHex-Spoofy终极指南:深度解析Windows硬件身份伪装技术实战应用
下一篇:Bevy 引擎性能剖析完整指南:CPU 运行时、GPU 瓶颈与编译期开销的诊断方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 1:30:23

STM32 HAL库 SBUS解析:DMA循环接收+IDLE中断+状态机实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:28:33

车载总线协议解析与云端诊断设备实测心得

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:27:56

Linux应急响应日志分析:SSH爆破识别与攻击链还原实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:27:32

基于BLE的ESP32无线调试方案解析:从PyBLE到平板开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:27:30

PLC工程师如何用AI提升编程效率与可靠性

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华