jpskill.com
📦 その他 コミュニティ

allra-test-writing

Allraのバックエンドテスト作成において、標準的なテストコードの書き方、適切なテストヘルパーの選択、Fixture Monkeyを使ったテストデータ生成、テストカバレッジの検証などを支援するSkill。

📜 元の英語説明(参考)

Allra 백엔드 테스트 작성 표준. Use when writing test code, choosing test helpers, generating test data with Fixture Monkey, or verifying test coverage.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Allraのバックエンドテスト作成において、標準的なテストコードの書き方、適切なテストヘルパーの選択、Fixture Monkeyを使ったテストデータ生成、テストカバレッジの検証などを支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o allra-test-writing.zip https://jpskill.com/download/17138.zip && unzip -o allra-test-writing.zip && rm allra-test-writing.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17138.zip -OutFile "$d\allra-test-writing.zip"; Expand-Archive "$d\allra-test-writing.zip" -DestinationPath $d -Force; ri "$d\allra-test-writing.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して allra-test-writing.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → allra-test-writing フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Allra テスト作成標準

Allra バックエンドチームのテスト作成標準を定義します。テストヘルパーの選択、Fixture Monkey データ生成、Given-When-Then パターン、AssertJ 検証を含みます。

プロジェクト基本情報

このガイドは、次の環境を基準に作成されました。

  • Java: 17 以上
  • Spring Boot: 3.2 以上
  • Testing Framework: JUnit 5
  • Assertion Library: AssertJ
  • Mocking: Mockito
  • Test Data: Fixture Monkey (選択事項)
  • Container: Testcontainers (選択事項)

参考: プロジェクトごとに使用するライブラリやバージョンが異なる場合があります。プロジェクトに合わせて調整して使用してください。

テストヘルパー選択ガイド

注意: 下記のテストヘルパーは Allra 標準テンプレートで提供されます。プロジェクトにこれらのヘルパーがない場合は、Spring Boot 基本テストアノテーション(@SpringBootTest, @DataJpaTest, @WebMvcTest など)を直接使用し、このガイドのテストパターンと原則を同様に適用します。

ヘルパー タグ 用途 重さ いつ?
IntegrationTest Integration 複数のサービス統合 🔴 重い 全体ワークフロー
RdbTest RDB Repository, QueryDSL 🟡 中間 クエリ検証
ControllerTest Controller API エンドポイント 🟢 軽い REST API 検証
RedisTest Redis Redis キャッシュ 🟢 軽い キャッシュ検証
MockingUnitTest MockingUnit Service 単位 🟢 非常に軽い ビジネスロジック
PojoUnitTest PojoUnit ドメインロジック 🟢 非常に軽い 純粋な Java

選択フロー

API エンドポイント? → ControllerTest
複数のサービス統合? → IntegrationTest
Repository/QueryDSL? → RdbTest
Redis キャッシュ? → RedisTest
Service ロジック (Mock)? → MockingUnitTest
ドメインロジック (POJO)? → PojoUnitTest

🎯 Mock vs Integration 選択基準 (重要!)

原則: 基本は MockingUnitTest、どうしても必要な時のみ IntegrationTest

目標: IntegrationTest の割合を 5% 以下に維持

意思決定フローチャート

