# コーディングとテストガイド

## 概要

このガイドは、開発ガイドのコーディングとテスト部分を詳細化したドキュメントです。TDD（テスト駆動開発）を中核とした実装プロセスの具体的な手順と、品質を維持するための規律を定義します。

## 基本原則

### TDD（Test-Driven Development）の三つの原則

1. **失敗するテストを書くまで、プロダクションコードを書いてはならない**
2. **失敗するテストは、失敗するのに十分なだけ書く**
3. **現在失敗しているテストを成功させる以上のプロダクションコードを書いてはならない**

### Red-Green-Refactor サイクル

```plantuml
@startuml
skinparam state {
  BackgroundColor Red
  BackgroundColor LightGreen
  BackgroundColor LightBlue
}

[*] --> Red
state Red {
  [*] --> テストを書く
  テストを書く : 失敗する
}
Red --> Green : 最小限の実装

state Green {
  [*] --> テストが通る
  テストが通る : 最小限のコード
}
Green --> Refactor : テスト成功

state Refactor {
  [*] --> リファクタリング
  リファクタリング : 設計改善
}
Refactor --> Red : 次のテスト
Refactor --> [*] : 完了

note right of Red
  失敗するテストを
  最初に書く
end note

note right of Green
  テストを通す
  最小限のコードを書く
end note

note right of Refactor
  重複を除去し
  設計を改善する
end note

@enduml
```

## イテレーション開発フロー

### 全体プロセス

```plantuml
@startuml "イテレーション開発プロセス"

start

partition "イテレーション開始" {
}

repeat :TODO確認;
partition "TDD実装サイクル" {
    repeat
      :TODO選択;
      
      repeat
        :失敗テスト作成 (Red);
        :最小実装 (Green);
        :リファクタリング (Refactor);
        :品質チェック;
        if (品質OK?) then (yes)
          :コミット;
        else (no)
          :修正;
        endif
      repeat while (TODO完了?)
      partition "コードレビュー" {
      }
    repeat while (全TODO完了?)
}

if (イテレーション完了?) then (yes)
  partition "受け入れ" {
    partition "ユーザーレビュー" {
    }
    if (受け入れOK?) then (yes)
      partition "ふりかえり" {
      }
    else (no)
      partition "修正対応" {
      }
    endif
  }
else (no)
  partition "設計リファクタリング" {
      partition "アーキテクチャリファクタリング" {
      }
      partition "データモデルリファクタリング" {
      }
      partition "ドメインモデルリファクタリング" {
      }
      partition "UIリファクタリング" {
      }
  }
endif
repeat while (次のTODO?)

stop

@enduml
```

## アプローチ戦略

### インサイドアウト vs アウトサイドイン

```plantuml
@startuml
title 実装アプローチ選択フロー

start

:ユーザーストーリー分析;

if (基本CRUD実装済み?) then (はい)
  if (ドメインロジックが複雑?) then (はい)
    :ドメインモデル中心;
    #lightgreen:アウトサイドイン推奨;
    note right
      UIからスタート
      ドメインロジックを
      段階的に実装
    end note
  else (いいえ)
    :シンプルな機能追加;
    #lightblue:どちらでも可;
  endif
else (いいえ)
  :貧血ドメインモデル;
  #yellow:インサイドアウト推奨;
  note right
    データ層から開始
    基盤を固めて
    上位層へ展開
  end note
endif

if (API実装済み?) then (はい)
  #lightblue:インサイドアウト活用;
  note right
    既存APIを利用し
    内部実装を改善
  end note
else (いいえ)
  #lightgreen:アウトサイドイン活用;
  note right
    UIのニーズから
    API設計を導出
  end note
endif

:アプローチ決定;

stop
@enduml
```

### インサイドアウトアプローチ

