コーディングとテストガイド¶
概要¶
このガイドは、開発ガイドのコーディングとテスト部分を詳細化したドキュメントです。TDD(テスト駆動開発)を中核とした実装プロセスの具体的な手順と、品質を維持するための規律を定義します。
基本原則¶
TDD(Test-Driven Development)の三つの原則¶
- 失敗するテストを書くまで、プロダクションコードを書いてはならない
- 失敗するテストは、失敗するのに十分なだけ書く
- 現在失敗しているテストを成功させる以上のプロダクションコードを書いてはならない
Red-Green-Refactor サイクル¶
イテレーション開発フロー¶
全体プロセス¶
アプローチ戦略¶
インサイドアウト vs アウトサイドイン¶
インサイドアウトアプローチ¶
アウトサイドインアプローチ¶
TDD実装の詳細手順¶
1. TODO作成¶
## TODOリストの例
- [ ] ユーザー登録機能
- [ ] Userエンティティの作成
- [ ] 必須フィールドの検証
- [ ] メールアドレスの形式検証
- [ ] パスワードの強度チェック
- [ ] UserRepositoryの実装
- [ ] save()メソッド
- [ ] findByEmail()メソッド
- [ ] existsByEmail()メソッド
- [ ] UserServiceの実装
- [ ] register()メソッド
- [ ] 重複チェック
- [ ] パスワードハッシュ化
- [ ] UserControllerの実装
- [ ] POST /users エンドポイント
- [ ] バリデーション
- [ ] エラーハンドリング
2. Red フェーズ(失敗するテストを書く)¶
// 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 フェーズ(最小限の実装)¶
// 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 フェーズ(設計改善)¶
// 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);
}
}
品質チェックリスト¶
コミット前の必須確認事項¶
# 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サイクル完了時にコミット
- ビルドが通る状態でコミット
テストの種類と戦略¶
テストピラミッド¶
テスト作成のベストプラクティス¶
1. AAA パターン(Arrange-Act-Assert)¶
@Test
void 商品の在庫が減少する() {
// Arrange(準備)
Product product = new Product("商品A", 10);
int orderQuantity = 3;
// Act(実行)
product.reduceStock(orderQuantity);
// Assert(検証)
assertThat(product.getStock()).isEqualTo(7);
}
2. テストの命名規則¶
// パターン1: 日本語での説明的な名前
@Test
void 在庫が不足している場合は注文できない() { }
// パターン2: Given-When-Then形式
@Test
void given在庫10個_when15個注文_then在庫不足例外() { }
// パターン3: メソッド名_条件_期待結果
@Test
void reduceStock_在庫不足_IllegalStateException() { }
3. テストデータの準備¶
// テストフィクスチャの使用
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文の条件をメソッド化 |
| ループの分割 | 単一責任の原則適用 | 異なる処理を別ループへ |
リファクタリング実施のタイミング¶
-
Rule of Three(3回ルール)
-
1回目: そのまま実装
- 2回目: 重複に気づくが我慢
-
3回目: リファクタリング実施
-
コードの臭い(Code Smells)を検知したとき
-
長すぎるメソッド(20行以上)
- 大きすぎるクラス(200行以上)
- 長すぎるパラメータリスト(4個以上)
- データの群れ(同じ引数の組み合わせ)
- スイッチ文の重複
継続的な改善¶
ふりかえりでの確認項目¶
## イテレーションふりかえりテンプレート
### Keep(継続すること)
- [ ] TDDサイクルの実践
- [ ] コードレビューの実施
- [ ] 品質基準の維持
### Problem(問題点)
- [ ] テスト作成に時間がかかった箇所
- [ ] リファクタリングが不十分な箇所
- [ ] 技術的負債の発生箇所
### Try(次に試すこと)
- [ ] 新しいテスト手法の導入
- [ ] リファクタリングの自動化
- [ ] 品質メトリクスの改善
### 数値指標
- テストカバレッジ: ___%
- ビルド成功率: ___%
- バグ発生率: ___件/イテレーション
- 平均サイクルタイム: ___時間
技術的負債の管理¶
まとめ¶
コーディングとテストは、よいソフトウェアを作るための最も基本的で重要な活動です。TDDを中心としたこれらの規律を守ることで:
- 品質の作り込み - バグの早期発見と修正
- 設計の改善 - 継続的なリファクタリング
- 変更容易性 - テストによる安全網
- ドキュメント性 - テストが仕様書として機能
これらの実践により、「変更を楽に安全にできて役に立つソフトウェア」の実現を目指します。