mcpField
C
contract-testing-ts

contract-testing-ts

github.com/beecode-rs测试与质量244 次安装14 周前更新
代码仓库

关于 contract-testing-ts

contract-testing-ts 是为 TypeScript 项目配置和编写声明式契约测试的技能。它通过 Vitest 使用 `*.contract.yaml` 测试函数、类和对象,并指导验证测试结果及模拟依赖。

主要功能

  1. 01配置 Vitest 契约测试发现
  2. 02为导出项编写 YAML 契约
  3. 03覆盖输入和错误场景
  4. 04检查模拟依赖的测试项
  5. 05运行并验证契约测试

适用场景

  1. 01为软件包添加契约测试
  2. 02为函数或类创建契约
  3. 03测试边界情况和错误行为
  4. 04验证使用模拟依赖的契约

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/beecode-rs/msh-test-contractor --skill contract-testing-ts

安装后在 Claude Code 中输入/contract-testing-ts确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
contract-structure.md
examples.md
mocking.md
special-objects.md
term-types.md

详细介绍

Test Contractor — 基于契约的测试

你正在协助使用 test-contractor 库:这是一个基于契约的测试框架,可通过 *.contract.yaml 文件以声明方式定义测试用例。你将处理两种主要工作流:

  1. 项目设置 — 配置软件包,通过 Vitest 查找并运行 *.contract.yaml 文件
  2. 编写契约 — 为函数、类和对象创建并维护契约 YAML 文件

这两种工作流都需要通过运行契约测试来验证工作成果。

如何判断使用哪种工作流

留意用户的需求:

  • “设置契约测试”、“配置契约”、“添加 test:contract 脚本”、“我想在这个项目中使用契约测试” → 转到项目设置
  • “为……编写测试”、“编写……的契约”、“为这个文件添加契约”,或用户指出了源文件 → 转到编写契约
  • “为这些文件编写契约,并确保项目已完成设置” → 两者都做,并从设置开始
  • 如果需求不明确,请询问用户

提问

遇到不清楚的地方时,先询问再继续。以下是一些常见的情况:

  • 用户指出了一个文件,但未说明要测试哪些导出项 → 询问他们希望覆盖哪些函数或类
  • 源文件有复杂的依赖关系,且不清楚哪些依赖应该模拟 → 询问要隔离哪些依赖
  • 用户希望为某个类编写契约,但没有提到构造函数场景 → 询问他们是否也需要构造函数相关的条款
  • 你发现有多个可能的 module 路径(例如,可以从不同位置导入该文件)→ 确认正确的路径
  • 用户笼统地提到“测试” → 确认他们指的是 contract.yaml 还是 Vitest 单元测试

不要询问那些可以通过阅读代码确定的事项。只有在选择确实会影响输出时才提问。


项目设置

每个软件包只需设置一次。完成后,npm run test:contract 会自动查找并运行所有 *.contract.yaml 文件。

步骤 1:检查是否已完成设置

进行更改之前,检查项目是否已经有:

  • vitest.config.contract.ts 文件
  • package.json 中的 test:contract 脚本

如果两者都存在,说明项目已完成配置——跳转到编写契约,或询问用户需要什么帮助。

步骤 2:创建 vitest.config.contract.ts

将此文件放在项目根目录下(与 package.json 位于同一目录):

import { ContractReporter } from '@beecode/msh-test-contractor/contract-reporter'
import { contractYamlPlugin } from '@beecode/msh-test-contractor/vitest-plugin'
import tsconfigPaths from 'vite-tsconfig-paths'
import { coverageConfigDefaults, defineConfig } from 'vitest/config'

export default defineConfig({
	plugins: [tsconfigPaths(), contractYamlPlugin()],
	test: {
		coverage: {
			exclude: ['lib/**', 'src/index.ts', 'src/**/__fixtures__/**', ...coverageConfigDefaults.exclude],
		},
		exclude: ['src/**/__fixtures__/**'],
		include: ['src/**/*.contract.yaml'],
		mockReset: true,
		passWithNoTests: true,
		reporters: [new ContractReporter()],
		setupFiles: ['./src/__tests__/index-vitest-setup.ts'],
		watch: false,
	},
})

如果项目结构与常规布局不同,请相应调整路径。关键字段:

  • include — 用于查找 *.contract.yaml 文件的 glob 模式。如果契约文件位于其他位置,请调整此项。
  • setupFiles — 可选;用于设置测试环境的文件(例如,process.env.TZ = 'utc')。如果不需要,请移除。
  • ContractReporter — 格式化契约测试输出,方便快速查看。如果你更喜欢 Vitest 的默认格式,也可以使用 'verbose'。

如果项目尚未依赖 vite-tsconfig-paths,则需要安装:

npm install --save-dev vite-tsconfig-paths
步骤 3:在 package.json 中添加 test:contract 脚本
{
	"scripts": {
		"test:contract": "vitest --config=./vitest.config.contract.ts"
	}
}

如果已有一个运行多个测试套件的 test 脚本(例如,"test": "concurrently --group 'npm:test:*'"),那么该脚本会自动调用 test:contract。

步骤 4:验证设置
npm run test:contract

如果还没有契约文件,你会看到“no test files found”——这是预期结果。设置已完成。


编写契约

工作流概述
  1. 阅读用户希望测试的源文件
  2. 确认导出项及其签名
  3. 在源文件旁创建 *.contract.yaml 文件
  4. 检查模拟的依赖是否缺少条款(参见模拟依赖检查)
  5. 运行契约以验证它们能否通过
  6. 修复任何失败项并重新运行
