테스트

Given-When-Then

창욱씨 2020. 4. 1. 18:26
반응형

Given-When-Then 패턴, 제대로 이해하기

Given-When-Then이란?

Given-When-Then은 테스트 코드를 구조화하는 패턴으로, BDD(Behavior-Driven Development)에서 유래했습니다.

단계 역할 설명
Given 사전 조건 테스트에 필요한 초기 상태/데이터를 준비
When 실행 테스트 대상 동작을 실행
Then 검증 예상 결과를 확인(assert)

기본 예시

@Test
void 잔액이_충분하면_출금에_성공한다() {
    // Given - 잔액 10,000원인 계좌 준비
    Account account = new Account(10_000);

    // When - 3,000원 출금 실행
    account.withdraw(3_000);

    // Then - 잔액이 7,000원인지 검증
    assertThat(account.getBalance()).isEqualTo(7_000);
}

왜 사용하는가?

  1. 가독성 — 테스트 의도가 한눈에 파악됩니다.
  2. 일관성 — 팀 전체가 동일한 구조로 테스트를 작성할 수 있습니다.
  3. 유지보수 — 어떤 부분이 준비/실행/검증인지 명확하여 수정이 쉽습니다.

하나의 행위에 대해 여러 값을 검증해도 될까?

됩니다. 실무에서는 하나의 서비스 메서드가 여러 부수효과(side effect)를 발생시키는 경우가 흔하고, 그걸 모두 검증하는 것은 자연스럽습니다.

@Test
void 주문을_생성하면_재고차감_결제_알림이_모두_수행된다() {
    // Given
    Product product = new Product("맥북", 10);
    Member member = new Member("홍길동", 3_000_000);

    // When
    Order order = orderService.createOrder(member, product, 2);

    // Then - 여러 값을 검증
    assertAll(
        () -> assertThat(order.getStatus()).isEqualTo(COMPLETED),
        () -> assertThat(product.getStock()).isEqualTo(8),
        () -> assertThat(member.getBalance()).isEqualTo(1_000_000),
        () -> verify(notificationService).send(any())
    );
}

여기서 중요한 구분이 있습니다.

괜찮음 ✅ 피해야 함 ❌
의미 하나의 행위에 대한 여러 결과 검증 여러 행위를 하나의 테스트에 몰아넣기
예시 createOrder() 호출 후 재고·결제·알림 모두 확인 createOrder() + cancelOrder()를 한 테스트에서 검증

핵심 원칙: When은 하나

✅ Given → When(하나) → Then(여러 검증)
❌ Given → When(A) → When(B) → Then(A+B 검증)

왜 여러 행위를 한 테스트에 넣으면 안 되는가?

  1. 실패 원인 특정이 어려움 — 테스트가 깨졌을 때 A 때문인지 B 때문인지 알 수 없습니다.
  2. 테스트 이름을 짓기 어려움 — 하나의 테스트가 두 가지를 설명해야 합니다.
  3. 행위 간 의존성 발생 — A가 실패하면 B는 실행조차 되지 않습니다.

❌ 나쁜 예 - 두 개의 행위를 한 테스트에서 검증

@Test
void 주문_생성_후_취소() {
    // When 1
    Order order = orderService.createOrder(member, product, 2);
    assertThat(order.getStatus()).isEqualTo(COMPLETED);

    // When 2
    orderService.cancel(order.getId());
    assertThat(order.getStatus()).isEqualTo(CANCELLED);
    assertThat(product.getStock()).isEqualTo(10);
}

✅ 좋은 예 - 행위별로 테스트 분리

@Test
void 주문을_생성하면_상태가_완료된다() {
    Order order = orderService.createOrder(member, product, 2);
    assertThat(order.getStatus()).isEqualTo(COMPLETED);
}

@Test
void 주문을_취소하면_상태가_변경되고_재고가_복구된다() {
    Order order = createOrder(member, product, 2); // Given (이미 생성된 주문)

    orderService.cancel(order.getId());            // When (하나)

    assertAll(                                     // Then (여러 검증 OK)
        () -> assertThat(order.getStatus()).isEqualTo(CANCELLED),
        () -> assertThat(product.getStock()).isEqualTo(10)
    );
}

Then이 너무 길어진다면?

검증이 많아질 경우 @Nested@BeforeEach를 활용하여 관점별로 분리할 수 있습니다.

@Nested
@DisplayName("주문 생성 시")
class CreateOrder {

    Order order;

    @BeforeEach
    void setUp() {
        // Given & When (공통)
        order = orderService.createOrder(member, product, 2);
    }

    @Test
    void 주문_상태가_완료된다() {
        assertThat(order.getStatus()).isEqualTo(COMPLETED);
    }

    @Test
    void 재고가_차감된다() {
        assertThat(product.getStock()).isEqualTo(8);
    }

    @Test
    void 잔액이_차감된다() {
        assertThat(member.getBalance()).isEqualTo(1_000_000);
    }

    @Test
    void 알림이_발송된다() {
        verify(notificationService).send(any());
    }
}

정리

  • Given — 준비, When — 실행, Then — 검증
  • When이 하나면 Then은 몇 개든 OK
  • When이 여러 개면 테스트를 쪼개라
  • 검증이 5~6개 이상으로 많아지면 @Nested + @BeforeEach로 관점별 분리를 고려하자
반응형

'테스트' 카테고리의 다른 글

BDD  (0) 2020.04.02
TDD  (0) 2020.04.01
JUnit  (0) 2020.03.30
Unit Test  (0) 2020.03.27