Spec-Driven Developmentの始め方:Pythonでテスト駆動から仕様駆動へ

Spec-Driven Developmentの始め方:Pythonでテスト駆動から仕様駆動へ | mohablog
目次

仕様駆動開発とは何か

テスト駆動開発(TDD)は多くのエンジニアに浸透しましたが、最近注目されている仕様駆動開発(Spec-Driven Development)はその先の段階だと考えます。単なるテストを書くのではなく、実行可能な仕様として要件を定義し、その仕様に基づいてコードを実装していくアプローチですね。

github/spec-kitは、このSpec-Driven Developmentを実践するためのツールキットです。AIアシスタント(Claude Copilotなど)との連携を前提に設計されており、仕様から実装へのギャップを最小化できます。

私がこのツールキットに注目したきっかけは、フリーランスで複数のクライアント案件を抱える中、要件定義の曖昧さによる手戻りが増えていたためです。仕様を形式化して、AIに正確に伝えられるようにしたいという課題がありました。

Spec-Driven DevelopmentとTDDの違い

まずアンチパターンから見てみましょう。従来的なTDDでは、テストコードが仕様の役割を果たしていました。

import unittest

class TestUserValidation(unittest.TestCase):
    def test_email_format(self):
        # テストがあるだけで、要件が明示的でない
        self.assertTrue(is_valid_email('user@example.com'))
    
    def test_password_length(self):
        # 「なぜ8文字なのか」という背景が不明確
        self.assertFalse(is_valid_password('short'))

一方、Spec-Driven Developmentでは仕様をより明示的に定義します:

"""User Registration Specification

As a user, I want to create an account with valid credentials.

Requirements:
  - Email must follow RFC 5322 format
  - Password must be at least 8 characters
  - Password must contain uppercase, lowercase, and numbers
  - Email must be unique in the system

Given a valid email and strong password
When the user submits the registration form
Then an account is created and a confirmation email is sent
"""

このように仕様を構造化することで、AIツールが要件を正確に理解でき、実装品質が向上します。

Spec-kitの主要な機能

github/spec-kitは以下のような機能を提供しています:

  • 仕様ファイルの定義:マークダウンベースで、BDD(Behavior-Driven Development)スタイルの仕様を記述
  • AI連携:Claude APIと統合し、仕様から自動でテストコードを生成
  • 検証レポート:実装が仕様を満たしているかを自動チェック
  • CLI ツール:コマンドラインから仕様の検証やテスト生成を実行

公式ドキュメントによると、v0.2.0からPythonの本格的なサポートが開始されました。

Pythonプロジェクトでの実装フロー

実際のプロジェクトで使う場合のステップを説明します。

ステップ1: 仕様ファイルの作成

プロジェクトルートにspecs/ディレクトリを作成し、以下のような仕様ファイルを配置します:

# Product Search API Specification

Version: 1.0
Last Updated: 2024-01-15

## Feature: Search for products

### Scenario 1: Search with valid query
Given a search query "laptop"
And the database has products matching the query
When the search endpoint is called
Then a list of products should be returned
And response status should be 200

### Scenario 2: Search with empty query
Given an empty search query
When the search endpoint is called
Then an error message should be returned
And response status should be 400

### Constraints
- Search should be case-insensitive
- Results should be paginated (max 50 items per page)
- Response time should be under 500ms

ステップ2: Spec-kitでテスト生成

CLIコマンドでこの仕様からテストコードを自動生成できます:

spec-kit generate --spec specs/search_api.md --output tests/test_search_api.py --language python
Generated test file: tests/test_search_api.py
Generated 2 test cases from specification
✓ test_search_with_valid_query
✓ test_search_with_empty_query

ステップ3: 生成されたテストコード

spec-kitが自動生成するテストは以下のような構造になります:

import pytest
from fastapi.testclient import TestClient
from app import app

client = TestClient(app)

