关于 The Standard Testing
The Standard Testing 是面向采用 The Standard 构建的软件的测试技能。它规定 TDD、验证、异常、服务、控制器和 UI 测试实践,并提供测试顺序与约定。
主要功能
- 01规定先失败的 TDD 步骤
- 02使验证测试对齐存储约束
- 03明确服务测试职责
- 04涵盖控制器与端点测试
- 05提供 UI 组件测试指引
- 06规定测试结构与命名约定
适用场景
- 01规划服务例程的新测试
- 02核对基础层验证与存储约束
- 03测试异常映射和依赖调用
- 04组织控制器及 UI 组件测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-testing安装后在 Claude Code 中输入/the-standard-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsoncontracts/contracts.jsonexamples/bad/example_bad_test.csexamples/good/example_validation_test.csexample_foundation_test.csrules/rules.jsonrules.mdtemplates/service_test_template.cstest_class_root_template.cstemplates/controllers/acceptance/controller_acceptance_test_class_root_template.cscontroller_acceptance_test_template.cstemplates/controllers/unit/controller_unit_test_class_root_template.cscontroller_unit_test_template.cstemplates/foundations/foundation_service_test_template.csfoundation_test_class_root_template.cstemplates/processings/processing_service_test_template.csprocessing_test_class_root_template.csvalidations/anti-patterns.mdchecklist.md详细介绍
The Standard 测试
此技能的用途
此技能规定如何测试、验证和证明 The Standard。 其涵盖测试驱动开发、验证策略、异常映射、单元测试结构、控制器测试和 UI 组件测试。
明确的覆盖范围
此技能明确涵盖:
- Services 章节中的基础服务实现及验证/测试模式
- 结构、逻辑、外部和依赖项验证测试
- 异常映射和类别测试
- 处理服务的测试职责
- 编排服务的调用顺序测试
- 聚合服务的测试限制
- REST 控制器单元测试和验收测试
- 基础组件、核心组件和页面的 UI 组件测试
- 所提供的实现规范中关于单元测试、部分测试组织、约定、AAA 和测试顺序的章节
- 与编写测试和验证实现相关的 TDD FAIL/PASS 规范
使用时机
编写、审查、扩展、修复或安排测试顺序时,请使用此技能。 在决定优先测试什么、如何在测试中映射异常,或如何证明符合 The Standard 的流程时,请使用此技能。
核心测试准则
- 遵循 TDD。
- 先编写失败的测试。
- 确认测试确实失败。
- 编写通过测试所需的最少实现。
- 确认所有相关测试套件均通过。
- 在不改变行为的前提下重构。
- 重复以上步骤。
验证测试规则
验证的事实来源
-
验证规则必须从所有权威来源推导:
- 基础服务业务规则
- 存储层配置(
StorageBroker.[Entity].Configurations.cs) - 使用方式所隐含的领域预期
-
存储配置代表最低强制约束:
- 必填与可选
- 最大长度
- 最小长度(如果已配置)
- 适用时的精度/比例/格式
-
基础服务代表强制执行边界:
- 所有可能导致持久化失败的约束,必须在到达存储层之前验证
- 在可以确定地避免数据库异常时,验证必须阻止此类异常
验证一致性规则
-
基础验证必须不弱于存储约束。
-
以下情况允许(强化规则):
- 存储层:可选 → 基础层:必填
- 存储层:可选 → 基础层:受限(最小/最大长度)
- 存储层:最大长度 → 基础层:更小的最大长度
-
以下情况不允许(规则弱化或缺失):
- 存储层:必填 → 基础层:未按必填进行验证
- 存储层:最大长度 → 基础层:未进行长度验证
-
一致性违规必须视为:
- 设计缺陷
- 测试失败条件
- 审查阻碍
验证职责规则
-
不得依赖数据库来执行以下验证:
- 必填字段验证
- 长度验证
- 格式验证
-
未事先验证、但可接受由数据库强制执行的约束仅限于:
- 外键约束
- 唯一性/重复键约束
- 并发约束
-
凡是能在基础服务中确定地执行的验证,都必须在那里执行
根据存储配置推导验证测试
-
存储配置(
StorageBroker.[Entity].Configurations.cs)定义的是约束,而非验证行为。 -
对于存储配置中定义的每项约束,基础服务中都必须有对应的验证规则:
- 必填字段
- 长度约束(最小值/最大值)
- 适用时的精度/格式约束
-
验证测试必须:
- 仅针对基础服务的验证方法
- 不得直接测试存储配置行为
- 证明每项由存储定义的约束都在基础层得到强制执行
-
对于每项属性约束,验证测试必须聚焦于违反约束和越界的情况:
-
无效情况(违反约束)→ 失败
- 缺少必填字段/字段为 null
- 值超过最大长度
- 值低于最小长度(如适用)
-
越界情况:
- 刚超过最大值 → 失败
- 刚低于最小值 → 失败(如适用)
-
-
有效场景(处于可接受范围内)由逻辑测试覆盖,验证测试中不得重复测试。
-
存储配置与基础层验证之间缺少一致性,必须视为:
- 设计缺陷
- 测试失败条件
- 评审阻塞项
-
自动化可以帮助识别约束(例如,通过 EF 元数据),但:
- 生成的测试仍必须验证基础服务行为
- 自动化不得导致测试直接验证数据库层
-
所有验证测试必须:
- 遵循标准命名约定
- 明确且能清楚表达意图
验证顺序
- 首先进行结构验证。
- 其次进行逻辑验证。
- 第三进行外部验证。
- 当外部资源是失败源时,再进行依赖项验证。
熔断式验证
- Null 检查和其他硬停止保护必须立即中断。
- 如果继续执行会导致无效解引用或无意义的工作,则立即停止。
连续验证
- 当多个字段可能各自独立地无效时,先收集所有错误,再抛出异常。
- 使用可添加或更新的异常数据。
- 使用包含条件和消息的动态规则。
- 使用验证收集器例程。
- 收集器中存在错误后再抛出异常。
混合式连续验证
- 验证父对象后,再验证子属性。
- 将嵌套验证拆分为多个层级,以避免意外的 null 引用异常。
基础服务测试规则
- 首先测试正常路径。
- 然后测试结构验证。
- 然后测试逻辑验证。
- 然后测试外部验证。
- 然后测试依赖项验证。
- 然后测试依赖项异常。
- 然后测试服务异常。
- 始终验证日志记录和 Broker 调用。
- 始终验证没有发生不需要的调用。
- 始终确保验证行为和异常行为局部且明确。
处理服务测试规则
- 测试高阶逻辑,而非原始 Broker 细节。
- 仅验证处理服务实际使用的内容。
- 测试转换器。
- 示例:对象 → bool 或对象 → 计数。
- 测试组合。
- 示例:检索 + 添加、检索 + 修改、确保存在、upsert。
- 测试处理服务对基础服务异常的映射。
编排服务测试规则
- 测试多实体流程组合。
- 存在合约映射/分支时,测试相应逻辑。
- 当流程依赖顺序时,测试调用顺序。
- 当输入/输出自然决定顺序时,优先采用自然顺序。
- 当顺序并非由自然流程决定时,使用显式顺序验证。
- 验证编排层级的异常包装和解包。
- 通过依赖项的形态及其产生的行为,间接测试规范化结果。
聚合服务测试规则
- 不要测试聚合服务中的依赖调用顺序。
- 不要对聚合服务使用 mock 序列式顺序断言。
- 仅测试基本结构验证和暴露层面的聚合行为。
- 聚合服务可能会多次调用或直接透传;应测试契约和暴露抽象,而不是编排逻辑。
控制器和协议测试规则
- 控制器需要针对映射逻辑编写单元测试。
- 对成功代码映射进行单元测试。
- 对验证/依赖/服务错误映射进行单元测试。
- 对安全性,即授权/身份验证失败映射进行单元测试。
- 对每个端点进行验收测试。
- 验收测试后清理测试数据。
- 运行验收测试时,模拟不属于该微服务的外部资源。
- 除单元测试和验收测试外,也可以进行集成测试和端到端测试。
UI 组件测试规则
基础组件
- 将基础组件视为轻量封装。
- 必要时测试其暴露的 API 和封装行为。
- 不要在基础组件中放入业务逻辑。
核心组件
- 核心组件采用测试驱动。
- 测试元素。
- 存在性
- 属性
- 操作
- 可以通过赋值属性、按 ID 搜索或常规搜索来测试存在性。
- 当样式属于组件契约的一部分时,测试样式。
- 测试会修改状态、创建组件或触发服务调用的操作。
- 核心组件应且只能与一个视图服务集成。
页面/容器
- 页面是更简单的路由容器。
- 通常不需要单元测试。
- 不应包含业务逻辑。
所提供实现配置文件中的单元测试约定
- 在测试中镜像分部类拆分方式。
- 在根测试文件中使用设置方法/辅助方法。
- 将测试拆分到逻辑、验证和异常文件中。
- 使用 GWT:Given / When / Then。
- 模拟所有依赖项。
- 使用易读的断言。
- 使用深度克隆来保护预期对象的标识。
- 默认使用随机数据。
- 验证确切的依赖调用。
- 以 VerifyNoOtherCalls 结束。
- 使用明确且以场景为导向的命名。
基础服务添加例程的精确测试实现顺序
在实现配置文件中实现 Add{Entity}Async 例程时,请遵循以下顺序:
- ShouldAdd{Entity}Async
- ShouldThrowValidationExceptionOnAddIf{Entity}IsNullAndLogItAsync
- ShouldThrowValidationExceptionOnAddIf{Entity}IsInvalidAndLogItAsync
- ShouldThrowDependencyValidationExceptionOnAddIfBadRequestErrorOccursAndLogItAsync
- ShouldThrowDependencyValidationExceptionOnAddIfConflictErrorOccursAndLogItAsync
- ShouldThrowCriticalDependencyExceptionOnAddIfUnauthorizedErrorOccursAndLogItAsync
- ShouldThrowCriticalDependencyExceptionOnAddIfForbiddenErrorOccursAndLogItAsync
- ShouldThrowCriticalDependencyExceptionOnAddIfNotFoundErrorOccursAndLogItAsync
- ShouldThrowCriticalDependencyExceptionOnAddIfUrlNotFoundErrorOccursAndLogItAsync
- ShouldThrowDependencyExceptionOnAddIfInternalServerErrorOccursAndLogItAsync
- ShouldThrowDependencyExceptionOnAddIfServiceUnavailableErrorOccursAndLogItAsync
- ShouldThrowCriticalDependencyExceptionOnAddIfHttpRequestErrorOccursAndLogItAsync
- ShouldThrowServiceExceptionOnAddIfServiceErrorOccursAndLogItAsync
对于基于存储的服务,请替换为相应的存储异常,例如重复键、DbUpdate 和 SQL 异常。
所提供实现配置文件中的测试和异常/本地化补充说明
8. 单元测试
单元测试遵循 The Standard 的分部类 + 三轴方法。
8.1 测试类结构
每个实体的测试都采用与服务相同的分部类拆分方式:
| 分部文件 | 测试 |
|---|---|
{Entity}ServiceTests.cs | 初始化、模拟对象、辅助方法(CreateRandom{Entity} 等) |
{Entity}ServiceTests.Logic.{Method}.cs | 正常路径/成功场景测试 |
{Entity}ServiceTests.Validations.{Method}.cs | 验证失败测试 |
{Entity}ServiceTests.Exceptions.{Method}.cs | 依赖项和服务异常测试 |
8.2 约定
| 约定 | 详情 |
|---|---|
| 模拟 | Moq — Mock<IStorageBroker>、Mock<IModernApiBroker>、Mock<ILoggingBroker> |
| 断言 | FluentAssertions — Should().BeEquivalentTo() |
| 深度克隆 | DeepCloner — 隔离输入/预期/实际对象 |
| 数据生成 | Tynamix.ObjectFiller — 使用自定义属性设置的 Filler<{Entity}> |
| 异常比较 | 通过 SameExceptionAs 表达式辅助方法调用 Xeption.SameExceptionAs() |
| 测试命名 | Should{Action}Async / ShouldThrow{Exception}On{Action}If{Condition}AndLogItAsync |
| 验证调用 | 每项测试都会验证 Broker 调用(Times.Once / Times.Never),并以 VerifyNoOtherCalls() 结束 |
| 测试框架 | xUnit — 单个用例使用 [Fact],参数化用例使用 [Theory] [InlineData] |
8.3 测试模式 — GWT(给定/当/则)
// given — build input, configure mocks, construct expected exception
// when — invoke the service method
// then — assert result / exception, verify broker interactions
8.4 测试实现顺序
测试必须按以下严格顺序编写并提交。此顺序可确保每个类别都在前一个类别的基础上构建:
- 正常路径 —
ShouldAdd{Entity}Async - 结构验证 — 检查实体是否为 null(
ShouldThrowValidationExceptionOnAddIf{Entity}IsNullAndLogItAsync) - 逻辑验证 — 使用
[Theory] [InlineData]进行属性级检查(ShouldThrowValidationExceptionOnAddIf{Entity}IsInvalidAndLogItAsync) - 外部依赖验证异常 —
BadRequest→Conflict - 外部关键依赖异常 —
Unauthorized→Forbidden→NotFound→UrlNotFound - 外部非关键依赖异常 —
InternalServerError→ServiceUnavailable - 传输层异常 —
HttpRequestException - 兜底服务异常 —
Exception
对于基于存储的服务,第 4–7 步替换为相应的 SQL/EF 异常
(DuplicateKeyException、DbUpdateException、SqlException)。
规则 — 提交前验证测试: 每个 FAIL 提交都必须确保测试实际运行并失败。每个 PASS 提交都必须确保所有测试运行并通过。切勿在未验证测试运行器报告了真实失败的情况下提交 FAIL。详情请参阅第 12.1.3 节 — 提交。
9. 关键库
| 包 | 用途 |
|---|---|
Xeption | 增强型异常,支持数据聚合 |
EFxceptions | EF Core 封装,用于抛出有意义的异常 |
RESTFulSense | 用于外部 API 代理的 HTTP 客户端封装 |
Moq | 单元测试模拟框架 |
FluentAssertions | 可读性更强的断言语法 |
DeepCloner | 基于值的测试对象深度克隆 |
Tynamix.ObjectFiller | 随机测试数据生成 |
xunit | 单元测试框架 |
异常处理原则
0. 范围
这些规则适用于所有服务层的异常设计、本地化、分类、传播和测试。
1. 本地化(强制)
- 外部(非本地)异常必须在边界处(Foundation)进行本地化。
- 原生异常(SQL、HTTP、SDK)不得跨越服务边界。
- 本地化必须将原生异常转换为特定于领域的异常。
数据保留(强制)
-
必须将外部异常中的所有相关数据复制到本地异常:
Data字典- 约束/验证元数据
- 标识符/键
-
本地化后的异常必须携带这些数据,以确保:
- 直接内部异常在分类后包含完整的验证详情。
2. 分类
-
所有异常必须归为以下类别之一:
- Validation
- DependencyValidation
- Dependency
- Service
-
分类决定上游处理方式和 exposer 映射。
3. 传播(解包/重新封装)
每个服务层必须:
- 捕获下游异常
- 解包分类异常
- 保留本地异常
- 重新封装为自身的分类异常
这可以防止泄露较低层的关注点,并强制遵守层级契约。
4. 保留内部异常(强制)
- 必须始终将原始本地异常保留为内部异常。
- 任何层都不得丢弃或替换本地异常。
这可以确保:
- 可追溯性
- 正确的 exposer 映射(例如 HTTP Conflict/FailedDependency)
- 保留验证数据
5. 层级职责
Foundation
- 本地化外部异常
- 填充本地异常数据
- 分类为 Validation / DependencyValidation / Dependency / Service
Processing
- 只能处理已分类的异常
- 不得依赖 Foundation 异常类型
- 必须重新封装为 Processing 层级的异常
Orchestration
- 必须处理来自所有依赖项的异常
- 必须将每种类型统一为单个分类异常
- 必须解包并保留内部异常
6. 全捕获(强制)
- 每个服务都必须实现:
catch (Exception)
- 必须映射为 ServiceException
这可以防止泄露未知故障。
7. 日志记录
- 每一层都必须在重新抛出前记录日志
- 只能记录分类后的异常
8. 测试
Orchestration 异常测试
- 应使用
[Theory] - 必须在单个测试中覆盖多种依赖异常类型
- 必须避免重复
9. 设计意图
这些规则旨在确保:
- 与外部系统完全隔离
- 稳定的层级契约
- 简化的 exposer 逻辑
- 在本地异常层级完整呈现验证信息
异常处理交叉引用(执行)
验证测试对齐
-
所有验证测试 MUST 遵循异常处理原则:
- MUST 验证本地化(不暴露原生异常)
- MUST 验证本地异常发生时的数据保留情况
- 验证异常 MUST 在内部异常中包含完整的错误详情
- 从处理服务层开始向上,其依赖项中的验证异常和依赖验证异常会重新包装为 [Entity][Layer]DependencyValidationExceptions
- 从处理服务层开始向上,其依赖项中的依赖异常和服务异常会重新包装为 [Entity][Layer]DependencyExceptions
-
验证测试 MUST 断言:
- 正确的本地异常类型
- 正确的分类异常类型
- 内部异常包含验证数据(Data 字典已填充)
基础异常测试(强制要求)
-
测试 MUST 验证本地化:
- 原生异常 → 本地异常 → 分类异常
-
测试 MUST 验证:
- 外部异常数据已复制到本地异常
- 本地异常作为内部异常保留
-
测试 MUST NOT 允许:
- 原生异常泄漏
- Data 字典传播缺失
处理异常测试(强制要求)
-
测试 MUST 断言:
- 重新包装为处理层异常
- 内部异常保留
-
测试 SHOULD:
- 使用
[Theory]在一个测试中测试同一类型的多个依赖验证异常 - 使用
[Theory]在一个测试中测试同一类型的多个依赖异常 - 在单个测试方法中测试多个用例,避免重复
- 使用
编排异常测试(强制要求)
-
测试 MUST 断言:
- 重新包装为编排层异常
- 内部异常保留(本地异常)
- 在编排层替换分类异常
-
测试 SHOULD:
- 使用
[Theory]在一个测试中测试同一类型的多个依赖验证异常 - 使用
[Theory]在一个测试中测试同一类型的多个依赖异常 - 在单个测试方法中测试多个用例,避免重复
- 使用
全捕获强制要求
- 测试 MUST 验证:
- 未知异常映射为 ServiceException
- 任何服务层都不会逸出原始 Exception
日志记录强制要求
- 测试 MUST 验证:
- 抛出异常前已进行日志记录
- 记录的异常是分类异常
设计完整性规则
- 任何违反异常处理原则的情况 MUST 视为:
- 设计缺陷
- 测试失败
- 评审阻塞项