```plantuml
@startuml
title インサイドアウトアプローチ

participant "テスト" as test
participant "データベース層" as db
participant "インフラ層" as infra
participant "ドメイン層" as domain
participant "サービス層" as service
participant "プレゼンテーション層" as ui

== Phase 1: データベース ==
test -> db: テーブル定義テスト
activate db
db --> test: スキーマ検証
deactivate db

== Phase 2: インフラストラクチャ ==
test -> infra: リポジトリテスト
activate infra
infra -> db: CRUD操作
db --> infra: 結果
infra --> test: 永続化確認
deactivate infra

== Phase 3: ドメイン ==
test -> domain: ビジネスロジックテスト
activate domain
domain -> infra: データ取得
infra --> domain: エンティティ
domain --> test: ビジネスルール検証
deactivate domain

== Phase 4: サービス ==
test -> service: ユースケーステスト
activate service
service -> domain: ドメイン操作
domain --> service: 結果
service --> test: トランザクション確認
deactivate service

== Phase 5: プレゼンテーション ==
test -> ui: UIテスト
activate ui
ui -> service: API呼び出し
service --> ui: レスポンス
ui --> test: 画面表示確認
deactivate ui

@enduml
```

### アウトサイドインアプローチ

```plantuml
@startuml
title アウトサイドインアプローチ

participant "受け入れテスト" as at
participant "プレゼンテーション層" as ui
participant "サービス層" as service
participant "ドメイン層" as domain
participant "インフラ層" as infra
participant "データベース層" as db

== Phase 1: 受け入れテスト ==
at -> ui: ユーザーシナリオ
activate at
ui --> at: モック応答
deactivate at

== Phase 2: UI実装 ==
at -> ui: UIテスト
activate ui
ui -> service: サービス呼び出し（モック）
service --> ui: モックレスポンス
ui --> at: 画面動作確認
deactivate ui

== Phase 3: サービス実装 ==
at -> service: 統合テスト
activate service
service -> domain: ドメイン呼び出し（モック）
domain --> service: モック結果
service --> at: ビジネスロジック確認
deactivate service

== Phase 4: ドメイン実装 ==
at -> domain: ドメインテスト
activate domain
domain -> infra: リポジトリ呼び出し（モック）
infra --> domain: モックデータ
domain --> at: ビジネスルール確認
deactivate domain

== Phase 5: インフラ実装 ==
at -> infra: 永続化テスト
activate infra
infra -> db: DB操作
db --> infra: 実データ
infra --> at: データ永続化確認
deactivate infra

@enduml
```

## TDD実装の詳細手順

### 1. TODO作成

```markdown
## TODOリストの例

- [ ] ユーザー登録機能
  - [ ] Userエンティティの作成
    - [ ] 必須フィールドの検証
    - [ ] メールアドレスの形式検証
    - [ ] パスワードの強度チェック
  - [ ] UserRepositoryの実装
    - [ ] save()メソッド
    - [ ] findByEmail()メソッド
    - [ ] existsByEmail()メソッド
  - [ ] UserServiceの実装
    - [ ] register()メソッド
    - [ ] 重複チェック
    - [ ] パスワードハッシュ化
  - [ ] UserControllerの実装
    - [ ] POST /users エンドポイント
    - [ ] バリデーション
    - [ ] エラーハンドリング
```

### 2. Red フェーズ（失敗するテストを書く）

```java
// UserTest.java
@Test
void ユーザー作成時に必須フィールドが検証される() {
    // Given
    String email = "test@example.com";
    String password = "SecurePass123!";
    String name = "山田太郎";

    // When
    User user = new User(email, password, name);

    // Then
    assertThat(user.getEmail()).isEqualTo(email);
    assertThat(user.getName()).isEqualTo(name);
    // パスワードはハッシュ化されている
    assertThat(user.getPassword()).isNotEqualTo(password);
}

@Test
void 無効なメールアドレスで例外が発生する() {
    // Given
    String invalidEmail = "invalid-email";

    // When/Then
    assertThrows(IllegalArgumentException.class, () -> {
        new User(invalidEmail, "password", "name");
    });
}
```

### 3. Green フェーズ（最小限の実装）

```java
// User.java
public class User {
    private String email;
    private String password;
    private String name;

    public User(String email, String password, String name) {
        if (!isValidEmail(email)) {
            throw new IllegalArgumentException("無効なメールアドレス");
        }
        this.email = email;
        this.password = hashPassword(password);
        this.name = name;
    }

    private boolean isValidEmail(String email) {
        return email != null && email.contains("@");
    }

    private String hashPassword(String password) {
        // 簡単なハッシュ化の実装（後で改善）
        return "hashed_" + password;
    }

    // getters...
}
```