class TestProductSearchAPI:
    """
    Product Search API Specification
    Feature: Search for products
    """
    
    def test_search_with_valid_query(self):
        """
        Scenario 1: Search with valid query
        Given a search query "laptop"
        And the database has products matching the query
        When the search endpoint is called
        Then a list of products should be returned
        And response status should be 200
        """
        response = client.get("/api/search?q=laptop")
        assert response.status_code == 200
        assert isinstance(response.json(), list)
        assert len(response.json()) > 0
    
    def test_search_with_empty_query(self):
        """
        Scenario 2: Search with empty query
        When the search endpoint is called with empty query
        Then an error message should be returned
        And response status should be 400
        """
        response = client.get("/api/search?q=")
        assert response.status_code == 400
        assert "error" in response.json()

ステップ4: 仕様に基づく実装

生成されたテストに基づいて、実装コードを書きます:

from fastapi import FastAPI, HTTPException, Query
from typing import List
import time

app = FastAPI()

class Product:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name

# ダミーデータベース
PRODUCTS = [
    Product(1, "Laptop Pro 15"),
    Product(2, "Gaming Laptop"),
    Product(3, "Desktop PC"),
    Product(4, "Laptop Stand"),
]

@app.get("/api/search")
async def search_products(
    q: str = Query(..., min_length=1),
    page: int = Query(1, ge=1),
    limit: int = Query(50, ge=1, le=50)
) -> List[dict]:
    """
    Search for products by query
    
    - Search is case-insensitive
    - Results are paginated
    - Response time constraint: < 500ms
    """
    start_time = time.time()
    
    if not q:
        raise HTTPException(status_code=400, detail="Search query cannot be empty")
    
    # Case-insensitive search
    query_lower = q.lower()
    results = [
        {"id": p.id, "name": p.name}
        for p in PRODUCTS
        if query_lower in p.name.lower()
    ]
    
    # Pagination
    start_idx = (page - 1) * limit
    end_idx = start_idx + limit
    paginated_results = results[start_idx:end_idx]
    
    elapsed = time.time() - start_time
    assert elapsed < 0.5, f"Response time exceeded: {elapsed}s"
    
    return paginated_results

AIアシスタントとの連携

spec-kitの真の力は、Claude CopilotやGitHub Copilotのようなアシスタントとの組み合わせにあります。仕様ファイルが明確に定義されていれば、AIが生成するコードの品質が大幅に向上します。

調べてみたところ、spec-kitはプロンプト生成機能も備えており、仕様からAIへの指示文を自動で構成できるということです。例えば:

spec-kit prompt --spec specs/search_api.md --model claude-3-5-sonnet

このコマンドで、Claude向けの最適化されたプロンプトが生成され、より正確な実装提案を受け取れます。

複数シナリオの管理

より複雑なシステムでは、複数の仕様ファイルを組織的に管理する必要があります:

  • specs/user-management/ - ユーザー認証・登録関連
  • specs/payment/ - 決済処理関連
  • specs/notifications/ - 通知機能関連

spec-kitはディレクトリ単位でのバッチ処理に対応しているため、全スペックを一括生成できます:

spec-kit generate --spec specs/ --output tests/ --language python --recursive
Processing specs/user-management/registration.md
Generated: tests/test_user_registration.py

Processing specs/payment/checkout.md
Generated: tests/test_payment_checkout.py

Total: 15 test files generated

実装での一般的なハマりどころ

このアプローチを導入する際に注意すべき点があります。

ハマりどころ1: 仕様の粒度が曖昧

仕様が大きすぎると、AIが生成するテストも不正確になります。1つの仕様ファイルは、1つの機能に焦点を当て、3~5個のシナリオに限定するのが目安です。

ハマりどころ2: 非機能要件の記述忘れ

レスポンス時間、キャッシュ戦略、セキュリティ要件などの非機能要件を仕様に含めないと、実装後に問題が生じます。Constraintsセクションに明示的に記載しましょう。

ハマりどころ3: 生成されたテストの過信

spec-kitが生成するテストはスケルトンであり、エッジケースを全てカバーしているわけではありません。生成後は必ず人間による確認が必要です。

Pythonでの型安全性の向上

spec-kitをPydanticと組み合わせると、型安全性をさらに高められます:

from pydantic import BaseModel, Field
from typing import List

class SearchQuery(BaseModel):
    """仕様から自動生成される入力スキーマ"""
    q: str = Field(..., min_length=1, max_length=100, description="Search query")
    page: int = Field(1, ge=1, description="Page number")
    limit: int = Field(50, ge=1, le=50, description="Items per page")

