test-driven-development について
test-driven-development は、開発者やコーディングエージェント向けに、複数のプログラミング言語で使えるTDDの実践方法をまとめたスキルです。Red、Green、Refactorの段階に沿って、テスト構成、モック、カバレッジ、壊れやすいテストの回避策を説明します。
主な機能
- 01Red-Green-Refactorのワークフロー
- 02Arrange-Act-AssertとGiven-When-Thenのパターン
- 03テスト命名規則と言語別の例
- 04モックとテストダブルの指針
- 05カバレッジの実践とアンチパターン
活用シーン
- 01テストを先に書いて機能を開発
- 02失敗するテストでバグを再現してから修正
- 03動作を保っているか確認しながらリファクタリング
- 04API契約やビジネスロジックのテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill test-driven-developmentインストール後、Claude Code で/test-driven-developmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.jsonreferences/anti-patterns.mdexamples.mdintegration.mdphilosophy.mdworkflow.md詳細
テスト駆動開発(TDD)
あらゆるプログラミング言語に対応した、包括的なTDDのパターンと実践方法です。このスキルにより、エージェントごとに重複していたテスト関連のガイダンスを約500~800行削減できます。
使用する場面
次の場合にTDDを適用します。
- 新機能の実装
- バグ修正(まずバグを再現するテストを書きます)
- コードのリファクタリング(テストによって動作が維持されていることを確認します)
- APIの開発(契約をテストします)
- 複雑なビジネスロジック
TDDのワークフロー(レッド・グリーン・リファクタリング)
1. レッドフェーズ:失敗するテストを書く
Write a test that:
- Describes the desired behavior
- Fails for the right reason (not due to syntax errors)
- Is focused on a single behavior
2. グリーンフェーズ:テストを通す
Write the minimum code to:
- Pass the test
- Not introduce regressions
- Follow existing patterns
3. リファクタリングフェーズ:コードを改善する
While keeping tests green:
- Remove duplication
- Improve naming
- Simplify logic
- Extract functions/classes
テスト構成のパターン
Arrange-Act-Assert(AAA)
// Arrange: Set up test data and conditions
const user = createTestUser({ role: 'admin' });
// Act: Perform the action being tested
const result = await authenticateUser(user);
// Assert: Verify the outcome
expect(result.isAuthenticated).toBe(true);
expect(result.permissions).toContain('admin');
Given-When-Then(BDDスタイル)
Given: A user with admin privileges
When: They attempt to access protected resource
Then: Access is granted with appropriate permissions
テストの命名規則
パターン:test_should_<expected_behavior>_when_<condition>
例:
test_should_return_user_when_id_exists()test_should_raise_error_when_user_not_found()test_should_validate_email_format_when_creating_account()
言語ごとの規則
Python(pytest):
def test_should_calculate_total_when_items_added():
# Arrange
cart = ShoppingCart()
cart.add_item(Item("Book", 10.00))
cart.add_item(Item("Pen", 1.50))
# Act
total = cart.calculate_total()
# Assert
assert total == 11.50
JavaScript(Jest):
describe('ShoppingCart', () => {
test('should calculate total when items added', () => {
const cart = new ShoppingCart();
cart.addItem({ name: 'Book', price: 10.00 });
cart.addItem({ name: 'Pen', price: 1.50 });
const total = cart.calculateTotal();
expect(total).toBe(11.50);
});
});
Go:
func TestShouldCalculateTotalWhenItemsAdded(t *testing.T) {
// Arrange
cart := NewShoppingCart()
cart.AddItem(Item{Name: "Book", Price: 10.00})
cart.AddItem(Item{Name: "Pen", Price: 1.50})
// Act
total := cart.CalculateTotal()
// Assert
if total != 11.50 {
t.Errorf("Expected 11.50, got %f", total)
}
}
テストの種類と対象範囲
単体テスト
- 対象範囲: 単一の関数/メソッド
- 依存関係: モック化
- 速度: 高速(テスト1件あたり10ms未満)
- カバレッジ: コードパスの80%以上
統合テスト
- 対象範囲: 複数のコンポーネント
- 依存関係: 実物またはテストダブル
- 速度: 中程度(テスト1件あたり1秒未満)
- カバレッジ: 重要なパスとインターフェース
エンドツーエンドテスト
- 対象範囲: ユーザーのワークフロー全体
- 依存関係: 実物(テスト環境内)
- 速度: 低速(数秒から数分)
- カバレッジ: 中核となるユーザージャーニー
モックとテストダブル
モックを使う場面
- 外部APIやサービス
- データベース操作(単体テストの場合)
- ファイルシステム操作
- 時間に依存する操作
- 乱数の生成
モックの種類
スタブ: あらかじめ定義したデータを返します
def get_user_stub(user_id):
return User(id=user_id, name="Test User")
モック: やり取りを検証します
mock_service = Mock()
service.process_payment(payment_data)
mock_service.process_payment.assert_called_once_with(payment_data)
フェイク: 簡易版の動作する実装です
class FakeDatabase:
def __init__(self):
self.data = {}
def save(self, key, value):
self.data[key] = value
def get(self, key):
return self.data.get(key)
テストカバレッジのガイドライン
目標とするカバレッジレベル
- 重要なパス: 100%
- ビジネスロジック: 95%以上
- プロジェクト全体: 80%以上
- UIコンポーネント: 70%以上
テストする項目
- ✅ ビジネスロジックとアルゴリズム
- ✅ エッジケースと境界条件
- ✅ エラー処理とバリデーション
- ✅ 状態遷移
- ✅ 公開APIとインターフェース
テストしない項目
- ❌ フレームワークの内部実装
- ❌ サードパーティーライブラリ
- ❌ 単純なゲッター/セッター
- ❌ 生成されたコード
- ❌ 設定ファイル
テストのベストプラクティス
1. 可能な限り、テスト1件につきアサーションは1つにする
# Good: Focused test
def test_should_validate_email_format():
assert is_valid_email("[email protected]") is True
# Avoid: Multiple unrelated assertions
def test_validation():
assert is_valid_email("[email protected]") is True
assert is_valid_phone("123-456-7890") is True # Different concept
2. テストの独立性
# Good: Each test is self-contained
def test_user_creation():
user = create_user("[email protected]")
assert user.email == "[email protected]"
# Avoid: Tests depending on execution order
shared_user = None
def test_create_user():
global shared_user
shared_user = create_user("[email protected]")
def test_update_user(): # Depends on previous test
shared_user.name = "Updated"
3. 失敗理由がわかるテスト
# Good: Clear failure message
assert result.status == 200, f"Expected 200, got {result.status}: {result.body}"
# Avoid: Unclear failure
assert result.status == 200
4. テストデータビルダー
# Good: Reusable test data creation
def create_test_user(**overrides):
defaults = {
'email': '[email protected]',
'name': 'Test User',
'role': 'user'
}
return User(**{**defaults, **overrides})
# Usage
admin = create_test_user(role='admin')
guest = create_test_user(email='[email protected]')
避けるべきテストのアンチパターン
❌ 実装の詳細をテストする
# Bad: Tests internal structure
def test_user_storage():
user = User("[email protected]")
assert user._internal_cache is not None # Implementation detail
❌ 壊れやすいテスト
# Bad: Breaks with harmless changes
assert user.to_json() == '{"name":"John","email":"[email protected]"}'
# Good: Tests behavior, not format
data = json.loads(user.to_json())
assert data['name'] == "John"
assert data['email'] == "[email protected]"
❌ 単体テストスイート内の遅いテスト
# Bad: Real HTTP calls in unit tests
def test_api_integration():
response = requests.get("https://api.example.com/users") # Slow!
assert response.status_code == 200
❌ すべてをUI経由でテストする
# Bad: Testing business logic through UI
def test_calculation():
browser.click("#input1")
browser.type("5")
browser.click("#input2")
browser.type("3")
browser.click("#calculate")
assert browser.find("#result").text == "8"
# Good: Test logic directly
def test_calculation():
assert calculate(5, 3) == 8
言語別クイックリファレンス
Python(pytest)
# Setup/Teardown
@pytest.fixture
def database():
db = create_test_database()
yield db
db.cleanup()
# Parametrized tests
@pytest.mark.parametrize("input,expected", [
("[email protected]", True),
("invalid-email", False),
])
def test_email_validation(input, expected):
assert is_valid_email(input) == expected
JavaScript(Jest)
// Setup/Teardown
beforeEach(() => {
database = createTestDatabase();
});
afterEach(() => {
database.cleanup();
});
// Async tests
test('should fetch user data', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('John');
});
Go
// Table-driven tests
func TestEmailValidation(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"[email protected]", true},
{"invalid-email", false},
}
for _, tt := range tests {
result := IsValidEmail(tt.input)
if result != tt.expected {
t.Errorf("IsValidEmail(%s) = %v, want %v",
tt.input, result, tt.expected)
}
}
}
TDDで得られるメリット
- 設計の改善: テストによって、より優れたAPI設計が促されます
- ドキュメント: テストは実行可能なドキュメントとして機能します
- 安心感: 安全にリファクタリングできるようになります
- デバッグ: テストによって問題をすばやく切り分けられます
- カバレッジ: 包括的なテストカバレッジを確保できます
- リグレッションの防止: デプロイ前にバグを検出できます
関連スキル
テスト駆動開発を使用する際は、次のスキルによってワークフローを強化できます。
- systematic-debugging:テストが予期せず失敗した場合の、デバッグを優先する方法
- react:Reactコンポーネント、フック、コンテキストのテスト
- django:Djangoモデル、ビュー、フォームのテスト
- fastapi-local-dev:FastAPIエンドポイントと依存性注入のテスト
[これらのスキルがバンドルに含まれている場合、詳しいドキュメントを利用できます]
コメント
Markdown 対応