ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

Claude Codeで実現するAI駆動TDD

はじめに

みなさんこんにちは!

株式会社ミツモアでエンジニアをしている佐藤です。(@satoman0703)

今年もミツモアではアドベントカレンダーを実施しています。

他の参加者の記事はこちら: MeetsMore Advent Calendar 2025

社内での取り組みを通じて蓄積された有益な情報が載っているので、ぜひご覧になってください。

今回で2回目のアドベントカレンダーの登場となります。前回は2年半の取り組みを振り返った記事を書きました: ほぼ未経験からのエンジニア転職 - 2年半で学んだこと


TDDは理想だけど現実的じゃない?

エンジニアとして開発を始めた当初、TDD(テスト駆動開発)について知りました。

「テストを先に書いて、それを通す実装をする」という理想的なアプローチに魅力を感じました。しかし当時は、AIの利用もGPTにわからないことを聞く程度だったこともあり、TDDは理想的だけどテストを書く時間もかかるし、現実的ではないように感じていました。

AI時代の到来で状況が一変

しかし、Claude Codeなどの高品質なAIツールの登場で状況が一変しました

現在担当している電子署名機能の開発では、AI駆動TDDを全面的に採用しています。その結果:

  • ✅ テストカバレッジが向上
  • ✅ API開発のリードタイムが短縮
  • ✅ フロントエンドとバックエンドの並行開発の実現

といった良い結果を得ることができています。

この記事では、私が実践しているAI駆動TDDのワークフローを、実際の開発例を交えて紹介します。


私たちの開発環境

本題に入る前に、私たちの開発環境を簡単に紹介します。

技術スタック

カテゴリ 技術スタック
Backend NestJS + Prisma + PostgreSQL
Frontend Next.js + Redux Toolkit Query
API連携 OpenAPI/Swagger による型安全な通信
AI Tools Claude Code + 各種MCP

OpenAPIを中心としたワークフロー

私たちのチームでは、OpenAPI/Swaggerを中心に据えた開発フローを確立しています:

1. Server側でAPI実装
   ↓
2. swagger.jsonを自動生成
   ↓
3. openapi でクライアント型を自動生成
   ↓
4. Frontend側で型安全にAPIを利用

この仕組みにより、バックエンドとフロントエンドの型の不整合が発生しないようになっています。

そして、この型生成のタイミングが、後述する2-PRワークフローの鍵となります。


従来のTDD vs AI駆動TDD

Before: TDDを実践する上でのハードル

TDDの有効性は理解しつつも実際に実践するには以下のようなハードルがありました:

  • 実装前に適切なテストケースを考えるのは意外と難しい
  • 正常系・異常系・エッジケースを漏れなく洗い出すには経験と労力が必要
  • 質の高いテストを書くにはそれなりの時間と工数がかかるため、実装に入るまでの時間が長くなってしまう

After: AI駆動TDDで解決

Claude CodeとNotion MCPを活用することで、これらの課題が解決しました:

  • テストケース設計 → AIが自動化:AIがNotionの要件書を読み込み、網羅的なテストケースを生成することで、テストファーストが当たり前になる
  • 網羅性 → AIが担保:正常系だけでなく異常系・エッジケースもAIが提案し、カバレッジが向上
  • テストを書く時間 → 大幅短縮:AIがテストコードを生成し、そのテストをもとに実装を生成。人間はレビューに集中でき、開発速度が大きく向上

AI駆動TDDの開発フロー

私が実践している開発フローは以下の通りです:

なぜ2つのPRに分けるのか

PR #1: APIコントラクト定義 (Step 3まで)

  • 目的: API仕様を確定し、OpenAPI型定義を生成
  • 含まれるもの: DTO、Controller、Serviceスケルトン、テスト(.skip付き)
  • 効果:
    • フロントエンド開発を並行して開始できる
    • git worktreeを使えば、FEとBEの繋ぎ込みも並行作業可能
  • レビュー: 仕様レビューに集中、レビュワーの負担軽減

PR #2: TDD実装 (Step 4-5)

  • 目的: テストを通す実装(要件に沿った実装)を完成させる
  • 含まれるもの: Service実装、.skipの削除、エッジケースの追加
  • 効果:
    • テストファーストで堅牢な実装
    • PR #1で型定義が確定しているため、AIがより正確な実装を出力
  • レビュー: 実装レビューに集中、レビュワーの負担軽減

ポイント:

  • レビュワーの1回あたりのレビュー量を減らせる
  • AIが生成したコードを段階的に検証できるため、品質を保ちやすい
  • PR #1で型が確定することで、PR #2のAI生成精度が向上

feature単位でclaude.mdを作成

また、feature単位でclaude.mdを作成してAIに事前に読み込ませることで効率化を図っています。

これにより、以下が実現できます:

  • 他のセッションでも同じコンテキストを共有
  • 実装の一貫性を保つ
  • AIがより正確な実装を出力(意図と異なった出力がされた場合はclaude.mdをupdateしています)

簡単にですが、claude.mdの例をご紹介します。

# E-signature Feature Development Guide