┌─────────────────────────────────┐
│ 何をテストしようとしているか?    │
└────────────┬────────────────────┘
             │
    ┌────────▼────────┐
    │ ドメインロジックのみ?  │ ──Yes──> PojoUnitTest
    └────────┬────────┘
             │ No
    ┌────────▼─────────────────────┐
    │ Repository/QueryDSL クエリ?   │ ──Yes──> RdbTest
    └────────┬────────┘
             │ No
    ┌────────▼─────────────────────┐
    │ API エンドポイント応答/検証?   │ ──Yes──> ControllerTest
    └────────┬────────┘
             │ No
    ┌────────▼─────────────────────────────┐
    │ Service ビジネスロジック検証?         │
    └────────┬─────────────────────────────┘
             │
    ┌────────▼──────────────────────────────────────────┐
    │ 次のいずれかに該当するか?                      │
    │                                                   │
    │ 1. 💰 金銭処理 (入金/出金/振込/払い戻し)            │
    │ 2. 🔄 トランザクションロールバックが重要なワークフロー           │
    │ 3. 📊 複数のテーブルデータ整合性検証             │
    │ 4. 🔐 実際の DB 制約条件検証必須                 │
    │ 5. 📝 複雑な状態遷移 (3段階以上)              │
    │ 6. 🎯 イベント発行/リスナー統合検証               │
    │ 7. 🤝 3つ以上のサービス必須協力                  │
    └────┬──────────────────────────────────────┬────────┘
         │ Yes                                  │ No
         │                                      │
    ┌────▼────────────┐              ┌─────────▼──────────┐
    │ IntegrationTest │              │ MockingUnitTest    │
    │ (最小化)        │              │ (基本選択)       │
    └─────────────────┘              └────────────────────┘

IntegrationTest が必要な具体的なケース

✅ 1. 金銭処理 (入金/出金/振込/払い戻し)

理由: お金が関連するロジックは実際の DB トランザクション動作検証が必須

// 例: ファンディング申請 (FsData → FsPayment → PointUsage → UserAccount 連携)
@DisplayName("ファンディング申請時に金額差し引きおよび決済生成")
class ApplyServiceIntegrationTest extends IntegrationTest {

    @Test
    @Transactional
    void apply_DecreasesAmount_Success() {
        // given: ユーザー残高 100万円
        User user = createUserWithBalance(1_000_000);

        // when: 50万円ファンディング申請
        applyService.apply(new ApplyRequest(user.getId(), 500_000));

        // then: 実際の DB で残高 50万円を確認
        User updated = userRepository.findById(user.getId()).get();
        assertThat(updated.getBalance()).isEqualTo(500_000);

        // then: FsPayment 生成確認
        FsPayment payment = fsPaymentRepository.findByUserId(user.getId()).get();
        assertThat(payment.getAmount()).isEqualTo(500_000);
    }
}

✅ 2. トランザクションロールバックが重要なワークフロー

理由: 失敗時にすべての作業が原子的にロールバックされなければならない

// 例: 決済失敗時に全体ロールバック
@Test
@DisplayName("決済失敗時に申請データもロールバック")
void apply_PaymentFails_RollbackAll() {
    // given
    User user = createUser();
    mockPaymentGateway_ToFail(); // 外部決済は Mock で

    // when & then
    assertThatThrownBy(() -> applyService.apply(request))
        .isInstanceOf(PaymentException.class);

    // then: DB にどんなデータも保存されない
    assertThat(fsDataRepository.findAll()).isEmpty();
    assertThat(fsPaymentRepository.findAll()).isEmpty();
}

参考: 外部連携(決済ゲートウェイ、外部 API)は @MockBean で処理

✅ 3. 複数のテーブルデータ整合性検証

理由: 関連するすべてのテーブルの状態が一貫して維持されているか確認

// 例: 契約生成時に UserAccount, Contract, FsData すべて生成
@Test
@DisplayName("新規契約時に関連テーブルすべて生成")
void createContract_CreatesAllRelatedData() {
    // when
    contractService.createContract(userId, contractType);

    // then: 3 つのテーブルすべてにデータが存在
    assertThat(userAccountRepository.findByUserId(userId)).isPresent();
    assertThat(contractRepository.findByUserId(userId)).isPresent();
    assertThat(fsDataRepository.findByUserId(userId)).isPresent();
}

✅ 4. 実際の DB 制約条件検証

理由: Unique, FK, Check 制約条件は実際の DB でのみ確認可能

// 例: 重複口座登録防止
@Test
@DisplayName("同一口座番号重複登録時に例外")
void registerAccount_Duplicate_ThrowsException() {
    // given
    userAccountRepository.save(new UserAccount(userId, "123-456-789"));

    // when & then: Unique 制約条件違反
    assertThatThrownBy(() ->
        userAccountRepository.save(new UserAccount(userId, "123-456-789"))
    ).isInstanceOf(DataIntegrityViolationException.class);
}

