ジョブカン事業部のアドベントカレンダー25日目になりました!最終日となりますが、張り切っていきたいと思います!
はじめに
Claude CodeやCursor、GitHub Copilotなど、LLMを活用した開発ツールが急速に普及しています。しかし、「AIに任せたら期待と違うコードが生成された」「レビューに時間がかかる」といった課題を感じている方も多いのではないでしょうか。
本記事では、LLMの「解空間」を制御するアプローチとして、仕様駆動開発(SDD)とテスト駆動開発(TDD)を組み合わせた手法を紹介します。さらに、このワークフローを実現するMCPサーバー @mayyya/spec-tdd-workflow-mcp の実装についても解説します。
LLMの解空間をいかに狭めるか
LLMは与えられたプロンプトに対して、膨大な「解空間」の中から回答を生成します。この解空間が広すぎると、期待とは異なるコードが生成されやすくなります。
解空間を狭めるアプローチは大きく3つに分類できます。
1. プロンプトを頑張る
最も直接的なアプローチです。詳細な指示、具体例、制約条件をプロンプトに含めることで、LLMの出力を制御します。
// ❌ 悪い例のプロンプト
// 「ユーザー認証機能を作って」
// ✅ 良い例のプロンプト
// 「JWT認証を使い、リフレッシュトークンは7日間有効、
// アクセストークンは15分で失効する認証機能を実装して。
// 既存のUserモデルを使用し、bcryptでパスワードをハッシュ化すること」
// 良い例のプロンプトから生成されるコードのイメージ
interface AuthConfig {
accessTokenExpiry: number; // 15分 = 900秒
refreshTokenExpiry: number; // 7日 = 604800秒
}
interface AuthService {
login(email: string, password: string): Promise<TokenPair>;
refresh(refreshToken: string): Promise<TokenPair>;
verifyAccessToken(token: string): Promise<User>;
}
ただし、プロンプトだけでは以下の限界があります:
- 毎回詳細な指示を書くのは非効率
- プロジェクト固有のルールを伝えきれない
- 指示の解釈が曖昧になりやすい
2. コンテクストを頑張る(Rules、CLAUDE.md等)
プロジェクト全体で共有するルールや規約をファイルとして定義し、LLMに読み込ませるアプローチです。
CLAUDE.mdの例:
## コーディング規約
- 関数名はcamelCase
- 型定義は必須
- エラーハンドリングはResult型を使用
## アーキテクチャ
- Clean Architecture準拠
- ドメイン層に外部依存を入れない
このアプローチを発展させたのが仕様駆動開発(SDD: Specification-Driven Developmentです。
3. 周辺ツールで制約を与える
LLMの出力を外部ツールで検証・制約するアプローチです。
| ツール | 役割 |
|---|---|
| Linter | コードスタイル、潜在的バグの検出 |
| 型システム | 型の整合性を保証 |
| テスト | 振る舞いの正しさを検証 |
特にTDD: テスト駆動開発は、実装前にテストを書くことで「期待する振る舞い」を明確に定義できます。
SDD + TDD:仕様とテストで解空間を挟み込む
仕様駆動開発(SDD)の課題
SDDでは、実装前に詳細な仕様書を作成します。LLMはこの仕様を参照しながらコードを生成するため、解空間を大幅に狭められます。
仕様書 → LLM → 実装コード
しかし、SDDには課題があります:
「仕様通りに実装されているか」のレビューが大変
仕様書と実装コードを突き合わせて確認する作業は、人間にとって認知負荷が高く、見落としが発生しやすいです。
TDDで「仕様→実装」をブリッジする
ここでTDDを組み合わせます。
仕様書 → テストコード → 実装コード
↑ここでブリッジ
この組み合わせの利点:
-
テストが仕様の「実行可能な形式」になる
- 仕様の曖昧さがテスト作成時に明確化される
- 「テストが通る = 仕様を満たす」という明確な基準
-
レビューコストの大幅削減
- 実装の詳細を追わなくても、テストが通れば仕様準拠を確認できる
- 人間はテストケースの妥当性だけを確認すればよい
-
LLMの解空間を二重に制約
- 仕様:「何を作るか」を定義
- テスト:「どう動くべきか」を定義
┌─────────────────────────────────────────────────────────┐
│ SDD + TDD の効果 │
│ │
│ 仕様書による制約 テストによる制約 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ │ │ │ │
│ │ 解空間を │ + │ 解空間を │ │
│ │ 上から制約 │ │ 下から制約 │ │
│ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ └────────┬───────────────┘ │
│ ↓ │
│ ┌───────────────┐ │
│ │ 期待する実装 │ │
│ │ の範囲 │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────┘
spec-tdd-workflow-mcp の紹介
背景
spec-workflow-mcpは、Claude等のLLMクライアントと連携して仕様駆動開発を行うためのMCPサーバーです。
このツールをforkし、TDDワークフローの機能を追加したのが @mayyya/spec-tdd-workflow-mcp です。
インストール
# npxで直接実行
npx -y @mayyya/spec-tdd-workflow-mcp /path/to/your/project
# または Claude Desktop の設定に追加
claude_desktop_config.json:
{
"mcpServers": {
"spec-tdd-workflow": {
"command": "npx",
"args": ["-y", "@mayyya/spec-tdd-workflow-mcp", "/path/to/your/project"]
}
}
}
ワークフロー概要
┌──────────────────────────────────────────────────────────────┐
│ SDD + TDD ワークフロー │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 要件定義 │ → │ 設計書 │ → │ タスク │ → │ 実装 │ │
│ │ │ │ │ │ 分解 │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────┐ │
│ │ _TDD: true │ │
│ │ タスク │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────────────────────────┼──────────────────┐ │
│ │ ↓ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Gate-A │→ │ Gate-B │→ │ Gate-C │ │ │
│ │ │ RED │ │ GREEN │ │ REFACTOR│ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │テスト作成 │ │ 実装して │ │リファクタ │ │ │
│ │ │全て失敗 │ │ 全て通過 │ │トレース │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ TDD Quality Gates │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
主要機能
1. 仕様管理(SDDベース)
プロジェクト内に .spec-workflow/ ディレクトリが作成され、以下の構造で仕様を管理します:
.spec-workflow/
├── specs/
│ └── feature-name/
│ ├── requirements.md # 要件定義
│ ├── design.md # 設計書
│ └── tasks.md # タスク一覧
├── steering/
│ ├── product.md # プロダクト方針
│ ├── tech.md # 技術スタック
│ └── structure.md # ディレクトリ構造
└── tdd/
└── feature-name/
├── results/ # テスト結果
└── traces/ # トレーサビリティ
2. TDD Quality Gates
TDDタスク(_TDD: true_ マーカー付き)には、3段階の品質ゲートが適用されます:
| Gate | 名前 | 条件 |
|---|---|---|
| Gate-A | RED | テストを作成し、全てのテストが失敗すること |
| Gate-B | GREEN | 実装を行い、全てのテストが通過すること |
| Gate-C | REFACTOR | リファクタリング後もテストが通過し、トレーサビリティが完備されていること |
これにより、「テストを書いたつもりが最初から通っていた」「実装が不完全なまま完了扱いになった」といった問題を防ぎます。
3. MCPツール一覧
| ツール名 | 説明 |
|---|---|
spec-workflow-guide |
仕様作成のガイダンス |
steering-guide |
プロジェクト方針の管理 |
spec-status |
仕様・タスクの進捗確認 |
approvals |
承認ワークフロー |
run-tests |
テスト実行と結果保存 |
tdd-gate |
品質ゲートの検証 |
generate-trace |
トレーサビリティ生成 |
log-implementation |
実装ログの記録 |
4. カスタム言語対応
config.toml で独自のテスト環境を定義できます:
# .spec-workflow/config.toml
# カスタム言語のTDD設定
[tdd.languages.rust]
test_command = "cargo test"
coverage_command = "cargo llvm-cov --json"
test_file_pattern = "**/*_test.rs"
coverage_parser = "llvm-cov"
[tdd.languages.python]
test_command = "pytest"
coverage_command = "pytest --cov --cov-report=json"
test_file_pattern = "**/test_*.py"
coverage_parser = "pytest-cov"
設定後、TypeScriptプロジェクトでは以下のようにテストを実行できます:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['node_modules/', 'dist/'],
},
},
});
ダッシュボード
Webダッシュボードでリアルタイムに進捗を確認できます:
# ダッシュボードを起動
npx @mayyya/spec-tdd-workflow-mcp dashboard
ダッシュボードの機能:
- 仕様一覧とステータス表示
- タスクの進捗管理
- TDD品質ゲートの状態確認
- 承認ワークフローのUI
将来的な拡張
1. Property-Based Testing との統合
現在のTDDは具体的なテストケースを書きますが、Property-Based Testing(例:QuickCheck、fast-check)と組み合わせることで、より網羅的な検証が可能になります。
import { test, expect } from 'vitest';
import * as fc from 'fast-check';
// 現在: 具体的なテストケース
test('add(1, 2) should return 3', () => {
expect(add(1, 2)).toBe(3);
});
test('add(0, 0) should return 0', () => {
expect(add(0, 0)).toBe(0);
});
// 将来: Property-Based Testing
// 任意の整数ペアに対して交換法則が成り立つことを検証
test.prop([fc.integer(), fc.integer()])('add is commutative', (a: number, b: number) => {
expect(add(a, b)).toBe(add(b, a));
});
// 任意の整数に対して単位元(0)の性質を検証
test.prop([fc.integer()])('add with zero is identity', (n: number) => {
expect(add(n, 0)).toBe(n);
});
2. 仕様からのテスト自動生成
仕様書(requirements.md)をLLMに解析させ、テストケースの雛形を自動生成する機能を検討しています。
// requirements.md の仕様例:
// - ユーザーはメールアドレスでログインできる
// - パスワードは8文字以上必須
// - ログイン失敗は3回までで、それ以降はロックされる
// ↓ LLMが自動生成するテスト雛形
import { describe, test, expect, beforeEach } from 'vitest';
import { AuthService } from './auth-service';
describe('AuthService', () => {
let authService: AuthService;
beforeEach(() => {
authService = new AuthService();
});
describe('ログイン機能', () => {
test('有効なメールアドレスとパスワードでログインできる', async () => {
// TODO: 実装
});
test('無効なメールアドレス形式はエラーになる', async () => {
// TODO: 実装
});
});
describe('パスワードバリデーション', () => {
test('8文字未満のパスワードはエラーになる', async () => {
// TODO: 実装
});
test('8文字以上のパスワードは受け入れられる', async () => {
// TODO: 実装
});
});
describe('アカウントロック', () => {
test('3回連続でログイン失敗するとアカウントがロックされる', async () => {
// TODO: 実装
});
test('ロック中のアカウントはログインできない', async () => {
// TODO: 実装
});
});
});
// ↓ 人間がレビュー・調整後、TDDワークフロー開始
3. Mutation Testing の導入
テスト自体の品質を検証するため、Mutation Testing(変異テスト)の統合を検討しています。
// 元の実装コード
function isAdult(age: number): boolean {
return age >= 18;
}
// ↓ Mutation Testing が自動で変異を加える
// 変異1: 境界値の変更 (>= → >)
function isAdult_mutant1(age: number): boolean {
return age > 18; // 18歳がfalseになる変異
}
// 変異2: 比較演算子の反転 (>= → <)
function isAdult_mutant2(age: number): boolean {
return age < 18; // 完全に逆の結果になる変異
}
// 変異3: 定数の変更 (18 → 17)
function isAdult_mutant3(age: number): boolean {
return age >= 17; // 17歳もtrueになる変異
}
// ↓ テストが変異を検出できるか?
import { test, expect } from 'vitest';
// このテストだけだと変異1を検出できない(境界値テストが不足)
test('20歳は成人', () => {
expect(isAdult(20)).toBe(true);
});
// 境界値テストを追加することで変異1も検出可能に
test('18歳は成人', () => {
expect(isAdult(18)).toBe(true); // 変異1はここで検出される
});
test('17歳は未成年', () => {
expect(isAdult(17)).toBe(false); // 変異3はここで検出される
});
// Mutation Score = 検出できた変異数 / 全変異数
// → テストの品質指標として活用
4. マルチエージェント対応
複数のLLMエージェントが協調してSDD + TDDを進めるワークフロー:
- Spec Agent: 仕様の作成・整合性チェック
- Test Agent: テストケースの生成・レビュー
- Impl Agent: 実装コードの生成
- Review Agent: 全体のレビュー・品質チェック
まとめ
AI駆動開発において、LLMの出力品質を高めるには「解空間の制御」が鍵となります。
| アプローチ | 効果 | 課題 |
|---|---|---|
| プロンプト改善 | 即効性がある | 毎回の手間、属人化 |
| コンテクスト(Rules等) | 一貫性を保てる | 仕様との整合性検証が難しい |
| SDD | 仕様を明確化 | 実装との整合性レビューが大変 |
| TDD | 振る舞いを保証 | テスト自体の品質担保が必要 |
| SDD + TDD | 仕様と振る舞いの両方を保証 | ワークフローの複雑化 |
@mayyya/spec-tdd-workflow-mcp は、SDD + TDDのワークフローをMCPサーバーとして実装し、Claude等のLLMクライアントから簡単に利用できるようにしたツールです。
AI駆動開発に取り組んでいる方は、ぜひ試してみてください。
最後に
ジョブカン事業部のアドベントカレンダー、お楽しみいただけましたでしょうか。
個性豊かな記事ばかりで(私の記事もそうなっていたら嬉しいですが)、読者としても投稿者としても楽しく参加できました。
ここまでお読みいただいた皆さまにも楽しんでいただけていたら幸いです。
どうぞ良いお年をお迎えください!
![]()
![]()
Merry Xmas ![]()
![]()
![]()
リポジトリ: GitHub
npm: @mayyya/spec-tdd-workflow-mcp
元リポジトリ: Pimzino/spec-workflow-mcp
最後になりますが、DONUTSでは新卒中途インターン問わず積極的に採用活動を行っています。
弊社にご興味を持っていただけた方はぜひご応募ください!
