TDD와 BDD, 그리고 테스트 작성의 기본 개념들
소프트웨어 개발에서 테스트는 단순한 "코드 검증" 이상의 역할을 한다. 특히, TDD(Test-Driven Development)와 BDD(Behaviour-Driven Development)는 테스트를 통해 설계와 개발 방향성을 잡아가는 접근법이다. 이번 포스팅에서는 TDD와 BDD를 중심으로, 테스트와 관련된 다양한 개념들을 정리해보려고 한다.
SUT란?
테스트하려는 대상을 SUT(System Under Test)라고 부른다.
즉, 테스트의 주요 대상이 되는 시스템, 메서드, 혹은 클래스를 지칭하는 용어다.
SUT를 잘 정의하는 것은 테스트의 초점을 명확히 하는 데 중요하다.
BDD (Behaviour-Driven Development)
BDD란?
BDD는 TDD에 "행동(Behaviour)"을 강조한 방식이다. 쉽게 말해, 시스템이 어떻게 동작해야 하는지를 중심으로 테스트를 작성하는 접근법이다.
TDD에서는 "어떤 테스트를 추가할까?"를 고민하지만, BDD는 "사용자가 시스템을 사용할 때 어떤 행동을 기대하는가?"에 초점을 맞춘다.
BDD는 유저 스토리와 시나리오를 기반으로 테스트를 작성하며, 다음 구조를 따른다.
Given-When-Then
- Given: 테스트의 초기 상태(어떤 조건들이 주어진 상황).
- When: 유저가 특정 행동을 취했을 때.
- Then: 그 결과로 기대되는 상태나 동작.
BDD를 통해 작성된 테스트는 "사용자 관점"에서 시스템을 검증할 수 있다. 이는 비즈니스 요구사항을 코드로 표현하는 데 효과적이다.
상호작용 테스트
상호작용 테스트는 메서드가 실제로 호출되었는지 확인하는 테스트다. 예를 들어, 특정 서비스 메서드가 제대로 호출되었는지 verify()를 사용해 검증하는 것이 상호작용 테스트의 대표적인 예다.
왜 상호작용 테스트는 그다지 좋지 않은가?
- 캡슐화 위반: 내부 구현을 감시하는 테스트는 캡슐화를 깨뜨릴 위험이 있다.
- 구현에 의존적: 내부 구현이 변경되면 테스트도 깨질 가능성이 높다. 결과적으로 리팩토링 비용이 증가한다.
상태 검증 vs. 행위 검증
- 상태 검증: 시스템에 특정 값을 넣었을 때, 반환되는 결과값이 기대값과 일치하는지 확인한다.
(예: assertEquals(expected, actual)) - 행위 검증: 특정 메서드가 호출되었는지, 호출 횟수는 맞는지 등을 확인한다.
(예: verify(mockObject).methodCall())
가능하면 상태 검증에 집중하는 것이 더 바람직하다.
테스트 픽스처
테스트를 실행하기 위해 필요한 리소스나 상태를 준비하는 것을 테스트 픽스처라고 한다.
JUnit에서는 @BeforeEach로 픽스처를 설정할 수 있다.
@BeforeEach
void setUp() {
// 테스트 전에 필요한 객체 생성 및 초기화
member = new Member(1L, "test@test.com");
}
왜 테스트 픽스처를 남발하면 안 될까?
- 가독성 저하: 테스트 코드만 봤을 때, 초기 상태를 이해하기 어렵게 만든다.
- 의존성 증가: 테스트 간에 공유되는 리소스가 많아질수록 문제가 생길 가능성이 커진다.
픽스처는 꼭 필요한 경우에만 사용하고, 테스트 코드 자체가 모든 흐름을 명확히 보여주도록 작성하는 것이 중요하다.
비욘세 규칙 (Beyoncé Rule)
"유지하고 싶은 상태나 정책이 있다면 테스트를 작성하라."
이는 테스트가 단순히 코드 검증을 넘어, 정책(policy)과 계약(contract) 역할을 한다는 뜻이다.
테스트는 시스템의 안정성을 위한 계약이므로, 남이 작성한 테스트를 함부로 수정하지 않는 것이 좋다. 기존 테스트가 깨졌다면, 그 이유를 철저히 검토한 후 수정해야 한다.
테스트 더블
테스트를 작성할 때, 실제 객체 대신 사용하는 가짜 객체를 테스트 더블이라고 한다.
테스트 더블에는 여러 종류가 있지만, 그중에서도 Mock 객체가 가장 자주 사용된다.
Dummy, Stub, Mock의 차이
- Dummy: 사용은 되지만 실제로 아무런 동작을 하지 않는 객체.
- Stub: 특정 메서드 호출에 대해 미리 정의된 응답을 반환하도록 설정된 객체.
- Mock: 호출된 메서드나 호출 횟수를 검증할 수 있는 객체. Mockito에서 흔히 사용된다.
Mock 객체는 행위 검증에 자주 사용되며, 테스트 코드에서 외부 의존성을 대체하는 역할을 한다.
결론
- TDD는 설계를 검증하고 개선하는 도구다.
- BDD는 사용자의 행동을 중심으로 테스트를 작성해 비즈니스 로직에 집중하게 만든다.
- 테스트는 상태 검증에 초점을 맞추되, 필요 시 상호작용 테스트나 픽스처를 적절히 활용하자.
- 테스트는 단순한 검증 이상의 "정책"과 "계약"의 역할을 하며, 시스템의 안정성을 보장한다.
테스트 코드에 담긴 철학이 생각보다 깊고 단단한것같다.
심지어 재미도 있는것같어~