mcpField
T
tdd-strict

tdd-strict

github.com/svenja-devテスト・品質3871 回インストール24 週間前に更新
リポジトリ

tdd-strict について

tdd-strictは、機能開発やバグ修正、リファクタリング、API変更でテストを先に書く開発スキルです。Red-Green-Refactorの手順と、テスト実践を確認するチェックリストを示します。

主な機能

  1. 01Red-Green-Refactorの手順
  2. 02コード変更前のテスト作成ルール
  3. 03コミット前の確認リスト
  4. 04一般的なテストフレームワークの指針
  5. 05TDD違反への対応手順

活用シーン

  1. 01機能実装前のテスト作成
  2. 02修正前にバグを再現
  3. 03動作を保ちながらのリファクタリング

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/svenja-dev/claude-code-skills --skill tdd-strict

インストール後、Claude Code で/tdd-strictを入力して読み込まれたか確認します。

詳細

厳格なテスト駆動開発

このスキルは、次の核となる原則に基づいて TDD の実践を徹底します。

「テストが失敗するのを確認していないなら、そのテストが正しいことを検証しているかどうかは分かりません。」

有効にするタイミング

  • 新しい機能を実装するとき
  • バグを修正するとき(まずバグを再現するテストを書き、その後に修正します)
  • リファクタリングするとき(変更の前後でテストが通る必要があります)
  • API を拡張するとき
  • エクスポートされる関数ごとに

Red-Green-Refactor サイクル

1. RED:失敗するテストを書く
// ZUERST: Test schreiben
describe('calculateOEE', () => {
  it('should return 0 when availability is 0', () => {
    const result = calculateOEE({ availability: 0, performance: 100, quality: 100 });
    expect(result).toBe(0);
  });
});

// Test MUSS fehlschlagen:
// Error: calculateOEE is not defined
// ODER
// Error: Expected 0 but received undefined

重要:テストは正しい理由で失敗しなければなりません。

  • 関数が存在しない
  • 関数が誤った結果を返す
  • テスト自体の構文エラーではない
2. GREEN:テストに合格する最小限のコードを書く
// DANACH: Minimaler Code
export function calculateOEE(params: OEEParams): number {
  if (params.availability === 0) return 0;
  // Weitere Logik kommt spaeter durch weitere Tests
  return 0;
}

ルール:テストに合格する最もシンプルなコードを書きます。

  • 最適化しない
  • 追加機能を加えない
  • 「明らかな」拡張を加えない
3. REFACTOR:新しい振る舞いを加えずに整理する
// Nach mehreren gruenen Tests: Refactoring erlaubt
export function calculateOEE({ availability, performance, quality }: OEEParams): number {
  return (availability * performance * quality) / 10000;
}

リファクタリングのルール:

  • 既存のテストはすべて合格し続けなければなりません
  • 新しい振る舞いを追加しないでください
  • コードの構造だけを改善してください
  • リファクタリングの各ステップ後にテストを実行してください

無効な言い訳 13 選

次の言い訳は決して許されません。

1. 「テストするには簡単すぎる」

現実:シンプルなコードにはシンプルなテストが必要です。テストは1行でも問題ありません。

it('should add two numbers', () => {
  expect(add(2, 3)).toBe(5);
});
2. 「後でテストします」

現実:「後で」とは「決してしない」という意味です。TDD ではテストが最初です。

3. 「すでに手動でテストしました」

現実:手動テストは再現性がなく、規模を拡大できません。

4. 「時間がないのでテストできません」

現実:テストはデバッグの時間を節約し、リグレッションを防ぎます。

5. 「プライベートメソッドはテストしなくてよい」

現実:パブリック API を通じて振る舞いをテストしてください。テストできない場合はリファクタリングしてください。

6. 「UI コードはテストできません」

現実:React Testing Library、Playwright、Storybook があるのは、まさにそのためです。

7. 「ロジックは単純です」

現実:単純なロジックも変更されます。テストは期待される振る舞いを記録します。

8. 「QA プロセスがあります」

現実:QA でバグを見つけるのは遅く、コストもかかります。TDD は最初からバグを防ぎます。

9. 「このコードは一時的なものです」

現実:一時的なコードは何年も使われることがよくあります。一時的なコードもテストで保護できます。

10. 「テストは開発を遅くします」

現実:TDD はデバッグとリグレッションを減らすことで、長期的には開発を速めます。

11. 「レガシーコードにはテストがありません」

現実:変更の前に特性テストを書いてください。段階的に改善します。

12. 「フレームワーク/ライブラリがすでにテストしています」

現実:フレームワーク自体ではなく、フレームワークの使い方をテストしてください。

13. 「モックの設定が大変すぎます」

現実:モックが複雑すぎるなら、設計が複雑すぎます。リファクタリングしてください。

検証チェックリスト

コミットする前に、以下を必ず満たしてください。

  • エクスポートされる関数ごとに、少なくとも1つのテストがある
  • すべてのテストが実装前に失敗している(RED を確認済み)
  • 各テストが1つのことだけを検証している(単一アサーションの原則)
  • テストごとに最小限のコードになっている(過剰設計がない)
  • エッジケースを網羅している:
    • Null/Undefined の入力
    • 空の配列/文字列
    • 境界値(0、MAX_INT、負の数)
    • 不正な入力(TypeError を想定)
  • テスト名が振る舞いを説明している:should [erwartetes Ergebnis] when [Bedingung]
  • コミットに skip または only のテストが含まれていない
  • 新しいコードのカバレッジが少なくとも80%ある

