✅ 핵심 구문 비교
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 |