mcpField
T
tdd

tdd

github.com/prowler-cloudテスト・品質15.1k58 回インストール今日更新
リポジトリ

tdd について

tdd は Prowler の UI、SDK、API 開発向けのテスト駆動開発スキルです。RED、GREEN、REFACTOR の手順に沿い、各スタックのテスト方法を示します。

主な機能

  1. 01RED、GREEN、REFACTOR のサイクル
  2. 02既存テストとカバレッジの確認
  3. 03新機能とバグ修正のテスト
  4. 04実ロジックを検証するテストの追加
  5. 05UI、SDK、API のテストコマンド

活用シーン

  1. 01UI 機能に Vitest テストを追加
  2. 02SDK の不具合を再現して修正
  3. 03API のリファクタリング前に基準を確立

インストール

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

$npx skills add https://github.com/prowler-cloud/prowler --skill tdd

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

詳細

TDD サイクル(必須)

+-----------------------------------------+
|  RED -> GREEN -> REFACTOR               |
|     ^                        |          |
|     +------------------------+          |
+-----------------------------------------+

問題は「テストを書くべきか?」ではなく、「どのテストが必要か?」です。


TDD の 3 つの法則

  1. 失敗するテストを書くまでは、本番コードを書かない
  2. 失敗させるために必要な範囲を超えて、テストを増やさない
  3. テストを成功させるために必要な範囲を超えて、コードを書かない

使用するスタックを特定する

始める前に、どのコンポーネントで作業するかを確認します。

作業場所スタックランナーテストパターン詳細
ui/TypeScript / ReactVitest + RTL*.test.{ts,tsx}(同じディレクトリに配置)vitest スキルを参照
prowler/Pythonpytest + mototests/ 内の *_test.py(サフィックス形式)prowler-test-sdk スキルを参照
api/Python / Djangopytest + djangoapi/src/backend/**/tests/ 内の test_*.py(プレフィックス形式)prowler-test-api スキルを参照

フェーズ 0:評価(必ず最初に実施)

コードを書く前に、必ず以下を行います。

UI(ui/)
# 1. Find existing tests
fd "*.test.tsx" ui/components/feature/

# 2. Check coverage
pnpm test:coverage -- components/feature/

# 3. Read existing tests
SDK(prowler/)
# 1. Find existing tests
fd "*_test.py" tests/providers/aws/services/ec2/

# 2. Run specific test
uv run pytest tests/providers/aws/services/ec2/ec2_ami_public/ -v

# 3. Read existing tests
API(api/)
# 1. Find existing tests
fd "test_*.py" api/src/backend/api/tests/

# 2. Run specific test
uv run pytest api/src/backend/api/tests/test_models.py -v

# 3. Read existing tests
判断フロー(すべてのスタック)
+------------------------------------------+
|     Does test file exist for this code?  |
+----------+-----------------------+-------+
           | NO                    | YES
           v                       v
+------------------+    +------------------+
| CREATE test file |    | Check coverage   |
| -> Phase 1: RED  |    | for your change  |
+------------------+    +--------+---------+
                                 |
                        +--------+--------+
                        | Missing cases?  |
                        +---+---------+---+
                            | YES     | NO
                            v         v
                    +-----------+ +-----------+
                    | ADD tests | | Proceed   |
                    | Phase 1   | | Phase 2   |
                    +-----------+ +-----------+

フェーズ 1:RED - 失敗するテストを書く

新機能の場合
UI(Vitest)
describe("PriceCalculator", () => {
  it("should return 0 for quantities below threshold", () => {
    // Given
    const quantity = 3;

    // When
    const result = calculateDiscount(quantity);

    // Then
    expect(result).toBe(0);
  });
});
SDK(pytest)
class Test_ec2_ami_public:
    @mock_aws
    def test_no_public_amis(self):
        # Given - No AMIs exist
        aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])

        with mock.patch("prowler...ec2_service", new=EC2(aws_provider)):
            from prowler...ec2_ami_public import ec2_ami_public

            # When
            check = ec2_ami_public()
            result = check.execute()

            # Then
            assert len(result) == 0
API(pytest-django)
@pytest.mark.django_db
class TestResourceModel:
    def test_create_resource_with_tags(self, aws_provider):
        # Given
        provider = aws_provider
        tenant_id = provider.tenant_id

        # When
        resource = Resource.objects.create(
            tenant_id=tenant_id, provider=provider,
            uid="arn:aws:ec2:us-east-1:123456789:instance/i-1234",
            name="test", region="us-east-1", service="ec2", type="instance",
        )

        # Then
        assert resource.uid == "arn:aws:ec2:us-east-1:123456789:instance/i-1234"

