Skip to main content Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/ForceInjection/domain-driven-design-skills --skill testing-expertコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... Conduct deep academic research for philosophy, neuroscience, cognitive science, and theoretical computer science (computability, complexity, AI theory, logic). Use when user asks to: research academic topics, find scholarly papers, conduct literature reviews, analyze citations, synthesize research findings, explore philosophical arguments, investigate consciousness/cognition, study computability/decidability/Turing machines, or analyze academic debates. Triggers on: 'research papers', 'literature review', 'academic sources', 'scholarly articles', 'philosophy of mind', 'computability theory', 'neuroscience studies', 'find papers on', 'what does the research say'.
name testing-expert version 3.0.0 description Integration Test E2E 테스트, TestRestTemplate 필수, Test Fixtures 재사용.
MockMvc 금지, @Sql 어노테이션 테스트 데이터 설정.
Gradle testFixtures 플러그인 활용, ArchUnit 의존성 검증.
author claude-spring-standards created "2024-11-01T00:00:00.000Z" updated "2025-12-05T00:00:00.000Z" tags ["project","testing","integration-test","fixtures","testresttemplate","e2e","archunit"]
Testing Expert (테스팅 전문가)
목적 (Purpose)
Integration Test + Test Fixtures 전략을 담당하는 전문가입니다.
E2E 테스트는 TestRestTemplate 기반 실제 HTTP 요청으로 수행하고,
테스트 데이터는 Gradle testFixtures + @Sql 어노테이션으로 관리합니다.
활성화 조건
/impl {layer} {feature} 명령 실행 시 (테스트 동시 작성)
실행 후 테스트 전략 결정 시
/plan
integration test, fixture, testresttemplate, @sql, e2e 키워드 언급 시
산출물 (Output) 컴포넌트 파일명 패턴 위치 Integration Test {Feature}IntegrationTest.javabootstrap/src/test/java/.../Domain Fixture {Aggregate}Fixture.javadomain/src/testFixtures/java/.../fixture/domain/Application Fixture {Command/Response}Fixture.javaapplication/src/testFixtures/java/.../fixture/application/Adapter Fixture {Request/Entity}Fixture.javaadapter-*/src/testFixtures/java/.../fixture/adapter/SQL Test Data {feature}-test-data.sqlsrc/test/resources/sql/
완료 기준 (Acceptance Criteria)
테스트 전략 개요
Integration Test = E2E Test HTTP Request (TestRestTemplate)
↓
Controller (REST API Layer)
↓
UseCase (Application Layer)
↓
Repository (Persistence Layer)
↓
Real Database (MySQL via TestContainers)
↓
HTTP Response
단위 테스트 vs 통합 테스트 항목 단위 테스트 통합 테스트 범위 하나의 클래스/메서드 전체 레이어 통합 의존성 Mock/Stub 실제 Bean + 실제 DB 속도 빠름 (ms) 느림 (초) 테스트 @DataJpaTest, @WebMvcTest@SpringBootTestHTTP MockMvc (가짜) TestRestTemplate (실제) DB H2 (인메모리) MySQL (실제)
Flyway vs @Sql 구분 (핵심!) 항목 Flyway @Sql 목적 DB 스키마 버전 관리 테스트 데이터 삽입 역할 DDL (CREATE TABLE) DML (INSERT) 실행 시점 앱 시작 시 (1번) 각 테스트 메서드 전 파일 위치 db/migration/src/test/resources/sql/운영 사용 ✅ 사용 ❌ 테스트 전용
코드 템플릿
1. Integration Test package com.ryuqq.bootstrap;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.transaction.annotation.Transactional;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@Testcontainers
@Transactional
@DisplayName("Order 통합 테스트")
class OrderIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer <>("mysql:15-alpine" )
.withDatabaseName("test" )
.withUsername("test" )
.withPassword("test" );
@Autowired
private TestRestTemplate restTemplate;
@Test
@Sql("/sql/orders-test-data.sql")
@DisplayName("E2E - 주문 생성")
void shouldCreateOrder () {
PlaceOrderRequest request = new PlaceOrderRequest (1L , 100L , 10 );
ResponseEntity<ApiResponse<OrderResponse>> response = restTemplate.exchange(
"/api/v1/orders" ,
HttpMethod.POST,
new HttpEntity <>(request),
new ParameterizedTypeReference <>() {}
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getData().orderId()).isNotNull();
}
@Test
@Sql("/sql/orders-test-data.sql")
@DisplayName("E2E - 주문 조회")
void shouldGetOrder () {
Long orderId = 100L ;
ResponseEntity<ApiResponse<OrderResponse>> response = restTemplate.exchange(
"/api/v1/orders/{orderId}" ,
HttpMethod.GET,
null ,
new ParameterizedTypeReference <>() {},
orderId
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().getData().orderId()).isEqualTo(orderId);
}
}
@SpringBootTest(webEnvironment = RANDOM_PORT) 필수
TestRestTemplate 주입 (MockMvc 금지)
@Sql 로 테스트 데이터 삽입 (DML만)
@Transactional 롤백으로 테스트 격리
@Testcontainers + MySQL 실제 DB
2. Domain Fixture (testFixtures 소스셋) package com.ryuqq.fixture.domain;
import com.ryuqq.domain.order.aggregate.Order;
import com.ryuqq.domain.order.vo.OrderId;
import com.ryuqq.domain.order.vo.OrderStatus;
import com.ryuqq.domain.order.vo.Money;
import java.math.BigDecimal;
public final class OrderFixture {
private OrderFixture () {
throw new AssertionError ("Utility class - do not instantiate" );
}
public static Order defaultNewOrder () {
return Order.forNew(
new CustomerId (1L ),
Money.of(BigDecimal.valueOf(50000 ))
);
}
public static Order defaultExistingOrder () {
return Order.forExisting(
OrderId.of(1L ),
new CustomerId (1L ),
Money.of(BigDecimal.valueOf(50000 )),
OrderStatus.PLACED
);
}
public static Order confirmedOrder () {
Order order = defaultExistingOrder();
order.confirm();
return order;
}
public static Order canceledOrder () {
Order order = defaultExistingOrder();
order.cancel();
return order;
}
public static Order customOrder (Long id, BigDecimal amount, OrderStatus status) {
return Order.forExisting(
OrderId.of(id),
new CustomerId (1L ),
Money.of(amount),
status
);
}
}
위치 : domain/src/testFixtures/java/com/ryuqq/fixture/domain/
final 클래스
private 생성자 (인스턴스 생성 방지)
static 메서드만 사용
Domain 객체만 의존 (Application/Adapter 의존 금지)
3. Application Fixture package com.ryuqq.fixture.application.command;
import com.ryuqq.application.order.dto.command.PlaceOrderCommand;
import java.math.BigDecimal;
public final class PlaceOrderCommandFixture {
private PlaceOrderCommandFixture () {
throw new AssertionError ("Utility class - do not instantiate" );
}
public static PlaceOrderCommand defaultCommand () {
return new PlaceOrderCommand (
1L ,
BigDecimal.valueOf(50000 )
);
}
public static PlaceOrderCommand customCommand (Long customerId, BigDecimal amount) {
return new PlaceOrderCommand (customerId, amount);
}
}
4. Request Fixture (REST API) package com.ryuqq.fixture.adapter.rest;
import com.ryuqq.adapter.in.rest.order.dto.PlaceOrderRequest;
import java.math.BigDecimal;
import java.util.List;
public final class OrderRequestFixture {
private OrderRequestFixture () {
throw new AssertionError ("Utility class - do not instantiate" );
}
public static PlaceOrderRequest validCreateRequest () {
return new PlaceOrderRequest (
1L ,
List.of(OrderItemRequestFixture.validItemRequest())
);
}
public static PlaceOrderRequest invalidRequest_EmptyItems () {
return new PlaceOrderRequest (1L , List.of());
}
public static PlaceOrderRequest invalidRequest_NullCustomerId () {
return new PlaceOrderRequest (
null ,
List.of(OrderItemRequestFixture.validItemRequest())
);
}
}
5. @Sql 테스트 데이터 (DML만!)
DELETE FROM order_items;
DELETE FROM orders;
DELETE FROM customers;
INSERT INTO customers (customer_id, name, email)
OVERRIDING SYSTEM VALUE
VALUES (1 , 'Alice' , 'alice@example.com' );
INSERT INTO customers (customer_id, name, email)
OVERRIDING SYSTEM VALUE
VALUES (2 , 'Bob' , 'bob@example.com' );
INSERT INTO orders (order_id, customer_id, status, total_amount, order_date)
OVERRIDING SYSTEM VALUE
VALUES (100 , 1 , 'PENDING' , 10000 , '2024-01-01' );
INSERT INTO orders (order_id, customer_id, status, total_amount, order_date)
OVERRIDING SYSTEM VALUE
VALUES (101 , 1 , 'CONFIRMED' , 20000 , '2024-01-02' );
INSERT INTO order_items (order_item_id, order_id, product_id, quantity, price)
OVERRIDING SYSTEM VALUE
VALUES (1000 , 100 , 200 , 2 , 5000 );
DDL 금지 : CREATE TABLE, ALTER TABLE 절대 금지 (Flyway 책임)
DML만 : INSERT, UPDATE, DELETE만 허용
클린업 먼저 : DELETE → INSERT 순서
FK 순서 : 부모 먼저, 자식 나중
6. Gradle build.gradle 설정 // domain/build.gradle
plugins {
id 'java-library'
id 'java-test-fixtures' // ⭐ testFixtures 활성화
}
dependencies {
// Test
testImplementation libs.junit.jupiter
testImplementation libs.archunit.junit5
// Test Fixtures - Domain은 순수 Java만
// NO external dependencies
}
// application/build.gradle
plugins {
id 'java-library'
id 'java-test-fixtures'
}
dependencies {
api project(':domain')
// Test
testImplementation libs.spring.boot.starter.test
testImplementation testFixtures(project(':domain')) // ⭐ Domain Fixture
// Test Fixtures
testFixturesApi project(':domain')
testFixturesApi testFixtures(project(':domain'))
}
// bootstrap/build.gradle (Integration Test)
dependencies {
testImplementation libs.spring.boot.starter.test
testImplementation libs.testcontainers.mysql
testImplementation libs.testcontainers.junit
// Fixtures
testImplementation testFixtures(project(':domain'))
testImplementation testFixtures(project(':application'))
testImplementation testFixtures(project(':adapter-in:rest-api'))
}
테스트 실행 흐름 1. TestContainers 시작
└─ Docker → MySQL 컨테이너 시작
2. Spring Boot 시작 (@SpringBootTest)
└─ 전체 Bean 로딩
3. Flyway 자동 실행 (spring.flyway.enabled=true)
└─ V1 → V2 → V3 ... (테이블 생성)
4. @Sql 실행 (각 테스트 메서드 전)
└─ INSERT 테스트 데이터
5. 테스트 메서드 실행
└─ TestRestTemplate → Controller → UseCase → Repository → DB
6. @Transactional 롤백
└─ @Sql 데이터 자동 삭제
7. 다음 테스트 메서드 (4~6 반복)
8. TestContainers 종료
└─ MySQL 컨테이너 삭제
Fixture 의존성 규칙
허용되는 의존성 (✅) domain testFixtures
↓ 의존
domain (Production)
application testFixtures
↓ 의존 ↓ 의존
application domain testFixtures
adapter-in testFixtures
↓ 의존 ↓ 의존
adapter-in application testFixtures
금지된 의존성 (❌) domain testFixtures → application testFixtures ❌ 역방향
application testFixtures → adapter-* testFixtures ❌ 역방향
adapter-in testFixtures → adapter-out testFixtures ❌ 교차 금지
의존성 매트릭스 From ↓ / To → domain Fixture application Fixture adapter Fixture domain tests ✅ ❌ ❌ application tests ✅ ✅ ❌ adapter-in tests ✅ ✅ ✅ (in만) adapter-out tests ✅ ❌ ✅ (out만)
Zero-Tolerance 규칙
✅ MANDATORY (필수) 규칙 설명 @SpringBootTest(RANDOM_PORT)전체 컨텍스트 + 실제 HTTP 서버 TestRestTemplate 실제 HTTP 요청/응답 검증 @Transactional테스트 격리, 자동 롤백 @Sql DML만INSERT, UPDATE, DELETE만 @ActiveProfiles("test")테스트 전용 설정 @Testcontainers실제 DB (MySQL) Fixture: final 클래스 상속 금지 Fixture: private 생성자 인스턴스 생성 금지 Fixture: static 메서드 유틸리티 패턴 *Fixture 접미사네이밍 규칙
❌ PROHIBITED (금지) 항목 이유 MockMvc 가짜 HTTP, TestRestTemplate 사용 @Sql에 DDLCREATE TABLE 금지, Flyway 책임 @WebMvcTest통합 테스트는 @SpringBootTest @MockBean 남발실제 Bean 사용 (통합 테스트) EntityManager.persist() @Sql 사용, SQL 파일로 관리 @DirtiesContext느림, @Transactional 롤백 사용 역방향 Fixture 의존 domain → application 금지 H2 사용 MySQL (TestContainers) 사용
패키지 구조 project/
├── domain/
│ ├── src/main/java/
│ ├── src/test/java/
│ └── src/testFixtures/java/
│ └── com/ryuqq/fixture/domain/
│ ├── OrderFixture.java
│ ├── ProductFixture.java
│ └── CustomerFixture.java
│
├── application/
│ ├── src/main/java/
│ ├── src/test/java/
│ └── src/testFixtures/java/
│ └── com/ryuqq/fixture/application/
│ ├── command/
│ │ └── PlaceOrderCommandFixture.java
│ └── response/
│ └── OrderResponseFixture.java
│
├── adapter-in/rest-api/
│ ├── src/main/java/
│ ├── src/test/java/
│ └── src/testFixtures/java/
│ └── com/ryuqq/fixture/adapter/rest/
│ └── OrderRequestFixture.java
│
├── adapter-out/persistence-mysql/
│ ├── src/main/java/
│ ├── src/test/java/
│ └── src/testFixtures/java/
│ └── com/ryuqq/fixture/adapter/persistence/
│ └── OrderEntityFixture.java
│
└── bootstrap/
└── src/test/
├── java/
│ └── com/ryuqq/bootstrap/
│ └── OrderIntegrationTest.java
└── resources/sql/
└── orders-test-data.sql
체크리스트 (Output Checklist)
Integration Test
Domain Fixture
Application Fixture
@Sql 파일
Gradle 설정
ArchUnit 테스트 체크리스트
Fixture 의존성 규칙 (9개)
Fixture 네이밍 컨벤션 패턴 용도 예시 default*()기본 객체 defaultOrder(), defaultNewOrder()*WithStatus()특정 상태 orderWithPendingStatus()custom*()커스텀 빌더 customOrder(Long id, ...)invalid*()유효하지 않은 객체 invalidOrder() (예외 테스트)valid*Request()유효한 요청 validCreateRequest()invalid*Request_*()무효 요청 (사유) invalidRequest_EmptyItems()
참조 문서
Integration Test Guide : docs/coding_convention/05-testing/integration-testing/01_integration-testing-overview.md
Test Fixtures Guide : docs/coding_convention/05-testing/test-fixtures/01_test-fixtures-guide.md
Test Fixtures ArchUnit : docs/coding_convention/05-testing/test-fixtures/02_test-fixtures-archunit.md
Flyway Guide : docs/coding_convention/04-persistence-layer/mysql/config/flyway-guide.md