关于 testing
testing 是用于编写、组织和维护软件测试的技能,覆盖单元、集成、E2E、无障碍和性能测试。它强调按改动选择适量测试,以捕获回归而非追求覆盖率数字。
主要功能
- 01覆盖多层测试的指导
- 02按需选择适量测试
- 03结合现有框架的测试模式
- 04诊断不稳定测试
- 05夹具、模拟与测试数据指导
适用场景
- 01为缺陷修复添加回归测试
- 02为功能改动选择测试
- 03诊断不稳定或失败的测试
- 04规划测试基础设施和覆盖率门槛
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iuliandita/ skills --skill testing安装后在 Claude Code 中输入/testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/agent-hygiene.mde2e-accessibility.mdlanguage-patterns.mdoutput-contract.md详细介绍
测试:编写能捕获真实错误的测试
在单元、集成、端到端(E2E)、无障碍和性能测试各层编写、组织和维护测试。目标是让测试捕获回归问题、记录行为并在 CI 中快速运行,而不是为了抬高覆盖率数字而写测试。
目标版本(2026 年 10 月):
- Vitest 5.0.5,Jest 30.5.2
- Playwright 1.64.1,Cypress 16.1.1(Vitest 和 Cypress 均为重大版本升级;请查看迁移说明)
- pytest 9.2.0,pytest-cov 7.1.0
- Go 1.27.3(testing 标准库,
testing/synctest正式发布) - Rust 1.99.0(
cargo test,cargo-nextest 0.9.150) - Testing Library 16.3.3(
@testing-library/react) - axe-core 4.14.0(
@axe-core/playwright) - Grafana k6 2.3.0
安全检查(2026-10-28):GHSA-2h32-95rg-cppp
是严重的浏览器运行器脚本注入漏洞,可能泄露 API 令牌并导致本地代码
执行。它影响 @vitest/browser >=4.0.17,<4.1.6 和 >=5.0.0-beta.0,<5.0.0-beta.3;
修复版本为 4.1.6 和 5.0.0-beta.3。请让浏览器包与稳定版运行器保持一致,并且在服务运行时不要
打开不可信的运行器 URL。后续严重的浏览器模式安全公告
(GHSA-g8mr-85jm-7xhm、GHSA-p63j-vcc4-9vmv)
将 4.x 的最低版本提高至 4.1.10,将 3.x 的最低版本提高至 3.2.7。10 月 21 日的安全公告再次提高最低版本:
GHSA-p458-7748-mcp3(server.fs.deny 绕过
可暴露 .env、密钥和 API 令牌;@vitest/ui/@vitest/browser <5.0.5)以及
GHSA-qxg6-89mg-rqv8(模拟注册代码
注入;@vitest/mocker <4.1.12 和 5.0.x <5.0.3)。最低版本为 5.0.5 和 4.1.12;3.x 没有
修复版本,因此请升级。
适用场景
- 编写新测试(单元、集成、E2E、无障碍、性能)
- 调试不稳定或失败的测试
- 为项目设计测试架构(fixture 策略、工厂模式、测试数据)
- 在 CI 中搭建测试基础设施(并行化、分片、覆盖率门槛)
- 选择测试工具或在测试框架之间迁移
- 选择能保护某项更改的最小测试集
- 为现有测试套件添加无障碍或视觉回归测试
不适用场景
- 对已确定的应用瓶颈进行性能分析和优化——请使用 performance-debugging。
- Broker 确认、重新投递、死信或重放语义——请使用 message-queues。
- 在代码审查中检查现有测试的质量或正确性——请使用 code-review。
- 专门的安全测试(渗透测试、OWASP 检查)——请使用 security-audit。
- 清理冗长或草率的测试代码——请使用 code-simplification。
- 在测试之外临时浏览网页、抓取内容或与页面交互——请使用宿主提供的浏览工具。
- CI/CD 流水线架构(测试任务会在流水线中运行,但流水线设计属于 ci-cd 的范畴)——请使用 ci-cd。
- 引擎层面的数据库测试模式——请使用 databases。
- 编写或改进大模型提示词——请使用 prompt-generator。
- 测试之外的基础设施或配置验证——请使用 terraform、ansible 或 kubernetes。
- AI/ML 模型评估或大模型输出评分——请使用 llm-app-development。
- 超出应用测试范围的基础设施级负载或混沌测试——集群级混沌测试请使用 kubernetes,流水线集成的负载测试编排请使用 ci-cd。
比例原则
根据改动规模安排测试套件。不要因为想测试每个函数或追求数量而违背这一原则。
- 每种行为写一个测试,再覆盖现实中可能出现的失败模式(错误输入、边界情况、错误路径)。同一种行为的不同输入应放在一个参数化测试中。
- 跳过针对胶水代码、接线配置、配置项、getter,以及框架或类型系统已保证的行为的测试。
- 新增测试超过约 5 个,或测试代码比被测代码还长时,请说明每个测试为何值得保留,否则就删减。
- 修复 Bug 时:编写一个修复前会失败的回归测试即可,不要写一整套测试。
- 优先选择数量最少、但在行为出错时仍能失败的测试。
AI 自检
AI 工具总是犯同样的测试错误。返回任何生成的测试代码前,请对照以下清单进行检查:
- 测试断言的是行为,而非实现;不要测试私有方法或内部状态
- 每个测试只有一个失败原因(一个断言概念,而非只调用一次
assert);同一种行为的不同输入应放在一个参数化测试中,而不是拆成多个测试 - 测试数量与改动规模相称(参见“比例原则”);没有近似重复的测试,也没有针对胶水代码或框架行为的测试
- 测试名称描述场景和预期结果,而非方法名称
- Mock/Stub 的作用域限于单个测试;不同测试之间不共享可变的 Mock 状态
- 没有会导致其他机器或 CI 出错的硬编码端口、路径或时间戳
- 异步测试会正确等待所有 Promise/Future;不会使用未等待的断言
- 测试数据彼此隔离;每个测试都会创建自己的状态,不依赖测试执行顺序
- 即使断言失败也会执行清理(使用
afterEach/teardown/t.Cleanup/Drop) - 等待异步操作时不使用
sleep()或固定延迟;改用轮询、重试或基于事件的等待 - 覆盖率阈值切合实际(行覆盖率 80% 是不错的默认值;100% 并不现实),且没有为了覆盖率而添加测试
- 提交前已手动检查快照测试(盲目使用
--update容易引发 Bug) - E2E 选择器优先使用角色或可访问名称,
data-testid仅作最后手段;不要使用 CSS 类或 DOM 结构 - 运行器 API 保持最新:pytest、Vitest、Jest、Playwright 和 Testing Library 示例符合当前运行器行为
- 已找出不稳定来源:不会在未诊断原因的情况下用重试掩盖非确定性
- 已应用跨领域 Agent 规范;参见
references/agent-hygiene.md
性能
- 将快速单元测试与集成测试、浏览器测试和性能测试套件分开。
- 使用 fixture 和测试数据构建器,避免重复进行耗时的初始化。
- 只有在隔离共享状态、端口、数据库和时钟后,才进行分片或并行运行。
工作流
复制此清单并跟踪进度:
- 步骤 1:确定范围并识别现有框架
- 步骤 2:选择测试层级
- 步骤 3:编写测试(修复 Bug 时,确认修复前测试会失败)
- 步骤 4:受影响的检查均已通过;并通过步骤 3 修复了并非由被测行为导致的失败
步骤 1:确定范围
根据上下文:
- 新功能 -> 测试重要行为,可与代码同时编写,也可在代码之后编写;不要测试每个函数
- 修复 Bug -> 先编写一个会失败的测试来复现 Bug,再进行修复
- 现有未测试代码 -> 优先覆盖关键路径,不要追求 100% 覆盖率
- 测试基础设施 -> 配置运行器、CI 配置和覆盖率门槛
从配置文件(vitest.config.ts、jest.config.*、pyproject.toml、Cargo.toml、*_test.go、playwright.config.ts)中识别项目现有的测试框架,并与其保持一致。没有理由时,不要引入第二个测试运行器。
步骤 2:选择测试层级
| 层级 | 测试内容 | 速度 | 适用场景 |
|---|---|---|---|
| 单元测试 | 隔离测试单个函数/模块 | 毫秒 | 纯逻辑、实用工具、数据转换、状态机 |
| 集成测试 | 测试多个模块及真实依赖 | 秒 | API 处理程序、数据库查询、服务边界 |
| 端到端测试 | 通过 UI 测试完整的用户流程 | 秒至分钟 | 关键路径、结账流程、身份验证、引导流程 |
| 无障碍测试 | WCAG 合规性、屏幕阅读器兼容性 | 秒 | 此次变更涉及的面向用户的组件/页面 |
| 视觉测试 | 截图对比 | 秒 | 样式变更后的 UI 组件 |
| 性能测试 | 负载、延迟、吞吐量 | 分钟 | 发布前、架构变更后 |
测试金字塔关注的是测试所处的位置,而不是配额。 将每项检查放在成本最低、但仍能发现故障的层级;不要为了填满各层而编写测试。颠倒这一原则(用耗时的端到端测试检查单元测试就能覆盖的逻辑),CI 就会耗时 45 分钟,而且大家都会忽略失败。优先选择数量最少、但行为出错时会失败的测试。
步骤 3:编写测试
遵循以下针对不同语言的模式。通用原则:
准备-执行-断言(或前提-执行-结果):
// Arrange: set up test data and dependencies
// Act: call the thing being tested
// Assert: verify the outcome
测试命名:描述场景,而不是函数。
# Bad: test_calculate_total
# Good: test_calculate_total_applies_discount_when_cart_exceeds_100
# Good: it("returns 401 when token is expired")
步骤 4:验证
- 运行与变更行为相符的检查,以及仓库要求的所有门禁。按需或在受影响范围需要时运行完整测试套件;不要为可逆且影响较小的编辑添加测试。只有出现新变更、失败或未解决的问题时,才重复或扩大通过的检查。
- 如果新测试失败的原因与待测行为无关,请修复测试或测试固件,然后返回步骤 3
- 将覆盖率变化作为发现遗漏行为的信号,而不是目标:新增代码不意味着需要追求某个数字,覆盖率也绝不是添加测试的理由
- 尽可能在 CI 中运行——本地通过却在 CI 中失败的测试最糟糕
TDD 工作流
可选。对于行为预先定义明确的缺陷修复和非简单逻辑,可采用红-绿流程。这并不意味着没有测试就不能编写代码,也不意味着每个函数都要有测试。在探索或原型开发时可跳过。
- 红:编写一个会失败的测试(确认失败原因正确)
- 绿:编写能让测试通过的最少代码(代码不够漂亮也没关系)
- 重构:在不改变行为的前提下清理代码(测试仍然通过)
TDD 最适用于:纯函数、数据转换、状态机、API 契约、缺陷复现。
TDD 不适用于:UI 布局、探索性原型开发、与未文档化 API 的集成。
模拟策略
在边界处进行模拟,而不是无处不模拟。过度模拟会让测试通过,但实际代码却已损坏。
| 模拟对象 | 不要模拟的对象 |
|---|---|
| 外部 API(HTTP、gRPC) | 你自己的纯函数 |
| 数据库(单元测试时) | 数据转换 |
| 时间/日期、随机值 | 简单的实用代码 |
| 文件系统(不便使用时) | 被测模块 |
| 第三方 SDK | 标准库函数 |
尽可能优先使用伪实现,而不是模拟对象。 内存数据库实现比返回预设响应的模拟对象能测试更多真实行为。
用于 TTL/时间依赖测试的可注入时钟 - 传入时钟依赖,而不是直接调用 Date.now() 或 time.Now():
// Production: clock = () => Date.now()
// Test: clock = () => FIXED_TS + offset
function isExpired(createdAt: number, ttlMs: number, clock = Date.now): boolean {
return clock() - createdAt > ttlMs;
}
// In test: advance virtual time without sleeping
const fakeNow = vi.fn().mockReturnValue(START);
expect(isExpired(START, 1000, fakeNow)).toBe(false);
fakeNow.mockReturnValue(START + 1001);
expect(isExpired(START, 1000, fakeNow)).toBe(true);
对于带缓存的请求,覆盖可观察到的路径(设置相同的参数化测试或顺序测试即可):
- 缓存未命中:HTTP 边界调用一次,并缓存返回值。
- 缓存命中:返回缓存值,且不调用 HTTP 边界。
- TTL 过期:推进注入的时钟或伪时钟,然后断言发生一次刷新,而不是通过休眠等待。
阅读 references/language-patterns.md,了解特定语言的模拟惯用法(Vitest vi.mock、Jest jest.mock、pytest monkeypatch、Go 接口、Rust trait 对象)。
测试数据与 Fixture
工厂模式(首选)
使用合理的默认值和每个测试的单独覆盖项来构建测试数据:
// TypeScript - factory function
function buildUser(overrides: Partial<User> = {}): User {
return { id: randomUUID(), name: "Test User", email: "[email protected]", ...overrides };
}
// Python - factory function
def build_user(**overrides) -> User:
defaults = {"id": uuid4(), "name": "Test User", "email": "[email protected]"}
return User(**(defaults | overrides))
Fixture 规则
- 每个测试彼此隔离。 共享的可变 Fixture 会导致依赖执行顺序的失败。
- 优先使用构建器/工厂,而不是原始对象字面量 - 默认值可避免测试脆弱。
- 数据库 Fixture:使用在每个测试后回滚的事务(pytest
dbFixture、JestbeforeEach中执行回滚)。使用预置的测试数据库优于共享的预发布环境数据。 - 文件 Fixture:使用临时目录(pytest 中的
tmp_path、Go 中的os.MkdirTemp、Rust 中的tempfile)。在拆卸阶段清理。
无障碍测试
自动检测 WCAG 违规。这不能替代手动测试,但能捕获机械性问题(缺少替代文本、ARIA 错误、对比度、键盘陷阱)。
使用 @axe-core/playwright(如果缺少,可使用仓库的包管理器将其添加为开发依赖:npm install -D、pnpm add -D、yarn add -D 或 bun add -d @axe-core/playwright)- 运行 new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]).analyze() 并断言违规数为零。将 axe 扫描范围限定在此次更改涉及的页面或组件,而不是每次更改都扫描整个应用。使用 .exclude() 排除已知问题,并将其作为技术债务跟踪,而不是永久豁免。
在此处维护可重复的无障碍回归检查;使用 frontend-design 修复交互设计和语义。将手动键盘和屏幕阅读器覆盖情况与自动扫描结果分开记录。
阅读 references/e2e-accessibility.md,了解 Playwright E2E 模式、视觉回归设置和 CI 无障碍门禁。
性能测试
分为两类:微基准测试(此函数是否足够快?)和负载测试(系统能否处理流量?)。
微基准测试
- Go:
func BenchmarkX(b *testing.B)- 内置于标准库 - Rust:使用 criterion 执行
cargo bench(criterion = "0.8") - JS/TS:
vitest bench(在 Vitest 外直接使用tinybench) - Python:
pytest-benchmark(一次性测量可用timeit)
负载测试(k6)
先检测:command -v k6 >/dev/null || echo "k6 missing"。
// k6 load test
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "30s", target: 50 }, // ramp up
{ duration: "1m", target: 50 }, // sustain
{ duration: "10s", target: 0 }, // ramp down
],
thresholds: {
checks: ["rate==1"], // fail the run when any status check fails
http_req_duration: ["p(95)<500"], // 95th percentile under 500ms
},
};
export default function () {
const res = http.get("http://localhost:3000/api/health");
check(res, { "status 200": (r) => r.status === 200 });
sleep(1);
}
未经明确批准,不要对生产环境运行负载测试。除非有专用基础设施,否则不要在 CI 中运行负载测试。
CI 集成
测试并行化
- Vitest/Jest:内置工作进程并行机制。Vitest 使用 Vite 的模块图来智能分配测试文件。
- Playwright:使用
--shard=1/4将测试拆分到多个 CI 运行器上。使用--workers=4在单个运行器内并行执行。 - pytest:使用
pytest-xdist和-n auto,根据 CPU 核心数并行执行。 - Go:使用
go test -parallel N并行执行包内测试,使用-p N并行执行多个包。 - Rust:使用
cargo nextest run实现每个测试进程隔离和并行执行(cargo nextest --version >/dev/null 2>&1 || echo "cargo-nextest missing: cargo install cargo-nextest")。
不稳定测试管理
不稳定测试会削弱信任。应立即修复或隔离。
- 识别:持续跟踪测试稳定性(大多数 CI 系统都提供不稳定测试看板)
- 隔离:将测试移到不会阻止合并的独立任务中。添加
@flaky或skip标签。 - 修复根本原因——各框架中的常见诱因:
- Playwright/Cypress:导航或动画中的竞态条件。使用
waitForLoadState、waitForSelector或 Playwright 的自动等待机制。避免使用page.waitForTimeout。模拟网络 请求以消除后端波动。为每项测试创建全新的浏览器上下文,避免 Cookie、 存储和 Service Worker 在测试用例之间泄漏。无头模式(CI)与有头模式的渲染时序不同。 首先使用与 CI 相同的无头浏览器、操作系统映像、视口、 字体和工作进程数复现问题;只有在排查渲染差异时才对比--headed。调整超时前,先检查 CI 运行器上的 CPU、内存和工作进程竞争情况。 - Vitest/Jest:测试文件之间共享模块状态。使用
--pool forks(Vitest)或--runInBand(Jest)进行隔离。检查是否有计时器泄漏(未恢复vi.useFakeTimers)。 - pytest:数据库状态在测试之间泄漏。使用
@pytest.mark.usefixtures("db")并通过事务回滚。检查 fixture 中是否修改了全局状态。 - Go:使用
t.Parallel()的测试共享包级状态。使用t.Cleanup执行清理。 使用goleak检查是否有 goroutine 泄漏。
- Playwright/Cypress:导航或动画中的竞态条件。使用
- 谨慎重试:
--retries 2(Playwright)或--reruns 2(pytest-rerunfailures)只是临时补救,并非修复
覆盖率阈值
对于已设置覆盖率门槛的项目,将以下数值作为 CI 门禁参考;否则,不要为小改动引入门槛。合理的默认值:
| 指标 | 阈值 | 原因 |
|---|---|---|
| 行覆盖率 | 80% | 能发现明显的覆盖缺口 |
| 分支覆盖率 | 70% | 能发现未测试的条件 |
阈值是 CI 门禁,不是目标:绝不要仅仅为了提高数字而添加测试。可通过 vitest --coverage --coverage.thresholds.lines=80、pytest --cov --cov-fail-under=80,或 go test -coverprofile 加阈值脚本来强制执行。
最简 CI 示例(pytest + GitHub Actions):
- run: pip install pytest pytest-xdist pytest-cov
- run: pytest -n auto --cov=src --cov-fail-under=80 --tb=short
参考文件
references/language-patterns.md- JS/TS(Vitest、Jest)、Python(pytest)、Go(testing 标准库)和 Rust(cargo test)的语言专用测试模式。涵盖模拟、表驱动测试、异步测试、快照测试和框架专用惯用法。references/e2e-accessibility.md- 使用 Playwright 进行 E2E 测试、视觉回归(截图对比、组件快照)、无障碍测试模式,以及浏览器测试的 CI 集成。
输出约定
完整约定请参阅 references/output-contract.md。
- 技能名称: TESTING
- 交付物类别:
audits - 模式: 有条件。当调用以分析、审查、审计或改进现有仓库内容时,遵循
references/output-contract.md中的报告篇幅和证据规则,并将交付物写入docs/local/audits/testing/<YYYY-MM-DD>-<slug>.md。当调用以回答问题、讲解概念、构建新成果或生成内容时,可自由回复,不受该约定限制。 - 严重程度级别:
P0 | P1 | P2 | P3 | info(参见共享约定;仅用于审计/审查模式)。
相关技能
- code-review — 在代码审查过程中评估测试的质量和正确性。此技能负责编写测试;code-review 负责评估测试是否真正覆盖了正确的内容。
- security-audit — 负责安全专项测试(OWASP、渗透测试、凭据扫描)。此技能负责功能测试。
- code-simplification — 审查冗长、过度抽象或由 AI 生成的测试代码,找出安全的精简方式。如果测试有效,但读起来像篇小说,请转交给 code-simplification。
- ci-cd — 设计运行测试的流水线。此技能负责编写测试和配置测试运行器;ci-cd 负责测试流水线的整体结构。
- databases — 涵盖数据库引擎测试和配置。此技能负责应用层面的数据库测试模式(事务、固定装置、测试数据)。
- llm-app-development — 负责 AI/ML 模型评估、大模型输出评分和基准测试框架。此技能负责应用功能测试;llm-app-development 负责模型层面的评估。
- kubernetes — 负责集群级混沌测试、韧性测试和基础设施层负载测试。此技能负责应用测试代码;kubernetes 负责集群级故障注入。
规则
AI 自检负责每项测试的防护措施(关注行为而非实现、不使用 sleep()、状态隔离、诊断不稳定测试、审查快照)。此外还需遵循:
- 不要测试框架本身。 测试 React 是否渲染了一个 div,或 Express 是否将请求路由到某个处理程序,都是在测试他人的代码。要测试你自己的逻辑。
- 避免测试膨胀。 不要针对同一行为编写近似重复的测试、复述实现的测试或过度模拟的测试。请参阅“适度原则”。