TDD와 BDD: 테스트 주도 개발과 행동 주도 개발의 이해

소개

테스트는 소프트웨어 개발에서 필수적인 역량이다. 이를 통해 제품의 품질을 정량적으로 평가하고, 예상된 동작이 실제로 수행되는지를 확인할 수 있다.

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 프로세스

  1. 시나리오 작성: 사용자가 원하는 기능이나 행동을 기반으로 시나리오를 정의한다.
  2. 테스트 코드 작성: 작성한 시나리오를 바탕으로 테스트 코드를 작성한다.
  3. 기능 구현: 테스트가 실패하는 것을 확인한 후, 그 테스트를 통과할 수 있도록 실제 기능을 구현한다.
  4. 리팩토링: 코드의 품질을 높이기 위해 필요에 따라 리팩토링을 진행한다.

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는 단위 테스트를 통해 기능의 세부적인 동작을 검증할 수 있다.

이 두 가지 방법론을 함께 사용하면 테스트 커버리지를 높일 수 있다.

참고자료

kotest가 있다면 TDD 묻고 BDD로 가!

위로 스크롤