tdd-strict について
tdd-strictは、機能開発やバグ修正、リファクタリング、API変更でテストを先に書く開発スキルです。Red-Green-Refactorの手順と、テスト実践を確認するチェックリストを示します。
主な機能
- 01Red-Green-Refactorの手順
- 02コード変更前のテスト作成ルール
- 03コミット前の確認リスト
- 04一般的なテストフレームワークの指針
- 05TDD違反への対応手順
活用シーン
- 01機能実装前のテスト作成
- 02修正前にバグを再現
- 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 違反があった場合
- 停止:テストなしでコードを生成し続けない
- 警告:「TDD 違反を検出しました:[説明]」
- ガイダンス:正しい TDD ワークフローを示す
- 質問:「まずテストを書きますか?」
成功を測定する指標
- テスト対コード比率:最低 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」(リファクタリング時のテストの安全性)