소개
테스트는 소프트웨어 개발에서 필수적인 역량이다. 이를 통해 제품의 품질을 정량적으로 평가하고, 예상된 동작이 실제로 수행되는지를 확인할 수 있다.
TDD(테스트 주도 개발)와 BDD(행동 주도 개발)와 같은 방법론은 테스트의 중요성을 높여 코드의 신뢰성과 유지보수성을 향상시키는 데 기여한다.
TDD(Test Driven Development: 테스트 주도 개발)란?
TDD는 소프트웨어 개발 방법론 중 하나로, 기능을 구현하기 전에 해당 기능에 대한 테스트 케이스를 먼저 작성하는 접근 방식을 의미한다.
TDD는 한 사이클로 구성되며, 실패하는 테스트 작성 → 테스트를 통과하도록 수정 → 리팩토링 순서로 진행된다.

- 실패하는 테스트 작성: 구현할 기능에 대한 테스트 케이스를 작성하여 초기 상태에서 테스트가 실패하도록 만든다.
- 테스트를 통과하도록 수정: 작성한 테스트를 통과시키기 위해 필요한 최소한의 코드를 구현한다.
- 리팩토링: 통과한 테스트를 기반으로 코드의 품질을 개선하고 가독성을 높인다.
처음 TDD를 접했을 때, 실패하는 코드를 어떻게 작성해야 할지 의문이 들었다. 자동 완성도 되지 않는 코드를 왜 먼저 작성해야 하는지도 이해가 가지 않았다. 그러나 예시를 작성하면서 조금씩 이해하게 되었다.
계산기 클래스를 제작하는 과정에서 더하기 함수를 만든다고 가정한다.
실패하는 테스트 작성
public class CalculatorTest {
@Test
void 두_수를_더하면_합을_반환한다() {
Calculator calculator = new Calculator();
assertThat(calculator.sum(1, 2)).isEqualTo(3);
}
}
// 오류 내용
// symbol: method sum(int,int)
// location: variable calculator of type Calculator
테스트 코드에서 sum 메서드가 없다는 오류가 발생한다. 이 오류 내용을 확인하고, 테스트를 통과할 수 있도록 코드를 수정해야 한다.
테스트를 통과하도록 수정
먼저 Calculator 클래스에 sum 메서드를 구현한다. 구현 방법은 여러 가지가 있을 수 있다. 이번 sum 함수는 비교적 간단하게 생각할 수 있지만, 만약 즉각적으로 명확한 구현이 어렵다면 가짜로 구현해도 좋다. 이렇게 가짜 구현을 통해 복잡한 코드일 경우 단계별로 쪼개서 TDD를 활용함으로써 문제없이 개발하는 능력을 기를 수 있다.
// 가짜로 구현하기
public class Calculator {
public static int sum(int a, int b) {
return 3;
}
}
// 명백하게 구현하기
public class Calculator {
public static int sum(int a, int b) {
return a + b;
}
}
이렇게 구현하면 테스트가 성공하는 것을 확인할 수 있다. 이 코드가 짧기 때문에 리팩토링이 필요하지는 않지만, 만약 리팩토링을 한다면 아래와 같이 변수 이름을 변경할 수 있다.
리팩토링
public class Calculator {
public static int sum(int num1, int num2) {
return num1 + num2;
}
}
이렇게 TDD 사이클을 통해 기능 명세에 따라 기능을 구현하게 된다.
먼저 작성한 코드가 깨지는 것을 바로바로 빠른 피드백을 받을 수 있으며, 오버엔지니어링을 피할 수 있는 장점이 있다.
BDD(Behavior Driven Development: 행동 주도 개발)란?
BDD는 TDD에서 파생된 개발 방법론으로, 테스트 케이스 자체가 요구 사항이 되도록 하는 개발 방법을 말한다. 이미 작성된 요구 사항을 활용하여 테스트 케이스를 작성함으로써 불필요한 개발 비용을 줄일 수 있다.
BDD 프로세스
- 시나리오 작성: 사용자가 원하는 기능이나 행동을 기반으로 시나리오를 정의한다.
- 테스트 코드 작성: 작성한 시나리오를 바탕으로 테스트 코드를 작성한다.
- 기능 구현: 테스트가 실패하는 것을 확인한 후, 그 테스트를 통과할 수 있도록 실제 기능을 구현한다.
- 리팩토링: 코드의 품질을 높이기 위해 필요에 따라 리팩토링을 진행한다.
BDD의 구조
- Given: 주어진 환경이나 조건을 설정한다.
- 이 단계에서는 테스트를 수행하기 전에 필요한 초기 상태를 정의한다.
- When: 특정 행위를 수행한다.
- 사용자가 어떤 행동을 취하는지를 설명하는 단계이다.
- Then: 기대하는 결과를 검증한다.
- 이 단계에서는 사용자가 수행한 행위에 대한 결과를 확인한다.
이러한 구조를 사용함으로써 개발자와 비즈니스 이해관계자 간의 의사소통이 원활해지고, 소프트웨어의 동작을 명확하게 정의할 수 있다.
BDD 구조 예시
다시 Calculator 클래스를 개발한다고 가정한다면, DBB는 아래와 같은 구조로 테스트를 작성할 수 있다.
@Test
void 사용자가_두_수를_입력하면_차를_반환한다() {
// Given - 사용자는 두 수를 입력한다.
int num1 = 2;
int num2 = 5;
// When - 두 수의 차이를 계산한다.
int result = Calculator.subtract(num1, num2);
// Then - 기댓값이 반환된다.
assertThat(result).isEqualTo(-3);
}
TDD와 BDD의 차이점

| 구분 | TDD (테스트 주도 개발) | BDD (행동 주도 개발) |
|---|---|---|
| 테스트 코드의 목적 | 기능의 동작 검증 | 서비스 사용자 시나리오의 동작 검증 |
| 테스트 코드 설계 중심 | 제공할 모듈의 기능 중심 | 서비스 사용자 행동 중심 |
| 테스트 코드 설계 재료 | 모듈 사양 문서 (개발자가 작성) | 서비스 기획서 및 사용자 스토리 |
| 적합한 프로젝트 유형 | 모듈 및 라이브러리 프로젝트 | 서비스 및 애플리케이션 프로젝트 |
| 장점 | 설계 단계에서 예외 케이스를 사전 확인 가능 | 설계 단계에서 누락된 기획 사항을 사전 확인 가능 |
결론
TDD와 BDD는 대립적인 관계가 아니라 상호 보완적인 관계이다. 따라서 하나만 선택하여 사용할 필요는 없다.

BDD는 사용자 관점에서 시나리오를 기반으로 테스트를 진행할 수 있는 장점이 있으며, TDD는 단위 테스트를 통해 기능의 세부적인 동작을 검증할 수 있다.
이 두 가지 방법론을 함께 사용하면 테스트 커버리지를 높일 수 있다.