단위 테스트, 슬라이스 테스트, 통합 테스트, 인수 테스트는 무엇인가? #27
Replies: 2 comments
-
|
단위 테스트, 테스트 대상(객체)가 가지는 책임에 집중하여 테스트한다.
슬라이스 테스트, 특정 계층의 역할과 책임을 검증한다. 특정 계층의 책임을 확인하기 위해, 특정 구성 요소만 로딩.
통합 테스트는 여러 계층이나 객체가 서로 올바르게 협력하는 지 검증한다.
인수 테스트는 사용자 시나리오대로 동작하는 지 검증한다. 내부 구현보다는 사용자 행동 흐름에 집중
각 테스트의 차이는 테스트의 목적, 관심사 그리고 검증 범위를 고려해야한다고 생각합니다! 예를 들어, 단순히 @SpringBootTest를 사용한다고 통합테스트가 되는 건 아니라 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
|
저는 단위/슬라이스/통합/인수 테스트의 분류 기준이 단순히 "테스트하는 범위의 크기"가 아니라, 즉 테스트의 종류는 피라미드의 어느 층에 속하는지 라벨을 붙이는 문제가 아니라, 1. 분류 기준에 대한 제 관점테스트를 분류하는 흔한 방식은 "테스트 범위의 크기"입니다. 단위 < 슬라이스 < 통합 < 인수 순으로 점점 커진다는 식입니다. 저는 이 분류가 결과적으로는 맞지만, 본질을 가린다고 생각합니다. 본질은 두 가지 축이라고 봅니다. 축 1. 검증 대상 (무엇을 확인하고 싶은가)
축 2. 협력 범위 (어디까지 실제로 동작시킬 것인가)
이 두 축을 어떻게 조합하느냐에 따라 단위/슬라이스/통합/인수 테스트가 결정된다고 생각합니다. 범위가 작으면 단위 테스트, 크면 통합 테스트라는 단순한 도식은 오해를 부르기 쉽다고 봅니다. 예를 들어 도메인 객체를 테스트하는데 협력 객체 5개를 실제로 같이 동작시켰다면, 범위는 크지만 본질은 단위 테스트의 의도(도메인 규칙 검증)에 가까울 수 있습니다. 2. 단위 테스트 (Unit Test)무엇인가 저는 단위 테스트를 "하나의 단위(주로 클래스 또는 메서드)의 행위가 의도대로 동작하는지 검증하는 테스트"라고 정의합니다. 여기서 "단위"의 크기에 대해서는 학파가 갈리는데(고전파 vs 런던파), 저는 "외부 의존성과 격리된 도메인 객체의 행위"를 단위로 보는 편이 자연스럽다고 생각합니다. 예시 @Test
void 예약_시간이_지난_경우_예외가_발생한다() {
LocalDateTime past = LocalDateTime.now().minusDays(1);
assertThatThrownBy(() -> new Reservation("moca", past, theme))
.isInstanceOf(IllegalArgumentException.class);
}이 테스트는 Spring 컨텍스트도, DB도, HTTP도 필요 없습니다. 그저 Reservation이라는 도메인 객체가 자기 규칙을 지키는지 확인합니다. 적합한 상황
주의할 점 저는 단위 테스트에서 가장 경계해야 할 것이 "단위 테스트를 위해 Mock으로 모든 의존성을 대체하는 Service 단위 테스트"라고 생각합니다. 이전 토론에서도 정리했지만, Mock 기반 Service 단위 테스트는 "유스케이스가 동작하는가"보다 "내가 예상한 호출 순서대로 메서드가 불렸는가"를 검증하는 형태가 되기 쉽고, 그러면 구현에 강하게 묶여 리팩터링 내성이 약해집니다. 그래서 저는 단위 테스트의 1순위 대상을 순수 도메인 객체로 봅니다. Service는 단위 테스트보다 통합 테스트가 더 적합한 경우가 많다고 생각합니다. 3. 슬라이스 테스트 (Slice Test)무엇인가 슬라이스 테스트는 '애플리케이션 전체가 아니라, 특정 계층(슬라이스)만 잘라서 그 계층의 책임을 검증하는 테스트라고 이해합니다. Spring에서는 예시 @WebMvcTest(ReservationController.class)
class ReservationControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private ReservationService reservationService;
@Test
void 잘못된_날짜_형식이면_400을_반환한다() throws Exception {
mockMvc.perform(post("/reservations")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"date\":\"invalid\"}"))
.andExpect(status().isBadRequest());
}
}이 테스트는 Controller 계층의 책임(요청 검증, 응답 형식, 상태 코드)만 검증합니다. Service는 Mock으로 대체하고, Repository나 DB는 아예 로드되지 않습니다. 적합한 상황
제 관점의 트레이드오프 슬라이스 테스트는 "특정 계층만 빠르게 검증"이라는 장점이 있지만, 저는 **'슬라이스 자체가 진짜 의미 있는 슬라이스인가'**를 먼저 따져봐야 한다고 생각합니다. 예를 들어 그래서 저는 슬라이스 테스트를 '계층 고유의 책임이 분명히 있을 때' 사용하는 편이 좋다고 봅니다. Repository 슬라이스 테스트는 SQL/매핑/제약 조건이라는 명확한 책임이 있어 가치가 큽니다. Controller 슬라이스 테스트는 입력 검증/응답 형식 검증 정도로 가치가 좁아질 수 있어, 차라리 통합 테스트로 흡수하는 편이 나을 때도 있다고 생각합니다. 4. 통합 테스트 (Integration Test)무엇인가 통합 테스트는 '여러 계층이 실제로 협력했을 때 유스케이스가 끝까지 동작하는지 검증하는 테스트'라고 정의합니다. Controller부터 Service, Repository, DB까지 실제로 동작시켜서 한 흐름이 의도대로 끝나는지 확인합니다. 예시 @SpringBootTest
@AutoConfigureMockMvc
class ReservationIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private ReservationRepository reservationRepository;
@Test
void 예약을_생성하면_DB에_저장되고_201을_반환한다() throws Exception {
mockMvc.perform(post("/reservations")
.contentType(MediaType.APPLICATION_JSON)
.content(예약_요청_JSON))
.andExpect(status().isCreated());
assertThat(reservationRepository.findAll()).hasSize(1);
}
}이 테스트는 HTTP 요청에서 시작해 DB 상태까지 확인합니다. Mock이 거의 없고, 실제 객체들이 협력하는 모습을 그대로 검증합니다. 적합한 상황
제 관점에서 통합 테스트의 가치 저는 이번 미션처럼 비즈니스 로직 대부분이 도메인 규칙 + DB 상태 변화로 구성된 경우, 통합 테스트가 가장 신뢰도 높은 테스트라고 생각합니다. 이유는 Mock으로 분리한 Service 단위 테스트는 "구현이 호출 순서대로 흘렀는가"를 검증하지만, 통합 테스트는 "실제로 예약이 만들어지고 DB에 저장되고 중복이 막혔는가"를 검증하기 때문입니다. 후자가 사용자가 실제로 경험하는 것에 훨씬 가깝습니다. 다만 통합 테스트의 단점도 분명합니다.
그래서 모든 케이스를 통합 테스트로 만들기보다, 유스케이스 단위로 대표 시나리오만 통합 테스트로 검증하고, 도메인 규칙의 세부 분기는 단위 테스트로 빠르게 검증하는 조합이 좋다고 생각합니다. 5. 인수 테스트 (Acceptance Test)무엇인가 인수 테스트는 **'사용자 관점에서 시스템이 요구사항을 만족하는지 검증하는 테스트'**라고 이해합니다. 통합 테스트와 비슷해 보이지만, 저는 두 가지가 본질적으로 다르다고 봅니다.
인수 테스트는 보통 HTTP 경계 바깥에서 RestAssured 같은 도구로 실제 API를 호출하는 형태로 작성됩니다. 예시 @Test
void 사용자는_예약을_생성하고_조회할_수_있다() {
// 사용자가 예약을 생성한다
ExtractableResponse<Response> 생성_응답 = RestAssured.given()
.contentType(ContentType.JSON)
.body(예약_요청)
.when().post("/reservations")
.then().statusCode(201).extract();
// 사용자가 자신의 예약을 조회할 수 있다
RestAssured.given()
.when().get("/reservations")
.then().statusCode(200)
.body("size()", is(1));
}적합한 상황
통합 테스트와의 실질적 차이 코드상으로는 통합 테스트와 인수 테스트가 거의 비슷해 보일 수 있습니다. 둘 다 Spring을 띄우고, 실제 DB를 쓰고, HTTP 요청을 보냅니다. 그래서 "이 둘이 정말 다른가"라는 의문이 들 수 있습니다. 저는 차이를 **'테스트의 어휘'**에서 찾습니다.
같은 코드라도 어떤 시선으로 작성되었느냐가 다르다고 봅니다. 인수 테스트는 요구사항을 검증하는 도구이고, 통합 테스트는 구현이 협력하는지 검증하는 도구입니다. 다만 현실에서는 이 둘이 같은 형태로 작성되는 경우가 많아, 굳이 엄격히 구분하지 않고 통합 테스트가 인수 테스트의 역할까지 겸하는 경우도 있다고 생각합니다. 6. 상황별 선택 기준저는 어떤 테스트를 선택할지 고민될 때 **'지금 내가 어떤 신뢰를 얻고 싶은가'**를 먼저 묻습니다. 도메인 규칙이 맞는지 빠르게 확인하고 싶다 → 단위 테스트 Reservation의 불변식, Theme 가격 계산, 시간 겹침 판단 등피드백이 빠르고, 분기별 케이스를 많이 다룰 수 있습니다. 외부 의존성 없이 도메인 객체만으로 검증되는 영역은 단위 테스트의 영역입니다. 특정 계층의 기술적 책임을 확인하고 싶다 → 슬라이스 테스트 SQL이 의도대로 동작하는가 → @DataJpaTest, @JdbcTest
요청 바인딩이 올바른가 → @WebMvcTest다만 슬라이스 테스트의 가치가 분명한 계층(특히 Repository)에 한정해서 사용하는 편이 좋다고 생각합니다. 유스케이스가 끝까지 동작하는지 확인하고 싶다 → 통합 테스트 예약 생성 요청이 검증을 거쳐 DB에 저장되고 응답까지 돌아오는 흐름
중복 예약이 실제로 막히는지 (DB 제약 조건 포함)이번 미션처럼 도메인 규칙과 DB 상태가 밀접하게 얽힌 경우, 통합 테스트가 가장 큰 신뢰를 줍니다. 요구사항이 만족되는지 사용자 관점에서 확인하고 싶다 → 인수 테스트 "사용자는 예약을 생성하고 자신의 예약 목록을 조회할 수 있다"
"관리자는 모든 예약을 취소할 수 있다"핵심 시나리오에 한정해서 작성하는 편이 좋다고 봅니다. 모든 케이스를 인수 테스트로 만들면 너무 느리고 무거워집니다. 제가 실제로 따르는 비율 감각 테스트 피라미드의 권장 비율(단위 많이 / 통합 적게 / E2E 매우 적게)에 동의는 하지만, 저는 도메인 성격에 따라 이번 미션처럼 도메인이 단순하고 DB 의존이 큰 경우는 통합 테스트의 비중을 더 키우는 편이 현실적이라고 생각합니다. 도메인 객체에 풍부한 규칙이 있을수록 단위 테스트의 비중이 커지고, 도메인이 얇고 CRUD 중심이라면 통합 테스트가 실질적인 신뢰의 핵심이 됩니다. 7. 흔히 혼동하는 지점들(a) "단위 테스트가 많을수록 좋다"는 맹신 저는 이 명제가 **'좋은 단위 테스트가 많을수록 좋다'**로 수정되어야 한다고 생각합니다. Mock으로 의존성을 모두 대체한 Service 단위 테스트가 많으면 오히려 리팩터링 내성이 약한 테스트가 쌓일 수 있습니다. (b) "통합 테스트는 느리니까 피해야 한다"는 회피 통합 테스트가 느린 건 사실이지만, 그 느림이 주는 신뢰의 가치가 큰 경우가 많습니다. 특히 이번 미션처럼 DB와 SQL이 핵심인 경우는 더욱 그렇습니다. 느림을 줄이는 방법(테스트 컨테이너 재사용, H2 활용)을 찾는 게 회피보다 낫다고 봅니다. (c) 통합 테스트와 인수 테스트의 강박적 구분 실무에서는 이 둘이 같은 형태로 작성되는 경우가 많고, 굳이 엄격히 구분할 실익이 없을 때도 많습니다. 중요한 것은 **"내가 지금 검증하려는 것이 구현의 협력인가, 사용자 시나리오인가"**라는 의도를 의식하는 것이라고 생각합니다. 8. 정리저는 네 가지 테스트의 관계를 이렇게 정리할 수 있을 것 같습니다.
네 가지는 서로 경쟁하는 도구가 아니라, 다른 종류의 신뢰를 제공하는 도구입니다. 어떤 테스트가 더 좋은가가 아니라, 지금 내가 어떤 신뢰를 얻고 싶은가에서 출발해야 한다고 생각합니다. 그리고 이 선택은 도메인의 성격에 따라 달라집니다. 도메인 규칙이 풍부하면 단위 테스트가 핵심이 되고, DB와 SQL이 핵심이면 통합 테스트가 신뢰의 중심이 됩니다. 비율을 정해두고 시작하는 게 아니라, 검증하고 싶은 신뢰의 종류에 따라 자연스럽게 비율이 결정되는 것이 더 건강한 접근이라고 봅니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
각 테스트가 무엇인지, 분류기준은 무엇인지 자유롭게 설명하고, 어떤 상황에서 어떤 테스트를 선택할지 작성해주세요!
Beta Was this translation helpful? Give feedback.
All reactions