✅ 5. 複雑な状態遷移 (3段階以上)

理由: 状態変化の流れを実際のシナリオ通りに検証

// 例: 契約状態遷移 (申請 → 審査 → 承認 → 完了)
@Test
@DisplayName("契約ワークフロー全体検証")
void contractWorkflow_FullCycle() {
    // given: 申請
    Contract contract = contractService.create(userId);
    assertThat(contract.getStatus()).isEqualTo(ContractStatus.PENDING);

    // when: 審査
    contractService.review(contract.getId());
    // then
    Contract reviewed = contractRepository.findById(contract.getId()).get();
    assertThat(reviewed.getStatus()).isEqualTo(Con
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Allra Test Writing Standards

Allra 백엔드 팀의 테스트 작성 표준을 정의합니다. 테스트 헬퍼 선택, Fixture Monkey 데이터 생성, Given-When-Then 패턴, AssertJ 검증을 포함합니다.

프로젝트 기본 정보

이 가이드는 다음 환경을 기준으로 작성되었습니다:

  • Java: 17 이상
  • Spring Boot: 3.2 이상
  • Testing Framework: JUnit 5
  • Assertion Library: AssertJ
  • Mocking: Mockito
  • Test Data: Fixture Monkey (선택 사항)
  • Container: Testcontainers (선택 사항)

참고: 프로젝트별로 사용하는 라이브러리나 버전이 다를 수 있습니다. 프로젝트에 맞게 조정하여 사용하세요.

테스트 헬퍼 선택 가이드

주의: 아래 테스트 헬퍼는 Allra 표준 템플릿에서 제공됩니다. 프로젝트에 이러한 헬퍼가 없는 경우, Spring Boot 기본 테스트 어노테이션(@SpringBootTest, @DataJpaTest, @WebMvcTest 등)을 직접 사용하되, 이 가이드의 테스트 패턴과 원칙은 동일하게 적용합니다.

헬퍼 태그 용도 무게 언제?
IntegrationTest Integration 여러 서비스 통합 🔴 무거움 전체 워크플로우
RdbTest RDB Repository, QueryDSL 🟡 중간 쿼리 검증
ControllerTest Controller API 엔드포인트 🟢 가벼움 REST API 검증
RedisTest Redis Redis 캐싱 🟢 가벼움 캐시 검증
MockingUnitTest MockingUnit Service 단위 🟢 매우 가벼움 비즈니스 로직
PojoUnitTest PojoUnit 도메인 로직 🟢 매우 가벼움 순수 자바

선택 플로우

API 엔드포인트? → ControllerTest
여러 서비스 통합? → IntegrationTest
Repository/QueryDSL? → RdbTest
Redis 캐싱? → RedisTest
Service 로직 (Mock)? → MockingUnitTest
도메인 로직 (POJO)? → PojoUnitTest

🎯 Mock vs Integration 선택 기준 (중요!)

원칙: 기본은 MockingUnitTest, 꼭 필요할 때만 IntegrationTest

목표: IntegrationTest 비율 5% 이하 유지

의사결정 플로우차트

┌─────────────────────────────────┐
│ 무엇을 테스트하려고 하는가?    │
└────────────┬────────────────────┘
             │
    ┌────────▼────────┐
    │ 도메인 로직만?  │ ──Yes──> PojoUnitTest
    └────────┬────────┘
             │ No
    ┌────────▼─────────────────────┐
    │ Repository/QueryDSL 쿼리?   │ ──Yes──> RdbTest
    └────────┬─────────────────────┘
             │ No
    ┌────────▼─────────────────────┐
    │ API 엔드포인트 응답/검증?   │ ──Yes──> ControllerTest
    └────────┬─────────────────────┘
             │ No
    ┌────────▼─────────────────────────────┐
    │ Service 비즈니스 로직 검증?         │
    └────────┬─────────────────────────────┘
             │
    ┌────────▼──────────────────────────────────────────┐
    │ 다음 중 하나라도 해당하는가?                      │
    │                                                   │
    │ 1. 💰 금전 처리 (입금/출금/이체/환불)            │
    │ 2. 🔄 트랜잭션 롤백이 중요한 워크플로우           │
    │ 3. 📊 여러 테이블 데이터 정합성 검증             │
    │ 4. 🔐 실제 DB 제약조건 검증 필수                 │
    │ 5. 📝 복잡한 상태 전이 (3단계 이상)              │
    │ 6. 🎯 이벤트 발행/리스너 통합 검증               │
    │ 7. 🤝 3개 이상 서비스 필수 협력                  │
    └────┬──────────────────────────────────────┬────────┘
         │ Yes                                  │ No
         │                                      │
    ┌────▼────────────┐              ┌─────────▼──────────┐
    │ IntegrationTest │              │ MockingUnitTest    │
    │ (최소화)        │              │ (기본 선택)       │
    └─────────────────┘              └────────────────────┘

IntegrationTest가 필요한 구체적인 케이스

✅ 1. 금전 처리 (입금/출금/이체/환불)

이유: 돈이 관련된 로직은 실제 DB 트랜잭션 동작 검증 필수

// 예시: 펀딩 신청 (FsData → FsPayment → PointUsage → UserAccount 연계)
@DisplayName("펀딩 신청 시 금액 차감 및 결제 생성")
class ApplyServiceIntegrationTest extends IntegrationTest {

    @Test
    @Transactional
    void apply_DecreasesAmount_Success() {
        // given: 사용자 잔액 100만원
        User user = createUserWithBalance(1_000_000);

        // when: 50만원 펀딩 신청
        applyService.apply(new ApplyRequest(user.getId(), 500_000));

        // then: 실제 DB에서 잔액 50만원 확인
        User updated = userRepository.findById(user.getId()).get();
        assertThat(updated.getBalance()).isEqualTo(500_000);

        // then: FsPayment 생성 확인
        FsPayment payment = fsPaymentRepository.findByUserId(user.getId()).get();
        assertThat(payment.getAmount()).isEqualTo(500_000);
    }
}

✅ 2. 트랜잭션 롤백이 중요한 워크플로우

이유: 실패 시 모든 작업이 원자적으로 롤백되어야 함

// 예시: 결제 실패 시 전체 롤백
@Test
@DisplayName("결제 실패 시 신청 데이터도 롤백")
void apply_PaymentFails_RollbackAll() {
    // given
    User user = createUser();
    mockPaymentGateway_ToFail(); // 외부 결제는 Mock으로

    // when & then
    assertThatThrownBy(() -> applyService.apply(request))
        .isInstanceOf(PaymentException.class);

    // then: DB에 어떤 데이터도 저장되지 않음
    assertThat(fsDataRepository.findAll()).isEmpty();
    assertThat(fsPaymentRepository.findAll()).isEmpty();
}

참고: 외부 연동(결제 게이트웨이, 외부 API)은 @MockBean으로 처리

✅ 3. 여러 테이블 데이터 정합성 검증

이유: 관련된 모든 테이블의 상태가 일관되게 유지되는지 확인

// 예시: 계약 생성 시 UserAccount, Contract, FsData 모두 생성
@Test
@DisplayName("신규 계약 시 관련 테이블 모두 생성")
void createContract_CreatesAllRelatedData() {
    // when
    contractService.createContract(userId, contractType);

    // then: 3개 테이블 모두 데이터 존재
    assertThat(userAccountRepository.findByUserId(userId)).isPresent();
    assertThat(contractRepository.findByUserId(userId)).isPresent();
    assertThat(fsDataRepository.findByUserId(userId)).isPresent();
}

✅ 4. 실제 DB 제약조건 검증

이유: Unique, FK, Check 제약조건은 실제 DB에서만 확인 가능

// 예시: 중복 계좌 등록 방지
@Test
@DisplayName("동일 계좌번호 중복 등록 시 예외")
void registerAccount_Duplicate_ThrowsException() {
    // given
    userAccountRepository.save(new UserAccount(userId, "123-456-789"));

    // when & then: Unique 제약조건 위반
    assertThatThrownBy(() ->
        userAccountRepository.save(new UserAccount(userId, "123-456-789"))
    ).isInstanceOf(DataIntegrityViolationException.class);
}

✅ 5. 복잡한 상태 전이 (3단계 이상)

이유: 상태 변화 흐름을 실제 시나리오대로 검증

// 예시: 계약 상태 전이 (신청 → 심사 → 승인 → 완료)
@Test
@DisplayName("계약 워크플로우 전체 검증")
void contractWorkflow_FullCycle() {
    // given: 신청
    Contract contract = contractService.create(userId);
    assertThat(contract.getStatus()).isEqualTo(ContractStatus.PENDING);

    // when: 심사
    contractService.review(contract.getId());
    // then
    Contract reviewed = contractRepository.findById(contract.getId()).get();
    assertThat(reviewed.getStatus()).isEqualTo(ContractStatus.REVIEWED);

    // when: 승인
    contractService.approve(contract.getId());
    // then
    Contract approved = contractRepository.findById(contract.getId()).get();
    assertThat(approved.getStatus()).isEqualTo(ContractStatus.APPROVED);
}

✅ 6. 이벤트 발행/리스너 통합 검증

이유: 이벤트가 실제로 발행되고 리스너가 동작하는지 확인

// 예시: 계약 완료 이벤트 → 알림 발송
@Test
@DisplayName("계약 완료 시 알림 이벤트 발행")
void completeContract_PublishesEvent() {
    // given
    Contract contract = createContract(userId);

    // when
    contractService.complete(contract.getId());

    // then: 실제로 알림이 발송되었는가? (외부 알림은 @MockBean)
    verify(notificationService).sendContractCompleteNotification(userId);
}

✅ 7. 3개 이상 서비스가 필수적으로 협력

이유: 서비스 간 상호작용을 실제 환경에서 검증

// 예시: 주문 생성 → 재고 차감 → 결제 → 알림
@Test
@DisplayName("주문 생성 워크플로우")
void createOrder_FullWorkflow() {
    // given
    Product product = createProductWithStock(100);

    // when
    orderService.createOrder(userId, product.getId(), 10);

    // then: 재고 차감
    Product updated = productRepository.findById(product.getId()).get();
    assertThat(updated.getStock()).isEqualTo(90);

    // then: 결제 생성
    Payment payment = paymentRepository.findByUserId(userId).get();
    assertThat(payment.getStatus()).isEqualTo(PaymentStatus.COMPLETED);
}

MockingUnitTest로 충분한 케이스

✅ 대부분의 Service 로직

  • 단순 조회 (findById, findAll)
  • 데이터 변환/계산
  • 검증 로직 (validation)
  • 단일 엔티티 CRUD
  • 비즈니스 규칙 검증
// 예시: 할인율 계산 로직 (Mock으로 충분)
@ExtendWith(MockitoExtension.class)
class DiscountServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private DiscountService discountService;

    @Test
    @DisplayName("VIP 회원 10% 할인 계산")
    void calculateDiscount_VipUser_10Percent() {
        // given
        User vipUser = User.builder().grade("VIP").build();
        when(userRepository.findById(1L)).thenReturn(Optional.of(vipUser));

        // when
        BigDecimal discount = discountService.calculateDiscount(1L, new BigDecimal("10000"));

        // then
        assertThat(discount).isEqualByComparingTo(new BigDecimal("1000"));
    }
}

외부 연동 처리 원칙

중요: IntegrationTest에서도 외부 시스템은 @MockBean으로 처리

@SpringBootTest
class PaymentServiceIntegrationTest extends IntegrationTest {

    @Autowired
    private PaymentService paymentService;

    @MockBean // 외부 결제 게이트웨이는 Mock
    private ExternalPaymentGateway externalPaymentGateway;

    @MockBean // 외부 알림 서비스는 Mock
    private ExternalNotificationService notificationService;

    @Test
    @DisplayName("결제 성공 시 내부 데이터 정합성 검증")
    void processPayment_Success() {
        // given: 외부 결제는 성공으로 Mock
        when(externalPaymentGateway.charge(any()))
            .thenReturn(new PaymentResult("SUCCESS", "tx-123"));

        // when: 실제 내부 로직 검증
        paymentService.processPayment(userId, amount);

        // then: 내부 DB 상태 확인
        Payment payment = paymentRepository.findByUserId(userId).get();
        assertThat(payment.getStatus()).isEqualTo(PaymentStatus.COMPLETED);
        assertThat(payment.getExternalTxId()).isEqualTo("tx-123");
    }
}

테스트 전략 요약

테스트 유형 목표 비율 실행 속도 주요 사용처
PojoUnitTest 30% ⚡️ 0.01초 도메인 로직, 유틸리티
MockingUnitTest 50% ⚡️ 0.1초 Service 비즈니스 로직
ControllerTest 10% 🟡 0.5초 API 검증
RdbTest 5% 🟡 1초 복잡한 쿼리 검증
IntegrationTest 5% 🔴 5초 금전/트랜잭션/워크플로우

빠른 판단 체크리스트

새로운 테스트를 작성할 때 다음을 확인하세요:

□ 돈이 관련되어 있나요? (입금/출금/결제)
  → Yes: IntegrationTest

□ 실패 시 데이터 롤백이 중요한가요?
  → Yes: IntegrationTest

□ 3개 이상 테이블의 정합성을 확인해야 하나요?
  → Yes: IntegrationTest

□ DB 제약조건(Unique/FK)이 핵심인가요?
  → Yes: IntegrationTest

□ 복잡한 상태 전이(3단계+)를 검증하나요?
  → Yes: IntegrationTest

□ 이벤트 발행/리스너를 검증하나요?
  → Yes: IntegrationTest

□ 3개 이상 서비스가 협력하나요?
  → Yes: IntegrationTest

모두 No → MockingUnitTest 사용

테스트 헬퍼 구조

IntegrationTest - 통합 테스트

@Tag("Integration")
@SpringBootTest
public abstract class IntegrationTest {
    // 전체 Spring Context, Testcontainers 활용
}

언제: 여러 서비스 협력, 실제 DB/외부 시스템 필요 주의: 가장 무거움, 외부 API는 @MockBean 사용

RdbTest - Repository 테스트

@Tag("RDB")
@DataJpaTest
public abstract class RdbTest {}

언제: Repository CRUD, QueryDSL 쿼리, N+1 문제 검증

ControllerTest - API 테스트

@Tag("Controller")
@WebMvcTest(TargetController.class)
public abstract class ControllerTest {
    @Autowired
    protected MockMvc mockMvc;
}

언제: API 엔드포인트, HTTP Status, 입력 검증 주의: Service는 @MockBean 필수

RedisTest - Redis 테스트

@Tag("Redis")
@DataRedisTest
public abstract class RedisTest {}

언제: Redis 캐싱, 세션 저장소 검증

MockingUnitTest - Service 단위 테스트

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;
}