### 4. Refactor フェーズ（設計改善）

```java
// User.java (リファクタリング後)
public class User {
    private final Email email;
    private final Password password;
    private final Name name;

    public User(String email, String password, String name) {
        this.email = new Email(email);
        this.password = new Password(password);
        this.name = new Name(name);
    }

    // getters...
}

// Email.java (値オブジェクト)
public class Email {
    private static final Pattern VALID_EMAIL_PATTERN =
        Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");

    private final String value;

    public Email(String value) {
        if (value == null || !VALID_EMAIL_PATTERN.matcher(value).matches()) {
            throw new IllegalArgumentException("無効なメールアドレス: " + value);
        }
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

// Password.java (値オブジェクト)
public class Password {
    private static final int MIN_LENGTH = 8;
    private final String hashedValue;

    public Password(String rawPassword) {
        validate(rawPassword);
        this.hashedValue = BCrypt.hashpw(rawPassword, BCrypt.gensalt());
    }

    private void validate(String password) {
        if (password == null || password.length() < MIN_LENGTH) {
            throw new IllegalArgumentException(
                "パスワードは" + MIN_LENGTH + "文字以上必要です"
            );
        }
    }

    public boolean matches(String rawPassword) {
        return BCrypt.checkpw(rawPassword, hashedValue);
    }
}
```

## 品質チェックリスト

### コミット前の必須確認事項

```bash
# 1. テスト実行
./gradlew test  # または npm test

# 2. コードフォーマット
./gradlew spotlessApply  # または npm run format

# 3. 静的解析
./gradlew check  # または npm run lint

# 4. ビルド確認
./gradlew build  # または npm run build

# 5. カバレッジ確認
./gradlew jacocoTestReport  # または npm run test:coverage
```

### 品質基準

| 項目 | 基準 | 必須/推奨 |
|------|------|-----------|
| テストカバレッジ | 80%以上 | 必須 |
| 循環的複雑度 | 10以下 | 必須 |
| メソッドの行数 | 20行以下 | 推奨 |
| クラスの行数 | 200行以下 | 推奨 |
| 重複コード | 0% | 必須 |
| コンパイラ警告 | 0個 | 必須 |
| Linter警告 | 0個 | 必須 |

## コミット規約

### コミットメッセージフォーマット

```
<type>(<scope>): <subject>

<body>

<footer>
```

### タイプ一覧

| タイプ | 説明 | 例 |
|--------|------|-----|
| feat | 新機能追加 | `feat(auth): ユーザー認証機能を追加` |
| fix | バグ修正 | `fix(api): NullPointerExceptionを修正` |
| docs | ドキュメント変更 | `docs(readme): インストール手順を更新` |
| style | コードスタイル変更 | `style: インデントを修正` |
| refactor | リファクタリング | `refactor(user): Userクラスを値オブジェクトに分割` |
| test | テスト追加・修正 | `test(user): ユーザー登録のテストを追加` |
| chore | ビルド・ツール変更 | `chore(gradle): 依存関係を更新` |

### コミット単位

- **1コミット = 1論理的変更**
- TODOリストの項目単位でコミット
- Red-Green-Refactorサイクル完了時にコミット
- ビルドが通る状態でコミット

## テストの種類と戦略

### テストピラミッド

```plantuml
@startuml
skinparam defaultTextAlignment center

rectangle "E2Eテスト\n(10%)" as e2e #LightCoral

rectangle "統合テスト\n(30%)" as integration #LightSalmon

rectangle "単体テスト\n(60%)" as unit #LightGreen

e2e -[hidden]down-> integration
integration -[hidden]down-> unit

note right of e2e
  ユーザーシナリオ
  システム全体の動作確認
  実行時間: 遅い
end note

note right of integration
  モジュール間連携
  API・DB接続テスト
  実行時間: 中程度
end note

note right of unit
  個別機能のテスト
  ビジネスロジック検証
  実行時間: 高速
end note

@enduml
```

### テスト作成のベストプラクティス

#### 1. AAA パターン（Arrange-Act-Assert）

