mcpField
G
grove-testing

grove-testing

github.com/autumnsgrove测试与质量667 次安装5 天前更新
代码仓库

关于 grove-testing

为 Grove 开发者提供测试策略指导,帮助判断该测什么以及如何评估测试价值。它侧重贴近用户行为的集成测试,并涵盖测试分层、边界条件和回归测试。

主要功能

  1. 01按用户行为规划测试
  2. 02优先编写集成测试
  3. 03评估测试有效性
  4. 04覆盖信任边界与错误情况
  5. 05将缺陷转化为回归测试

适用场景

  1. 01为新功能挑选测试范围
  2. 02审查现有测试的价值
  3. 03为缺陷修复补充测试

安装

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

$npx skills add https://github.com/autumnsgrove/lattice --skill grove-testing

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

详细介绍

Grove 测试技能

何时启用

在以下情况下启用此技能:

  • 决定要测试什么(而不只是如何测试)
  • 为 Grove 的新功能编写测试
  • 审查现有测试是否有效
  • 在没有具体指导的情况下被要求“添加测试”
  • 评估测试是否真正发挥了作用
  • 重构导致大量测试失败(这是测试质量差的表现)

对于技术实现(Vitest 语法、模拟模式、断言),请同时使用 javascript-testing 技能。


测试理念

“编写测试。不要太多。以集成测试为主。” — Guillermo Rauch

这概括了 Grove 对测试的全部看法:

编写测试——自动化测试很有价值。它们让你可以放心重构,充当文档,并在用户发现回归问题之前将其捕获。

不要太多——测试的收益会递减。目标不是覆盖率数字,而是信心。当你有信心发布时,测试就足够了。

以集成测试为主——集成测试能捕获真实问题,又不易脆弱。它们测试用户实际体验到的行为,而不是内部实现。

指导原则

“测试越接近软件的实际使用方式,就越能让你有信心。” — Kent C. Dodds(Testing Library)

问问自己:功能失效时,这个测试会失败吗? 如果会,它就有价值。如果它只会在重构时失败,那它测试的就是实现细节。


什么样的测试有价值

好的测试具有以下特性(Kent Beck 的 Test Desiderata):

特性含义
对行为敏感实际功能失效时会失败
不受结构影响安全重构时不会失败
确定性每次结果都相同,不会时好时坏
快速几秒内而不是几分钟内提供反馈
诊断清晰失败时,你能准确知道哪里出了问题
编写成本低投入与代码复杂度成正比
信心测试

编写测试前,先问:

  1. 如果它在生产环境中失效,我会注意到吗? 如果会,就测试它。
  2. 功能失效时,这个测试会失败吗? 如果不会,就不要编写。
  3. 这个测试是否接近用户与该功能的交互方式? 如果不是,请重新考虑。

不要测试什么

并非所有内容都需要测试。有些测试反而会损害代码库。

跳过测试
内容原因
简单代码getter、setter、没有逻辑的数据模型
框架行为相信 SvelteKit 的路由能够正常工作
实现细节内部状态、私有方法、CSS 类
一次性脚本维护成本超过其价值
不稳定的原型需求尚不明确,代码也会发生变化
少量测试
内容方法
配置做冒烟测试以确认能够加载,而非测试每个选项
第三方集成在边界处模拟,测试代码的响应
视觉设计使用快照测试或视觉回归测试,而非单元测试
充分测试
内容原因
业务逻辑应用的核心价值
面向用户的流程用户实际体验的内容
边界情况错误状态、空状态、边界条件
错误修复每个错误都应转化为测试,以防止回归

测试奖杯

现代 JavaScript 测试遵循测试奖杯,而不是旧的测试金字塔:

                    ╭─────────╮
                    │   E2E   │  ← Few: critical user journeys
                    ╰────┬────╯
               ╭─────────┴─────────╮
               │   Integration     │  ← Many: this is where confidence lives
               ╰─────────┬─────────╯
                  ╭──────┴──────╮
                  │    Unit     │  ← Some: pure functions, algorithms
                  ╰──────┬──────╯
              ╭──────────┴──────────╮
              │   Static Analysis   │  ← TypeScript, ESLint (always on)
              ╰─────────────────────╯
各层的作用

静态分析(TypeScript、ESLint)

  • 捕获拼写错误、类型错误和明显失误
  • 零运行时开销,始终运行
  • 这是你的第一道防线