## TDDのやり方
1. NotionのTRDを読み込む
2. DTO、Controller、Service、Testを生成 (.skip付き)
3. PR #1を作成してマージ
4. テストを通す実装を書く
5. .skipを削除してテスト実行
6. PR #2を作成してマージ

## 実装の注意点
- Server側でエンドポイントを作成したら、必ず `pnpm openapi` で型生成も実行
- 既存のRepositoryパターンを使う
- schema定義はe-signature.prismaを見る
...

などといったことを記載しています。

このclaude.mdを最初に読み込ませることで、AIが一貫したコードを生成できます。


実践: Notion MCPとClaude Codeで電子署名キャンセルAPIを開発

実際の開発例を見ていきましょう。

Step 1-2: Notionで要件を精緻化

特にTRDの段階でかなり細かいレベルまでNotionに仕様を記載します。

かなり細かいレベルというのは他の実装者もタスクチケットを見る事で実装が明確にわかるレベルを目指しています。

この段階で要件を精緻化しておくと、実装の際にNotionのページをAIに読み込ませることで出力の精度があがります。

TRD (Technical Requirements Document) の例

実際のNotionには以下のような詳細を記載しています。

# 電子署名依頼キャンセル機能

## User Story
**As a user** ユーザーは
**I want** 一度依頼した署名の書類を取り消すことができる
**So** 取り消しのアクションを追加する

## Acceptance Criteria -> このタスクで達成すべき要件を記載します
- [ ] 署名依頼をしている書類を取り消すことができる
- [ ] 取り消しになった場合、メールで取り消しのメールを送信する
...

## API仕様: POST /api/e-signature/requests/:requestId/cancel

### エンドポイント
- Method: POST
- Path: /api/e-signature/requests/:requestId/cancel

### Request DTO
- PathParams: requestId (UUID)
- Body:
  {
    reason?: string // キャンセル理由(任意)
  }

### Response DTO
- 200 OK:
  {
    success: boolean
  }
- 400 Bad Request: 既にキャンセル済み/完了済み
- 404 Not Found: 依頼が存在しない

### ビジネスロジック
-> 実際の処理順序を記載します

### データベース更新
-> 実際にデータベースを更新する際にどのような処理を行うかを記載します

### Controllerの実装例
-> method,endpoint,RBACを含めてcontrollerの実装イメージを記載します

### Serviceの実装例
-> ビジネスロジックをもとに必要なデータの取得、バリデーション、トランザクションなどの処理の実装イメージを記載します。

このように、User Story、Acceptance Criteria、API仕様、DTO定義、ビジネスロジック、DB更新内容まで詳細に記載します。

この詳細なTRDがClaude Codeに読み込ませる要件書となります。


Step 3: Claude Code + Notion MCPでコントラクト生成

開発者 → Claude Code

重要: claude.mdとNotionのTRDを両方読み込ませます