実践での TDD ワークフロー

ステップ1:テストファイルを作成する
# Fuer neue Funktion in src/utils/oee.ts
touch src/utils/oee.test.ts
ステップ2:最小限の失敗するテストを書く
// src/utils/oee.test.ts
import { describe, it, expect } from 'vitest';
import { calculateOEE } from './oee';

describe('calculateOEE', () => {
  it('should return 85 for sample manufacturing data', () => {
    const result = calculateOEE({
      availability: 90,
      performance: 95,
      quality: 99
    });
    expect(result).toBeCloseTo(84.645, 2);
  });
});
ステップ3:テストを実行する(必ず失敗すること)
npm run test -- --run src/utils/oee.test.ts

# Erwartete Ausgabe:
# Error: Cannot find module './oee'
# ODER nach Stub:
# Error: Expected 84.645 but received undefined
ステップ4:最小限の実装を行う
// src/utils/oee.ts
export interface OEEParams {
  availability: number;
  performance: number;
  quality: number;
}

export function calculateOEE(params: OEEParams): number {
  return (params.availability * params.performance * params.quality) / 10000;
}
ステップ5:テストを実行する(必ず合格すること)
npm run test -- --run src/utils/oee.test.ts

# Erwartete Ausgabe:
# PASS src/utils/oee.test.ts
ステップ6:エッジケース用の次のテストを書く
it('should throw when values exceed 100', () => {
  expect(() => calculateOEE({
    availability: 101,
    performance: 100,
    quality: 100
  })).toThrow('Values must be between 0 and 100');
});

ステップ3(RED)→ステップ4(GREEN)に戻り、繰り返します。

アンチパターンを見つける

禁止:コードの後にテストを書く
// FALSCH: Code zuerst geschrieben
function add(a: number, b: number): number {
  return a + b;
}

// Dann erst Test geschrieben - UNGUELTIG!
// Du weisst nicht ob der Test das richtige testet
禁止:一度に大量のコードを書く
// FALSCH: Komplette Klasse ohne Tests implementiert
class UserService {
  async create(user: User) { ... }
  async update(id: string, data: Partial<User>) { ... }
  async delete(id: string) { ... }
  async findById(id: string) { ... }
  async findAll(filters: FilterOptions) { ... }
}

// RICHTIG: Eine Methode nach der anderen mit TDD
禁止:テストに合格するようにテストを調整する
// FALSCH: Test geaendert weil Implementation anders ist
// Vorher: expect(result).toBe(100);
// Nachher: expect(result).toBe(99.5); // "Weil die Formel das so ausgibt"

// RICHTIG: Implementation korrigieren ODER Anforderungen klaeren

フレームワーク固有のパターン

React コンポーネント(Vitest + Testing Library)
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('LoginButton', () => {
  it('should show loading spinner when clicked', async () => {
    render(<LoginButton onLogin={vi.fn()} />);

    await userEvent.click(screen.getByRole('button', { name: /login/i }));

    expect(screen.getByRole('progressbar')).toBeInTheDocument();
  });
});
API エンドポイント(Supertest)
import request from 'supertest';
import { app } from '../app';

describe('POST /api/analyze', () => {
  it('should return 401 without authentication', async () => {
    const response = await request(app)
      .post('/api/analyze')
      .send({ data: 'test' });

    expect(response.status).toBe(401);
    expect(response.body.error).toBe('Unauthorized');
  });
});
非同期コード
describe('fetchUserData', () => {
  it('should retry 3 times on network failure', async () => {
    const mockFetch = vi.fn()
      .mockRejectedValueOnce(new Error('Network'))
      .mockRejectedValueOnce(new Error('Network'))
      .mockResolvedValueOnce({ id: 1, name: 'Test' });

    const result = await fetchUserData(1, { fetch: mockFetch });

    expect(mockFetch).toHaveBeenCalledTimes(3);
    expect(result.name).toBe('Test');
  });
});

TDD 違反があった場合

  1. 停止:テストなしでコードを生成し続けない
  2. 警告:「TDD 違反を検出しました:[説明]」
  3. ガイダンス:正しい TDD ワークフローを示す
  4. 質問:「まずテストを書きますか?」

成功を測定する指標

  • テスト対コード比率:最低 1:1(テストの LOC 対コードの LOC)
  • カバレッジ:新しいコードは最低 80%
  • レッド・グリーン時間:短いサイクル(機能ごとに 5~10 分)
  • テスト実行時間:単体テストは 10 秒未満

コマンド

# Einzelnen Test laufen lassen (RED pruefen)
npm run test -- --run path/to/file.test.ts

# Alle Tests (vor Commit)
npm run test

# Coverage Report
npm run test:coverage

# Watch Mode (waehrend Entwicklung)
npm run test -- --watch

他のスキルとの連携

  • code-quality-gate:TDD テストはゲート 1(コミット前)の一部です
  • strict-typescript-mode:テストも型安全である必要があります
  • supervisor:レビュアーエージェントが TDD の遵守状況を確認します

参考資料および関連文献

  • Kent Beck:「Test-Driven Development: By Example」
  • Robert C. Martin:「Clean Code」(第 9 章:ユニットテスト)
  • Martin Fowler:「Refactoring」(リファクタリング時のテストの安全性)

コメント