코딩/TestCode 공부

단위 테스트 : BDD

americanoallday 2025. 6. 24. 15:05

✅ 핵심 구문 비교

BDD 스타일 (권장) 전통 스타일 (가능)
given(...).willReturn(...) when(...).thenReturn(...)

둘 다 완전히 동일한 동작을 해, 그냥 가독성 차이.

 

✅ 언제 써?

  • 테스트 중인 메서드에서 mock 객체의 어떤 메서드가 호출될 예정이고, 그 메서드가 리턴 값을 가진다면, → 그 리턴 값을 미리 정의해주는 것.

 

예를 들어 서비스 메서드가 내부에서 userRepository.findById(...) 를 부르면, 이게 null을 리턴하면 안 되니까 가짜 User를 리턴.

 

✅ 예제

User mockUser = new User(...);

// BDD 스타일
given(userRepository.findById(1L)).willReturn(Optional.of(mockUser));

// 전통 스타일
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

 

willReturn() vs thenReturn()?

  • 같은 기능.
  • BDD 스타일에서는 given(...).willReturn(...)을, 기존 방식에서는 when(...).thenReturn(...)을 사용.

 

✅ BDD 스타일 (Behavior Driven Development) : 행동 기반 개발

⭐ 구조

// given: 조건 (사전 준비)
given(mock.method()).willReturn(value);

// when: 행동 (메서드 호출)
Type result = service.method();

// then: 검증 (기대 결과 검증)
then(mock).should().method();      // 호출 여부 검증
assertEquals(expected, result);    // 결과 검증

 

👉 예시

given(userRepository.findById(1L)).willReturn(user);

User found = userService.findUser(1L);

then(userRepository).should().findById(1L);
assertEquals("minsu", found.getName());

 

✅ 일반 (Non-BDD, TDD 스타일 : Test-Driven Development, 테스트 주도 개발)

⭐ 구조

// stubbing
when(mock.method()).thenReturn(value);

// method call
Type result = service.method();

// verification
verify(mock).method();
assertEquals(expected, result);

 

👉 예시

when(userRepository.findById(1L)).thenReturn(user);

User found = userService.findUser(1L);

verify(userRepository).findById(1L);
assertEquals("minsu", found.getName());

 

더보기

✅ TDD란? TDD = Test-Driven Development

직역하면 테스트 주도 개발.

 

📌 정의:

TDD는 테스트를 먼저 작성하고, 그 테스트를 통과하는 최소한의 코드를 작성한 다음, 리팩토링하는 개발 방식이야.

 

✅ TDD 3단계 사이클 (Red → Green → Refactor)

단계 설명
🔴 Red 실패하는 테스트를 먼저 작성함 (아직 구현 안되어 있어서 당연히 실패)
🟢 Green 테스트를 통과시키기 위한 최소한의 코드를 작성
🟡 Refactor 중복 제거, 설계 개선 등 리팩토링 (테스트는 계속 통과해야 함)

 

✅ 예제 (Java, JUnit 기반)

 

1. 실패하는 테스트 작성 (Red)

@Test
void add() {
    Calculator calculator = new Calculator();
    assertEquals(5, calculator.add(2, 3)); // 아직 add() 없음 → 실패
}

 

 

2. 코드 작성 (Green)

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

 

 

3. 리팩토링 (필요 시, 예: 불필요한 코드 정리)

 

✅ TDD vs BDD

항목 TDD BDD
전체 개념 테스트 먼저 작성하고 개발 “행동(기능)“을 먼저 설명하고 그걸 기반으로 개발
테스트 관점 개발자 중심 사용자/비즈니스 중심
주요 구조 test-implement-refactor given-when-then
예시 assertEquals(3, sum(1, 2)) given user logs in, when they click logout, then they are logged out

✅ 핵심 요약

  • TDD: 테스트 먼저 → 구현 → 리팩토링
  • 목표: 작은 단위에서 신뢰성 있는 코드 작성
  • 결과: 테스트 가능한 구조, 설계 개선, 리팩토링 내성 있는 코드

✅ BDD vs 일반(Mock 기반) 비교