언제: Service 로직 단위 테스트, 빠른 테스트 주의: Spring Context 없음, @Autowired 불가

PojoUnitTest - 도메인 로직 테스트

class UserTest {
    @Test
    void activate_Success() {
        // 순수 자바 로직 테스트
    }
}

언제: 도메인 엔티티, VO, 유틸리티 클래스


Fixture Monkey - 테스트 데이터 생성

의존성 설정

// Gradle
testImplementation 'com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.13'
<!-- Maven -->
<dependency>
    <groupId>com.navercorp.fixturemonkey</groupId>
    <artifactId>fixture-monkey-starter</artifactId>
    <version>1.0.13</version>
    <scope>test</scope>
</dependency>

사용법

import static {your.package}.fixture.FixtureFactory.FIXTURE_MONKEY;

// 단순 생성
User user = FIXTURE_MONKEY.giveMeOne(User.class);

// 특정 필드 지정
User user = FIXTURE_MONKEY.giveMeBuilder(User.class)
    .set("email", "test@example.com")
    .set("active", true)
    .sample();

// 여러 개 생성
List<User> users = FIXTURE_MONKEY.giveMe(User.class, 10);

Given-When-Then 패턴 (필수)

모든 테스트는 Given-When-Then 패턴 필수

@Test
@DisplayName("사용자 생성 - 성공")
void createUser_Success() {
    // given - 테스트 준비
    UserRequest request = new UserRequest("test@example.com", "password");
    User savedUser = FIXTURE_MONKEY.giveMeOne(User.class);
    when(userRepository.save(any())).thenReturn(savedUser);

    // when - 실제 실행
    UserResponse response = userService.createUser(request);

    // then - 검증
    assertThat(response).isNotNull();
    verify(userRepository, times(1)).save(any());
}