何时阅读参考文件

根据需要按需阅读以下文件:

文件阅读时机
references/contract-structure.md始终阅读——核心结构、必填字段、主体类型
references/term-types.md编写包含基本类型、对象、数组或错误的参数/结果时
references/special-objects.md使用 !undefined、new Error(...)、new Date(...)、Promise.resolve(...)、__fn__、__class_ref:、__import: 时
references/mocking.md主体具有需要模拟的依赖项时
references/examples.md需要展示源代码与契约配对的完整端到端示例时

始终从阅读 references/contract-structure.md 开始——它定义了必填字段和约定。根据具体情况阅读其他文件。

契约文件放置位置

将 *.contract.yaml 文件放在与源文件相同的目录中。module 字段必须指向编译后的 .js 文件,而不是 .ts 源文件。

src/
  user-service.ts          ← source
  user-service.contract.yaml  ← contract (module: ./user-service.js)
项设计策略:零、一、多、大量、糟糕情况

为方法编写项时,按顺序考虑以下五种情况。每个方法以 3–7 个项为目标。

场景测试内容示例
零空输入/缺失输入。什么都没有时会怎样?[]、''、null、{}、0
一单个项目。最简单的非平凡情况。[42]、['alice']、{ id: 1 }
多少量项目。典型/理想路径情况。[1, 2, 3]、包含 3–5 个元素的数组
大量大型输入。行为会随规模变化吗?包含 1000 个项目的数组、非常长的字符串
糟糕情况出错情况。无效输入、边界情况、错误。负 ID、格式错误的对象、类型错误

并非每个方法都需要涵盖全部五种情况——不适用的场景可以跳过。阅读源代码后,对每个导出的方法问自己:

  1. 对此函数来说,“什么都没有”是什么样的?→ 零
  2. 最简单且有意义的输入是什么?→ 一
  3. 正常、典型的调用是什么样的?→ 多
  4. 函数的行为会随规模变化吗?→ 大量(不适用时跳过)
  5. 可能出现什么问题?→ 糟糕情况(错误、边界情况、临界值)

然后为每个适用的场景编写一个项。

模拟依赖检查

如果你编写的契约使用 mock 引用了另一个单元的契约,就需要验证被引用的契约是否覆盖了被测对象实际发起的调用。这一点很重要:如果被测对象使用的参数与模拟契约中的任何条款都不匹配,模拟就不知道该返回什么,测试可能会失败或表现异常。

检查流程:

  1. 编写契约后,阅读被测对象的源代码,找出所有对模拟依赖的调用
  2. 对于每个调用,检查模拟依赖的契约中是否有 params 与被测对象传入的参数匹配的条款
  3. 如果源代码中存在调用,但模拟契约中没有匹配的条款,则添加缺失的条款
  4. 更新后,重新运行模拟依赖自身的契约测试,确保新条款有效

**示例:**如果 orderService.createOrder({ item: 'book', price: 29.99 }) 内部调用了 logger.debug('Creating order: book'),那么 logger.contract.yaml 必须包含一条针对 debug 的条款,其 params: ['Creating order: book'](或能匹配该内容的模式)。如果缺少这条款,就添加它。

这项检查可以避免一种常见故障:契约单独运行时通过,但被另一个契约用作模拟时却失败。

验证契约

编写或更新契约后,务必运行:

npm run test:contract

如果某条款失败:

  • 仔细阅读错误消息,它会指出失败的条款以及实际结果与预期结果
  • 检查 params 是否与函数实际收到的参数匹配
  • 检查 result 是否与函数实际返回的结果匹配
  • 如果使用了模拟,确认模拟条款覆盖了被测对象发起的所有调用
  • 修复条款并重新运行

如果你更新了模拟依赖的契约,也要运行它的契约测试:

npx vitest --config=./vitest.config.contract.ts path/to/the-updated.contract.yaml
最简模板
subject: myFunction # exported name
module: ./my-module.js # path to compiled JS
subjectType: function # or "class"
methods:
  __self__: # for standalone functions; use method name for objects
    terms:
      - params: [input]
        result: expected
回退到 Vitest

Contract.yaml 涵盖了大多数测试场景。只有在无法通过声明式方式表达的情况下,才使用 Vitest *.test.ts:

  • 断言内部调用的顺序或次数(例如,“helper 恰好被调用两次”)
  • 无法静态设置的运行时设置或清理(例如,临时文件、网络服务器)
  • 非确定性状态(例如,无法通过契约模拟的时间戳)
  • 复杂的异步模式(延迟重试、竞态条件、流式处理)

遇到这类情况时,只针对该特定情况编写 Vitest 测试,其余部分仍放在 contract.yaml 中。在文件顶部添加注释:

// Supplements: ./my-module.contract.yaml
// Covers: [what's tested here and why contract.yaml couldn't handle it]

快速参考

  • 文件命名: *.contract.yaml(例如,user-service.contract.yaml)
  • subject: 导出名称,必须与其在模块中的名称完全一致
  • module: 已编译 .js 文件的相对路径(例如,./user-service.js)
  • __self__: 独立函数的方法名
  • constructor + constructorParams: 用于类被测对象
  • 每条条款: 输入 params,输出 result 或 error
  • 自动发现: Vite 插件会找到所有 *.contract.yaml 文件,无需入口 .test.ts
  • 运行: npm run test:contract(需要设置)
  • 条款策略: 零、一、多、很多、出错

评论

支持 Markdown