実行 -> 必ず失敗すること: テストでは、まだ存在しないコードを参照します。

バグ修正の場合

まず、バグを再現するテストを書きます。

UI: expect(() => render(<DatePicker value={null} />)).not.toThrow();

SDK: assert result[0].status == "FAIL" # Currently returns PASS incorrectly

API: assert response.status_code == 403 # Currently returns 200

実行 -> 失敗するはずです(バグを再現します)。

リファクタリングの場合

リファクタリングの前に、現在の動作をすべて記録します。

# Any stack: run ALL existing tests, they should PASS
# This is your safety net - if any fail after refactoring, you broke something

実行 -> すべて成功するはずです(ベースライン)。


フェーズ 2:GREEN - 最小限のコード

テストを成功させるために、最小限のコードを書きます。最初のテストでは、ハードコードしてもかまいません。

UI:

// Test expects calculateDiscount(100, 10) === 10
function calculateDiscount() {
  return 10; // FAKE IT - hardcoded is valid for first test
}

Python(SDK/API):

# Test expects check.execute() returns 0 results
def execute(self):
    return []  # FAKE IT - hardcoded is valid for first test

これで成功します。しかし、まだ終わりではありません……


フェーズ 3:三角測量(重要)

テストが 1 つだけでは、偽装が可能です。テストを複数用意すれば、実際のロジックが必要になります。

ハードコードした値では対応できないような、異なる入力値を使ったテストを追加します。

シナリオ必須か
正常系はい
ゼロ/空の値はい
境界値はい
異なる有効な入力(偽装を破綻させる)はい
エラー条件はい

UI:

it("should calculate 10% discount", () => {
  expect(calculateDiscount(100, 10)).toBe(10);
});

// ADD - breaks the fake:
it("should calculate 15% on 200", () => {
  expect(calculateDiscount(200, 15)).toBe(30);
});

it("should return 0 for 0% rate", () => {
  expect(calculateDiscount(100, 0)).toBe(0);
});

Python:

def test_single_public_ami(self):
    # Different input -> breaks hardcoded empty list
    assert len(result) == 1
    assert result[0].status == "FAIL"

def test_private_ami(self):
    assert result[0].status == "PASS"

これで偽装は破綻します -> 実際の実装が必要です。


フェーズ 4:リファクタリング

テストが GREEN -> 動作を変えずに、コードの品質を改善します。

  • 関数/メソッドを抽出する
  • 命名を改善する
  • 型/バリデーションを追加する
  • 重複を減らす

変更のたびにテストを実行します -> 必ず GREEN のままにします。


クイックリファレンス

+------------------------------------------------+
|                 TDD WORKFLOW                    |
+------------------------------------------------+
| 0. ASSESS: What tests exist? What's missing?   |
|                                                |
| 1. RED: Write ONE failing test                 |
|    +-- Run -> Must fail with clear error       |
|                                                |
| 2. GREEN: Write MINIMUM code to pass           |
|    +-- Fake It is valid for first test         |
|                                                |
| 3. TRIANGULATE: Add tests that break the fake  |
|    +-- Different inputs, edge cases            |
|                                                |
| 4. REFACTOR: Improve with confidence           |
|    +-- Tests stay green throughout             |
|                                                |
| 5. REPEAT: Next behavior/requirement           |
+------------------------------------------------+

アンチパターン(絶対にしないこと)

# ANY language:

# 1. Code first, tests after
def new_feature(): ...  # Then writing tests = USELESS

# 2. Skip triangulation
# Single test allows faking forever

# 3. Test implementation details
assert component.state.is_loading == True   # BAD - test behavior, not internals
assert mock_service.call_count == 3         # BAD - brittle coupling

# 4. All tests at once before any code
# Write ONE test, make it pass, THEN write the next

# 5. Giant test methods
# Each test should verify ONE behavior

スタック別コマンド

UI(ui/)
pnpm test                           # Watch mode
pnpm test:run                       # Single run (CI)
pnpm test:coverage                  # Coverage report
pnpm test ComponentName             # Filter by name
SDK(prowler/)
uv run pytest tests/path/ -v              # Run specific tests
uv run pytest tests/path/ -v -k "test_name"  # Filter by name
uv run pytest -n auto tests/              # Parallel run
uv run pytest --cov=./prowler tests/      # Coverage
API(api/)
uv run pytest -x --tb=short                           # Run all (stop on first fail)
uv run pytest api/src/backend/api/tests/test_file.py     # Specific file
uv run pytest -k "test_name" -v                       # Filter by name

コメント