AssertJ 검증 패턴

// 단일 값
assertThat(response).isNotNull();
assertThat(response.userId()).isEqualTo(1L);

// 컬렉션
assertThat(users).hasSize(3);
assertThat(users).extracting(User::getEmail)
    .containsExactlyInAnyOrder("a@test.com", "b@test.com");

// Boolean
assertThat(user.isActive()).isTrue();

// 예외
assertThatThrownBy(() -> userService.findById(999L))
    .isInstanceOf(BusinessException.class)
    .hasMessageContaining("USER_NOT_FOUND");

// Optional
assertThat(result).isPresent();
assertThat(result.get().getName()).isEqualTo("홍길동");

Mockito 패턴

Mock 설정

// 반환값
when(userRepository.findById(1L)).thenReturn(Optional.of(user));

// void 메서드
doNothing().when(emailService).sendEmail(any());

// 예외 발생
when(userRepository.findById(999L))
    .thenThrow(new BusinessException(ErrorCode.USER_NOT_FOUND));

Mock 호출 검증

// 호출 횟수
verify(userRepository, times(1)).findById(1L);
verify(userRepository, never()).delete(any());

// 인자 검증
verify(userRepository).save(argThat(user ->
    user.getEmail().equals("test@example.com")
));

테스트 명명 규칙