单元测试

  • 纯函数、算法、工具函数
  • 速度快、隔离性强、易于调试
  • 不要什么都模拟——实际可行时,测试真实行为

集成测试(最佳着力点)

  • 多个单元协同工作
  • 测试用户实际体验的行为
  • 比单元测试更不易脆弱,比端到端测试更快
  • 大多数测试都应该属于这一类

端到端测试(Playwright)

  • 仅覆盖关键用户旅程:登录、结账、核心流程
  • 编写和维护成本高
  • 留给失败会影响业务的流程

编写有效的测试

结构:准备-执行-断言

每个测试都应遵循以下模式:

it("should reject invalid email during registration", async () => {
	// Arrange: Set up the scenario
	const invalidEmail = "not-an-email";

	// Act: Do the thing
	const result = await registerUser({ email: invalidEmail, password: "valid123" });

	// Assert: Check the outcome
	expect(result.success).toBe(false);
	expect(result.error).toContain("email");
});

执行部分应只有一行。如果不止一行,这个测试可能做得太多了。

命名:说清楚什么会出错

测试名称应描述行为,而不是实现:

好的名称:

  • should reject registration with invalid email
  • should show error message when API fails
  • should preserve draft when navigating away

不好的名称:

  • test email validation(具体是什么?)
  • handleSubmit works(“正常工作”是什么意思?)
  • test case 1(不行)
一次只测试一件事

每个测试都应当只有一个失败原因。测试失败时,你应该能立刻知道哪里出了问题。

// Bad: Testing multiple things
it('should handle registration', async () => {
    // Tests validation, API call, redirect, AND email sending
});

// Good: Focused tests
it('should reject invalid email format', ...);
it('should call API with valid data', ...);
it('should redirect after successful registration', ...);
it('should send welcome email after registration', ...);

测试信任边界(Rootwork)

每个信任边界都应针对有效和无效数据编写测试:

表单操作: 提交缺失字段、错误类型和边界值 → 验证 parseFormData() 返回结构化错误(而不是崩溃)

KV 读取: 模拟 KV 返回损坏或过时的 JSON → 验证 safeJsonParse() 会回退到默认值

缓存读取: 模拟缓存服务返回意外的数据形态 → 验证 createTypedCacheReader() 会使用回退值

Catch 块: 触发重定向和 HTTP 错误 → 验证 isRedirect()/isHttpError() 会正确处理它们

参考:Rootwork(@autumnsgrove/lattice/server)提供 parseFormData、safeJsonParse、createTypedCacheReader、isRedirect 和 isHttpError。


实践集成测试

集成测试是 Grove 测试策略的核心。下面介绍如何编写优质的集成测试。

测试用户行为,而非实现
// Bad: Testing implementation
it("should set isLoading state to true", async () => {
	const { component } = render(LoginForm);
	await fireEvent.click(getByRole("button"));
	expect(component.isLoading).toBe(true); // Testing internal state!
});

// Good: Testing user experience
it("should show loading indicator while logging in", async () => {
	render(LoginForm);
	await fireEvent.click(getByRole("button", { name: /sign in/i }));
	expect(getByRole("progressbar")).toBeInTheDocument();
});
使用无障碍查询

像用户查找元素那样查询元素:

// Priority order (best to worst):
getByRole("button", { name: /submit/i }); // How screen readers see it
getByLabelText("Email"); // Form fields
getByText("Welcome back"); // Visible text
getByTestId("login-form"); // Last resort
不要过度模拟

模拟会降低对集成的信心。请谨慎使用:

// Over-mocked: False confidence
vi.mock("./api");
vi.mock("./validation");
vi.mock("./utils");
// You're testing... nothing real

// Better: Mock at boundaries
vi.mock("./external-api"); // Mock the network, not your code
// Let validation, utils, etc. run for real

经验法则: 如果你在模拟自己编写的内容,不妨重新考虑。


测试失败时

测试失败是在告诉你一些事情。请认真对待。

良性失败(符合预期)
  • 功能发生变化 — 测试发现行为变了。更新测试。
  • 错误已修复 — 旧测试有误。修正它。
  • 需求发生变化 — 测试反映的是旧需求。更新测试。