항목 BDD 스타일 일반 스타일
코드 구성 방식 given-when-then when + method call + verify
가독성 자연어에 가까움 (~하면 ~한다) 익숙한 Mockito 문법
문법 given().willReturn(), then().should() when().thenReturn(), verify()
선호되는 상황 테스트 가독성이 중요한 경우 단순한 mock 테스트

 

✅ 추가: Mockito에서 제공하는 BDD 전용 static import

import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;

💡 정리

키워드 의미
given 테스트 준비 단계 (mock 세팅)
when 실제 행동 호출
then 결과 검증 (verify 또는 assert)

BDD는 특히 “비즈니스 로직 중심의 행동” 에 집중하고 싶은 경우에 유용. 좀 더 사람이 읽기 쉽게 하려는 의도가 있음. 

 

✅ 예외 터뜨리기: 

willThrow

given(userRepository.findById(-1L)).willThrow(new EntityNotFoundException());

→ 이런 식으로 예외도 설정 가능.

 

✅ void 메서드의 경우

willDoNothing().given(boardRepository).incrementViewCount(1L);

 

  • incrementViewCount는 void 메서드니까 willReturn 안 됨.
  • 대신 willDoNothing()이나 willThrow(...) 같은 걸 써야 돼.

 

✅ 요약 정리

목적 구문
반환값 지정 given(...).willReturn(...)
예외 발생 given(...).willThrow(...)
void 메서드에 예외 설정 willThrow(...).given(mock).voidMethod()
void 메서드 아무것도 안함 willDoNothing().given(...)

 

✅ 1. 결과값 검증 (assert 계열)

assertEquals, assertTrue, assertNotNull 등을 이용해 메서드 반환값의 상태나 값을 검증하는 방식.

예시:

ReadBoardResponseDto response = boardService.readBoard(boardId);
assertEquals("title", response.getTitle()); // 값이 정확히 일치하는지
assertTrue(response.getLike() >= 0);        // 조건 만족 여부
assertNotNull(response.getContent());       // Null 아님 여부

✅ 2. 행동(호출) 검증 (Mockito의 then().should())

Mock 객체에 대해 특정 메서드가 호출되었는지, 호출 횟수, 호출 인자 등을 검증.

예시:

then(boardRepository).should().incrementViewCount(boardId);       // 1번 이상 호출
then(boardRepository).should(times(1)).findBoardById(boardId);    // 정확히 1번 호출
then(reactionRepository).should(never()).countBoardReactions(any()); // 호출되지 않아야 함

✅ 3. 예외 검증 (assertThrows)

예외가 발생하는 경우를 테스트할 땐 assertThrows()를 사용해서 예외 타입과 메시지를 함께 검증.

예시:

assertThrows(IllegalArgumentException.class, () -> {
    boardService.updateBoard(userId, invalidDto, boardId);
});

메시지도 검증하고 싶으면 이렇게:

Exception exception = assertThrows(IllegalArgumentException.class, () -> {
    boardService.updateBoard(userId, invalidDto, boardId);
});
assertEquals("클로버는 110 아래로만 걸 수 있습니다.", exception.getMessage());

🔄 그 외에 자주 쓰는 검증 방법들

메서드 설명
assertNull(obj) obj가 null인지 확인
assertFalse(cond) 조건이 false인지 확인
assertIterableEquals(list1, list2) 리스트/컬렉션이 같은지 확인
assertAll() 여러 assert를 묶어서 한 번에 실행 (여러 검증 결과 보기 좋음)
verify(mock).method() Mockito에서 호출 여부 검증 (BDD 스타일이 아닌 경우)

🔧 팁: assertAll()**로 그룹화**

assertAll(
    () -> assertEquals("title", result.getTitle()),
    () -> assertEquals(10L, result.getLike()),
    () -> assertFalse(result.isSelected())
);

이렇게 하면 하나 실패한다고 바로 테스트 중단되지 않아서 여러 값을 동시에 확인할 때 좋음.

'코딩 > TestCode 공부' 카테고리의 다른 글

Mocking 개념, Test Double 종류  (5) 2025.06.14
JUnit  (2) 2025.06.01