@[claude.md](http://claude.md) apps/server/src/e-signature/[claude.md](http://claude.md)
@Notion https://www.notion.so/.../e-signature-cancel-api

この要件書を元に、以下を生成して:

1. DTO定義 (バリデーション付き)
2. Controller (メソッドシグネチャのみ)
3. Service (throw new Error('Not implemented'))
4. Integration Test (describe.skip付き)

Claude Code → 生成されたコード

DTO

export class CancelESignatureRequestDto {
  // class-validatorを使ったバリデーション
  @IsOptional()
  @IsString()
  reason?: string
}

export class CancelESignatureRequestResponseDto {
  success: boolean
}

Controller

@Controller('e-signature/requests')
export class ESignatureController {
  @Post(':requestId/cancel')
  // 権限チェック: 独自のRBAC(Role-Based Access Control)デコレータを使用
  async cancel(
    @Param('requestId') requestId: string,
    @Body() dto: CancelESignatureRequestDto,
  ): Promise<CancelESignatureRequestResponseDto> {
    return this.eSignatureRequestsService.cancel(requestId, dto)
  }
}

Service

@Injectable()
export class ESignatureService {
  async cancel(
    requestId: string,
    dto: CancelESignatureRequestDto,
  ): Promise<CancelESignatureRequestResponseDto> {
    // 実装はまだ空、PR #2で実装する
    throw new Error('Not implemented')
  }
}

Integration Test (.skip付き)

describe.skip('POST /api/e-signature/requests/:requestId/cancel', () => {
  it('署名依頼をキャンセルできること', async () => {
    const { data, response } = await [api.POST](http://api.POST)(
      `/api/e-signature/requests/${[request.id](http://request.id)}/cancel`,
      { body: { reason: 'テスト理由' } }
    )

    expect(response.status).toBe(200)
    expect(data?.success).toBe(true)

    // DBでステータスが更新されていることを確認
    const cancelled = await prisma.eSignature.findUnique({
      where: { id: [request.id](http://request.id) }
    })
    expect(cancelled.status).toBe('cancelled')
  })

  it('既にキャンセル済みの場合、400を返すこと', async () => {
    const { response } = await [api.POST](http://api.POST)(
      `/api/e-signature/requests/${[cancelledRequest.id](http://cancelledRequest.id)}/cancel`,
      { body: {} }
    )

    expect(response.status).toBe(400)
  })
  ...
})

この時点で、要件を満たすようなエンドポイント,dto,網羅的なテストを作成することを目標とします。

OpenAPI型生成

pnpm openapi

これで、フロントエンド側で使える型定義が生成されます。

PR #1を作成してマージ

この段階でPR #1を作成。レビュアーは以下を確認します

  • NotionのTRDと仕様が一致しているか
  • DTOのバリデーションルールは適切か
  • テストケースは網羅的か
  • OpenAPI定義は正しく生成されているか

PR #1がマージされた時点で、フロントエンド側は生成された型定義を使って開発を開始できます。

私はgit worktreeを使って並行作業を行っています。

こうする事で開発効率が大幅に向上しました。

Step 4-5: テストを通す実装を生成

開発者 → Claude Code

このテストを通す実装を書いて。

Claude Code → 実装を生成

Service実装

@Injectable()
export class ESignatureService {
  async cancel(
    requestId: string,
    dto: CancelESignatureRequestDto,
  ): Promise<CancelESignatureRequestResponseDto> {
    // Prismaのトランザクションを使用
    return await this.prisma.$transaction(async (tx) => {
      // 1. 署名依頼の取得とバリデーション
      // 2. ステータスチェック
      // 3. キャンセル処理
      // 4. メール通知

      return { success: true }
    })
  }
}

テストの.skipを削除して実行

pnpm test e-signature-requests.integration.spec.ts

PR #2を作成してマージ

レビュアーは以下を確認します

  • すべてのテストが通っているか
  • エッジケースが考慮されているか
  • トランザクション処理は適切か
  • セキュリティ上の問題はないか

PR #2がマージされて、API開発完了!


Claude Codeを最大限活用するプロンプト術

1. 要件を最初に精緻化することの重要性

悪い例

電子署名キャンセルAPIを作って

良い例

@Notion https://www.notion.so/.../e-signature-cancel-api

この要件書を元に、DTO、Controller、Service、テストを生成して。
既存のコードスタイルに従ってください。

ポイント:

  • NotionのTRDで要件を詳細化してから、AIに読み込ませる
  • 要件が明確であればあるほど、AIの出力精度が上がる
  • Notion MCPを使うことで、最新の要件を常に参照できる

2. テストファーストで指示する

PR #1とPR #2に分ける効果:

方法 精度 理由
要件だけ読み込ませて一気に実装 50-60% 要件の解釈がブレる
PR #1でテスト生成 → PR #2で実装 80-90% テストが要件書の役割を果たす

悪い例

要件を読んで、実装を全部書いて

良い例

【PR #1】
要件を読んで、まずテストを書いて。
実装はthrow new Error('Not implemented')で。

【PR #2】
このテストを通す実装を書いて。

ポイント:

  • PR #1でテストを確定させることで、PR #2のAI生成精度が劇的に向上
  • テストが「何を実装すべきか」の明確な指針となる
  • 人間がレビューしやすくなる

AI駆動TDDのメリット

1. 要件漏れを防ぎ、品質が向上

  • テストを最初に書くことで、要件を満たしているかを機械的に検証
  • レビュアーがテストケースで仕様を理解しやすくなる
  • テストカバレッジが大幅に向上する

2. 最初から高品質なコードが出力

  • テストを見ながらAIに実装を書かせることで、初回の出力から8割程度完成させることができる
  • エッジケースもAIが提案
  • 人間はレビューに集中できる

3. フロントエンドとの並行開発が可能

  • PR #1でOpenAPI型定義を早期に生成
  • バックエンド実装を待たずにフロントエンド開発を開始
  • git worktreeを使えば、FEとBEの繋ぎ込みも並行作業可能
  • 開発のリードタイムを短縮することができる

4. レビューの質と効率が向上

  • PR #1では仕様レビューに集中
  • PR #2では実装レビューに集中
  • レビュワーの1回あたりのレビュー量を減らせる
  • レビュー指摘の50%をAIが事前検出

まとめ

Claude CodeとNotion MCPの登場により、テスト駆動開発のハードルは劇的に下がりました

AI駆動TDDの開発フローは、以下を達成できています。

  • 要件漏れを防ぎ、品質が向上
  • 最初から8割完成のコードが出力
  • フロントエンドとの並行開発が可能
  • レビューの質と効率が向上

ただまだベストプラクティスとは言えないので、実践を通してよりよい開発フローを探求していきたいと思います。


最後まで読んでいただき、ありがとうございました!

株式会社ミツモアではソフトウェアエンジニア、プロダクトマネージャーなどを募集しています!

こんな方と一緒に働きたいです

  • TypeScriptでの開発経験をお持ちの方
  • 急成長中のスタートアップで多種多様な国籍のメンバーと働きたい方
  • 日本語でも英語でも仕事がしたい方
  • SaaS開発やAIエージェント開発に興味がある方

興味がある方はぜひご応募ください! https://corp.meetsmore.com/