不良失败(测试质量不佳的表现)
  • 重构了内部代码 — 测试与实现耦合。重写它。
  • 更改了 CSS 类 — 测试在查询实现细节。改用无障碍查询。
  • 调整了代码顺序 — 测试依赖执行顺序。让它不受顺序影响。

如果重构经常导致测试失败,说明你的测试测错了内容。


Bug → 测试流程

每个生产环境中的 Bug 都应该转化为一个测试:

  1. 报告 Bug — 用户无法使用某些商品结账
  2. 在本地复现 — 找出确切的条件
  3. 编写失败测试 — 捕获 Bug 出现的条件
  4. 修复 Bug — 测试现在可以通过
  5. 测试防止回归 — Bug 永远不会再出现

这是最有价值的测试实践之一。它能将痛苦转化为保护。


应避免的反模式

冰淇淋筒
        ╭───────────────────────────╮
        │      Many E2E tests       │  ← Slow, brittle, expensive
        ╰───────────┬───────────────╯
              ╭─────┴─────╮
              │ Few int.  │
              ╰─────┬─────╯
                ╭───┴───╮
                │ Few   │
                │ unit  │
                ╰───────╯

这完全本末倒置。端到端测试成本高昂。集成测试的投资回报率最高。

测试实现细节
// Testing implementation (bad)
expect(component.state.items).toHaveLength(3);
expect(handleClick).toHaveBeenCalledWith({ id: 1 });

// Testing behavior (good)
expect(getByRole("list").children).toHaveLength(3);
expect(getByText("Item added!")).toBeInTheDocument();
覆盖率表演

一味追求 100% 的覆盖率会导致糟糕的测试:

// Written only to hit coverage, provides zero value
it("should have properties", () => {
	const user = new User();
	expect(user.email).toBeDefined();
	expect(user.name).toBeDefined();
});

覆盖率是信号,而不是目标。糟糕测试带来的高覆盖率,比良好测试带来的中等覆盖率更糟。

滥用快照

快照适用于:

  • 复杂的序列化输出
  • 错误消息格式
  • API 响应结构

快照不适用于:

  • UI 组件(每次样式变更都会导致失败)
  • 带有时间戳或随机 ID 的内容
  • 大型对象(没人会审查 500 行的快照差异)

Grove 测试工作流

收到添加测试的请求时,请遵循此工作流:

1. 理解功能

此功能为用户做什么?不是如何实现,而是它能提供什么价值?

2. 找出关键路径

如果此功能失效,会有什么问题?这些就是你的测试用例。

3. 先编写集成测试

从能验证真实用户行为的测试开始。只有复杂逻辑才需要添加单元测试。

4. 让测试靠近代码
src/
└── lib/
    └── features/
        └── auth/
            ├── login.ts
            ├── login.test.ts      ← Right next to the code
            └── register.ts
5. 持续运行测试
npx vitest              # Watch mode during development
npx vitest run          # CI verification

快速决策指南

情况操作
新功能为面向用户的行为编写集成测试
修复 Bug先编写能复现 Bug 的测试,然后再修复
重构运行现有测试;如果安全的变更会导致测试失败,那这些测试就有问题
“需要更高覆盖率”为尚未覆盖的行为添加测试,而不是为尚未覆盖的代码行添加测试
纯函数/算法为其编写单元测试
API 端点使用模拟的外部服务进行集成测试
UI 组件使用 Testing Library 进行组件测试
关键用户流程使用 Playwright 进行端到端测试

与其他技能集成

javascript-testing

以下情况请使用 javascript-testing:

  • Vitest 配置语法
  • 模拟模式和 API
  • 断言参考
  • SvelteKit 专用测试模式
grove-documentation

编写测试描述时,请遵循 Grove 的风格:

  • 名称清晰、直接
  • 不使用术语
  • 描述用户会遇到什么
code-quality

编写测试前后都要运行代码检查和类型检查。静态分析能发现与测试不同的 Bug。


自查清单

在认为测试“完成”之前:

  • 测试描述的是用户行为,而不是实现方式
  • 每个测试都有一个明确的失败原因
  • 测试使用无障碍查询(getByRole、getByLabelText)
  • 仅在外部边界使用模拟
  • 测试名称说明了测试失败时会出什么问题
  • 不对易变内容进行快照测试
  • Bug 修复包含回归测试
  • 测试运行迅速(几秒,而不是几分钟)

好的测试能让你充满信心地发布。这就是全部意义所在。

评论

支持 Markdown