```java
@Test
void 商品の在庫が減少する() {
    // Arrange（準備）
    Product product = new Product("商品A", 10);
    int orderQuantity = 3;

    // Act（実行）
    product.reduceStock(orderQuantity);

    // Assert（検証）
    assertThat(product.getStock()).isEqualTo(7);
}
```

#### 2. テストの命名規則

```java
// パターン1: 日本語での説明的な名前
@Test
void 在庫が不足している場合は注文できない() { }

// パターン2: Given-When-Then形式
@Test
void given在庫10個_when15個注文_then在庫不足例外() { }

// パターン3: メソッド名_条件_期待結果
@Test
void reduceStock_在庫不足_IllegalStateException() { }
```

#### 3. テストデータの準備

```java
// テストフィクスチャの使用
public class UserTestFixture {
    public static User createDefaultUser() {
        return new User(
            "test@example.com",
            "password123",
            "テストユーザー"
        );
    }

    public static User createUserWithEmail(String email) {
        return new User(
            email,
            "password123",
            "テストユーザー"
        );
    }
}
```

## リファクタリングパターン

### よく使うリファクタリング手法

| パターン | 適用場面 | 例 |
|----------|----------|-----|
| メソッド抽出 | 長いメソッドの分割 | 検証ロジックを別メソッドへ |
| 変数抽出 | 複雑な式の簡略化 | 計算結果を一時変数へ |
| クラス抽出 | 責務の分離 | 値オブジェクトの抽出 |
| メソッド移動 | 適切なクラスへの配置 | ユーティリティメソッドの移動 |
| 条件記述の分解 | 複雑な条件式の整理 | if文の条件をメソッド化 |
| ループの分割 | 単一責任の原則適用 | 異なる処理を別ループへ |

### リファクタリング実施のタイミング

1. **Rule of Three（3回ルール）**

     - 1回目: そのまま実装
     - 2回目: 重複に気づくが我慢
     - 3回目: リファクタリング実施

2. **コードの臭い（Code Smells）を検知したとき**

     - 長すぎるメソッド（20行以上）
     - 大きすぎるクラス（200行以上）
     - 長すぎるパラメータリスト（4個以上）
     - データの群れ（同じ引数の組み合わせ）
     - スイッチ文の重複

## 継続的な改善

### ふりかえりでの確認項目

```markdown
## イテレーションふりかえりテンプレート

### Keep（継続すること）
- [ ] TDDサイクルの実践
- [ ] コードレビューの実施
- [ ] 品質基準の維持

### Problem（問題点）
- [ ] テスト作成に時間がかかった箇所
- [ ] リファクタリングが不十分な箇所
- [ ] 技術的負債の発生箇所

### Try（次に試すこと）
- [ ] 新しいテスト手法の導入
- [ ] リファクタリングの自動化
- [ ] 品質メトリクスの改善

### 数値指標
- テストカバレッジ: ___%
- ビルド成功率: ___%
- バグ発生率: ___件/イテレーション
- 平均サイクルタイム: ___時間
```

### 技術的負債の管理

```plantuml
@startuml
title 技術的負債の対処フロー

start

:技術的負債の識別;
note right
  - コードレビューで発見
  - 静的解析ツールの警告
  - パフォーマンス問題
end note

if (緊急度は?) then (高)
  :即座に対処;
  #red:ホットフィックス;
elseif (影響範囲は?) then (大)
  :次イテレーションで対処;
  #yellow:計画的リファクタリング;
else (小)
  :バックログに追加;
  #lightgreen:継続的改善;
endif

:対処結果の記録;
note right
  - 対処内容
  - 所要時間
  - 学習事項
end note

stop

@enduml
```

## まとめ

コーディングとテストは、よいソフトウェアを作るための最も基本的で重要な活動です。TDDを中心としたこれらの規律を守ることで：

1. **品質の作り込み** - バグの早期発見と修正
2. **設計の改善** - 継続的なリファクタリング
3. **変更容易性** - テストによる安全網
4. **ドキュメント性** - テストが仕様書として機能

これらの実践により、「変更を楽に安全にできて役に立つソフトウェア」の実現を目指します。