class ProductResponse(BaseModel):
    """仕様から自動生成される出力スキーマ"""
    id: int
    name: str
    relevance_score: float = Field(..., ge=0.0, le=1.0)

class SearchResult(BaseModel):
    items: List[ProductResponse]
    total: int
    page: int
    has_more: bool

このようにスキーマを定義しておくと、自動生成されるテストにも型情報が反映され、より正確な検証が可能になります。

CI/CDパイプラインへの統合

本番環境では、spec-kitの検証をCI/CDパイプラインに組み込むべきです:

# .github/workflows/spec-validation.yml
name: Spec-Driven Development Validation

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.12'
      
      - name: Install spec-kit
        run: pip install spec-kit
      
      - name: Validate specifications
        run: spec-kit validate --spec specs/ --language python
      
      - name: Generate tests
        run: spec-kit generate --spec specs/ --output tests/generated/ --language python
      
      - name: Run generated tests
        run: pytest tests/generated/ -v

実装環境の設定

Pythonプロジェクトでspec-kitを導入する場合の最小限の設定を示します:

my-project/
├── specs/
│   ├── user-management.md
│   ├── search-api.md
│   └── payment-api.md
├── src/
│   ├── main.py
│   ├── models.py
│   └── api/
│       ├── search.py
│       └── payment.py
├── tests/
│   ├── generated/
│   │   ├── test_user_management.py
│   │   ├── test_search_api.py
│   │   └── test_payment_api.py
│   └── integration/
│       └── test_workflows.py
├── pyproject.toml
└── spec-kit.config.yaml

設定ファイルの例:

version: 1.0
language: python
framework: fastapi

spec:
  dir: ./specs
  format: markdown

test:
  output_dir: ./tests/generated
  framework: pytest
  include_docstrings: true

ai:
  provider: claude
  model: claude-3-5-sonnet
  auto_fix: false

validation:
  strict: true
  check_constraints: true

業界別の活用シーン

異なるドメインでの使い分けを考えてみましょう。

ドメイン 仕様の重点 ツールの活用方法
金融系API セキュリティ、監査証跡、トランザクション整合性 Constraints セクションで厳格な要件を定義
SaaS アプリケーション ユーザー体験、エラーハンドリング、UI状態遷移 複数のシナリオで正常系・異常系を網羅
IoT バックエンド レスポンス時間、スケーラビリティ、データフォーマット 非機能要件を詳細に記述
マイクロサービス API コントラクト、バージョニング、フォールバック サービス間の依存関係を仕様に明示

まとめ

Spec-Driven Developmentは、仕様の形式化とAI連携による新しい開発アプローチです。github/spec-kitはPythonでこれを実践するための実用的なツールとなります。

  • 仕様の構造化は、要件定義の曖昧さを減らし、AIとのコミュニケーション精度を向上させる
  • テスト自動生成により、BDD的な思考が自然に身につく
  • CI/CD統合で、仕様と実装の乖離を継続的に検証できる
  • 複数シナリオ管理により、大規模プロジェクトでも仕様の一貫性を保ちやすい
  • 型安全性との組み合わせで、実装品質が格段に向上する

よくある質問(FAQ)

Q1: 既存のテストコードがある場合、どう移行すべき?

既存のテストコードは一度に置き換える必要はありません。新機能からSpec-Driven Developmentを導入し、段階的に仕様を追加していくアプローチをお勧めします。既存テストと並行して運用し、リファクタリング時に仕様に統合するとスムーズです。

Q2: AIが生成したテストに信頼性がない場合は?

spec-kitの生成テストは初期スケルトンと考え、必ず人間による検査が必要です。特にエッジケース、セキュリティ検証、パフォーマンス境界値は手動で追加すべき領域です。auto_fix: falseに設定し、手動レビューを厳しくするのが安全です。

Q3: マイクロサービスアーキテクチャでの使用方法は?

マイクロサービス環境では、各サービスごとにspecs/ディレクトリを配置し、サービス間のAPI コントラクトを仕様に明記します。共有仕様はspecs/shared/に配置し、サービス間の整合性を保ちます。APIゲートウェイレベルでも統合テストを仕様化すると、サービス間の連携検証がより堅牢になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次