클래스

class ApplyServiceIntegrationTest extends IntegrationTest  // Integration
class UserRepositoryTest extends RdbTest                   // Repository
class UserControllerTest extends ControllerTest            // Controller
class UserServiceTest                                      // Service Unit
class UserTest                                             // Domain

메서드

// 패턴: {메서드명}_{시나리오}_{예상결과}
@Test
@DisplayName("사용자 생성 - 성공")
void createUser_ValidRequest_Success()

@Test
@DisplayName("사용자 조회 - 사용자 없음")
void findById_UserNotFound_ThrowsException()

테스트 예시

Controller 테스트

@DisplayName("User -> UserController 테스트")
@WebMvcTest(UserController.class)
class UserControllerTest extends ControllerTest {

    @MockBean
    private UserService userService;

    @Test
    @DisplayName("사용자 조회 API - 성공")
    void getUser_Success() throws Exception {
        // given
        Long userId = 1L;
        UserResponse response = new UserResponse(userId, "test@example.com");
        when(userService.findById(userId)).thenReturn(response);

        // when & then
        mockMvc.perform(get("/api/v1/users/{id}", userId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.userId").value(userId));
    }
}

Service 단위 테스트

@ExtendWith(MockitoExtension.class)
@DisplayName("User -> UserService 단위 테스트")
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("사용자 조회 - 성공")
    void findById_Success() {
        // given
        Long userId = 1L;
        User user = FIXTURE_MONKEY.giveMeBuilder(User.class)
            .set("id", userId)
            .sample();
        when(userRepository.findById(userId)).thenReturn(Optional.of(user));

        // when
        UserResponse response = userService.findById(userId);

        // then
        assertThat(response).isNotNull();
        assertThat(response.userId()).isEqualTo(userId);
        verify(userRepository, times(1)).findById(userId);
    }
}

Repository 테스트

@DisplayName("User -> UserRepository 테스트")
class UserRepositoryTest extends RdbTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    @DisplayName("활성 사용자 조회 - 성공")
    void findActiveUsers_Success() {
        // given
        User active = FIXTURE_MONKEY.giveMeBuilder(User.class)
            .set("active", true)
            .sample();
        userRepository.save(active);

        // when
        List<UserDto> result = userRepository.findActiveUsers();

        // then
        assertThat(result).hasSize(1);
        assertThat(result).extracting(UserDto::email)
            .contains(active.getEmail());
    }
}

