grove-testing について
Grove の開発者向けに、何をテストするか、テストの価値をどう判断するかを案内するスキルです。ユーザーの利用に近い統合テストを重視し、テスト階層や境界条件、回帰テストも扱います。
主な機能
- 01ユーザー行動に沿ったテスト設計
- 02統合テストの優先
- 03テストの有効性評価
- 04信頼境界とエラーの検証
- 05バグを回帰テストに反映
活用シーン
- 01新機能のテスト範囲の選定
- 02既存テストの価値の見直し
- 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)。
| 特性 | 意味 |
|---|---|
| 動作に敏感 | 実際の機能が壊れたときに失敗する |
| 構造に左右されない | 安全なリファクタリングでは壊れない |
| 決定論的 | 常に同じ結果になり、不安定でない |
| 高速 | 数分ではなく数秒でフィードバックが得られる |
| 明確な診断 | 失敗したときに、何が壊れたか正確にわかる |
| 低コストで書ける | コードの複雑さに見合った労力で書ける |
自信を確かめるテスト
テストを書く前に、次のことを確認してください。
- 本番環境でこれが壊れたら、気づくでしょうか? 気づくなら、テストしてください。
- 機能が壊れたとき、このテストは失敗するでしょうか? 失敗しないなら、書かないでください。
- このテストは、ユーザーが機能を使う方法に似ていますか? 似ていないなら、再検討してください。
テストしないもの
すべてにテストが必要なわけではありません。テストすると、かえってコードベースに悪影響を及ぼすものもあります。
テストを省略する
| 対象 | 理由 |
|---|---|
| 単純なコード | ゲッター、セッター、ロジックのないデータモデル |
| フレームワークの動作 | 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)
- タイポ、型エラー、明らかなミスを検出します
- 実行時のコストはゼロで、常に実行されます
- これが最初の防御線です
ユニットテスト
- 純粋関数、アルゴリズム、ユーティリティをテストします
- 高速で、分離されており、デバッグが容易です
- 何もかもモックせず、実用的な場合は実際の動作をテストします
統合テスト(最適な領域)
- 複数のユニットが連携して動作する様子をテストします
- ユーザーが実際に体験する動作をテストします
- ユニットテストより壊れにくく、E2E テストより高速です
- テストの大半はここに置くべきです
E2E テスト(Playwright)
- ログイン、購入手続き、中核となるフローなど、重要なユーザージャーニーだけをテストします
- 作成と保守にコストがかかります
- 失敗がビジネスに影響するフローに限って使用します
効果的なテストを書く
構成:Arrange-Act-Assert
すべてのテストで、次のパターンに従います。
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");
});
Act セクションは1行にします。そうでない場合、テストがやりすぎている可能性があります。
命名:何が壊れるかを示す
テスト名では、実装ではなく動作を説明します。
良い名前:
should reject registration with invalid emailshould show error message when API failsshould preserve draft when navigating away
悪い名前:
test email validation(何についてでしょうか?)handleSubmit works(「動作する」とはどういう意味でしょうか?)test case 1(情報がありません)
1つのことをテストする
各テストには、失敗する理由が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 クラスを変更した — テストが実装の詳細を照会していました。アクセシブルなクエリを使います。
- コードの順序を変更した — テストが実行順序に依存していました。順序に依存しないようにします。
リファクタリングで頻繁にテストが失敗するなら、テスト対象が間違っています。
バグからテストへのパイプライン
本番環境で発生したバグは、すべてテストにします。
- バグの報告 — 特定の商品を購入できない
- ローカルで再現 — 正確な条件を特定する
- 失敗するテストを作成 — バグが発生する条件を捉える
- バグを修正 — テストがパスするようになる
- テストで再発を防止 — バグが二度と発生しないようにする
これは、最も価値の高いテスト手法のひとつです。問題を防御に変えます。
避けるべきアンチパターン
アイスクリームコーン
╭───────────────────────────╮
│ Many E2E tests │ ← Slow, brittle, expensive
╰───────────┬───────────────╯
╭─────┴─────╮
│ Few int. │
╰─────┬─────╯
╭───┴───╮
│ Few │
│ unit │
╰───────╯
これは順序が逆です。E2E テストはコストが高く、統合テストが最も高い ROI をもたらします。
実装の詳細をテストする
// 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
すばやく判断するためのガイド
| 状況 | 対応 |
|---|---|
| 新機能 | ユーザー向けの動作を統合テストで検証する |
| バグ修正 | まずバグを再現するテストを書き、その後に修正する |
| リファクタリング | 既存のテストを実行する。安全な変更で失敗するなら、そのテストは不適切 |
| 「カバレッジを増やしたい」 | カバーされていない行ではなく、カバーされていない動作をテストする |
| 純粋関数/アルゴリズム | ユニットテストを行う |
| API エンドポイント | 外部サービスをモックした統合テストを行う |
| UI コンポーネント | Testing Library を使ってコンポーネントテストを行う |
| 重要なユーザーフロー | Playwright を使って E2E テストを行う |
他のスキルとの連携
javascript-testing
次の用途には javascript-testing を使用します。
- Vitest の設定構文
- モックのパターンと API
- アサーションのリファレンス
- SvelteKit 固有のテストパターン
grove-documentation
テストの説明を書く際は、Grove の文体に従います。
- 明確で簡潔な名前
- 専門用語を使わない
- ユーザーがどのような体験をするかを書く
code-quality
テストを書く前後に、リンティングと型チェックを実行します。静的解析は、テストとは異なるバグを検出します。
セルフレビューチェックリスト
テストを「完了」とする前に、次の点を確認します。
- テストが実装ではなく、ユーザーの動作を表している
- 各テストが失敗する理由が明確にひとつある
- アクセシブルなクエリ(getByRole、getByLabelText)を使用している
- モックを外部との境界に限定している
- テスト名から、失敗時に何が壊れるかがわかる
- 変化しやすい内容にスナップショットテストを使っていない
- バグ修正に回帰テストを含めている
- テストが速く実行される(数分ではなく数秒)
質の良いテストがあれば、自信を持ってリリースできます。それがすべてです。