When to Use This Skill

이 skill은 다음 상황에서 자동으로 적용됩니다:

  • 테스트 파일 생성 또는 수정
  • 테스트 헬퍼 선택 (IntegrationTest vs MockingUnitTest 판단)
  • 테스트 데이터 생성 (Fixture Monkey 사용)
  • Given-When-Then 패턴 적용
  • AssertJ 검증 코드 작성
  • Mockito Mock 설정 및 검증

특히 중요: 새로운 Service 테스트 작성 시 먼저 "Mock vs Integration 선택 기준"을 확인하세요!


Checklist

테스트 코드 작성 시 확인사항:

모든 테스트 공통

  • [ ] Given-When-Then 패턴을 따르는가?
  • [ ] @DisplayName으로 테스트 의도가 명확한가?
  • [ ] AssertJ로 검증하는가?
  • [ ] 메서드명이 메서드_시나리오_결과 패턴인가?

테스트 헬퍼 선택 (가장 먼저 확인!)

  • [ ] 금전 처리(입금/출금/결제) 또는 트랜잭션 롤백 검증이 필요한가? → IntegrationTest
  • [ ] 3개 이상 테이블 정합성 또는 DB 제약조건 검증이 필요한가? → IntegrationTest
  • [ ] 복잡한 상태 전이(3단계+) 또는 이벤트 발행/리스너 검증이 필요한가? → IntegrationTest
  • [ ] 3개 이상 서비스가 협력하는가? → IntegrationTest
  • [ ] 위 조건 모두 해당 안됨 → MockingUnitTest 사용

IntegrationTest

  • [ ] 위 선택 기준 중 하나 이상에 해당하는가?
  • [ ] 외부 API는 @MockBean으로 처리했는가?
  • [ ] 정말 IntegrationTest가 필요한지 다시 한번 검토했는가?

RdbTest

  • [ ] Repository/QueryDSL 테스트만 포함하는가?
  • [ ] N+1 문제를 검증했는가?

ControllerTest

  • [ ] @WebMvcTest(TargetController.class)를 명시했는가?
  • [ ] Service는 @MockBean으로 처리했는가?
  • [ ] HTTP Status Code를 검증하는가?

MockingUnitTest

  • [ ] @Mock으로 의존성, @InjectMocks로 테스트 대상을 주입했는가?
  • [ ] verify()로 Mock 호출을 검증했는가?

PojoUnitTest

  • [ ] 도메인 로직만 테스트하는가?
  • [ ] 외부 의존성이 없는가?

테스트 실행 명령어

Gradle

./gradlew test                                    # 전체 테스트
./gradlew test --tests * -Dtest.tags=Integration # 태그별 실행
./gradlew test --tests UserServiceTest            # 특정 클래스

Maven

./mvnw test                        # 전체 테스트
./mvnw test -Dgroups=Integration   # 태그별 실행
./mvnw test -Dtest=UserServiceTest # 특정 클래스

테스트 품질 기준

  1. 커버리지: 핵심 비즈니스 로직 70% 이상
  2. 격리성: 각 테스트가 독립적으로 실행 가능
  3. 속도: 단위 테스트 1초 이내, 통합 테스트 5초 이내
  4. 명확성: 테스트 이름만으로 의도 파악 가능
  5. 신뢰성: 같은 입력에 항상 같은 결과