개발/Spring

TDD의 개념과 적용 사례

IamBD 2025. 3. 15. 15:42

기존 항해 학습간 TDD를 처음 접하게 되었고 사내 전파를 위해 귀찮기만 한, 오히려 작업량이 늘어버리는 이미지를 가진 TDD의 효용성에 대해 설득을 위해 간단한 정리를 해보고자 합니다.

 

 

TDD란?

개발을 하다 보면 이런 경험 한 번씩 있으실겁니다.

코드 다 짰는데 안 돌아가네? ⇒ 디버깅 ⇒ 버그 발견 ⇒ 수정 ⇒ 또 안 됨 ⇒ OTL

 

이런 지옥의 무한 루프에 빠지지 않기 위해 탄생한 것이 바로 TDD(Test-Driven Development) 즉 테스트 주도 개발입니다.

 

TDD의 핵심 개념은 아주 간단합니다.

 

코드를 짜기 전에 실패하는 테스트를 먼저 작성하자!

 

즉, 기능을 만들고 테스트하는 게 아니라 테스트부터 만들고 그걸 통과하는 개발을 진행하는 방식입니다. 쉽게 말하면 시험 문제를 보고 거기에 맞춰 답안을 작성해나가는 방식으로 이해하셔도 좋습니다.

 

Why TDD?

전통적인 개발 방식에서는 기능을 먼저 개발하고 각종 이름으로 오픈 전 테스트를 진행하는 것이 일반적입니다. 하지만 이러한 방식에는 다음과 같은 문제들을 자주 겪고는 했습니다.

  • 개발이 끝난 후 버그 폭탄💣
  • 코드 변경 시 기존 기능이 알아서(?) 터짐
  • 개발 과정에서 요구사항 변경 시 야근

TDD는 이러한 문제를 해결하기 위해 코드 작성 이전에 테스트를 먼저 정의하여 단점을 개선합니다.

  • 기능에 대한 사전 검증
  • 리팩토링시 사이드 이펙트에 대한 검증
  • 유지보수시 견고한 코드 구조 유지

이렇듯 TDD는 단순한 테스트 기법이 아닌 소프트웨어 개발의 사고방식 자체를 변화시키는 방법론으로 위와 같은 장점으로 인해 많은 기업과 개발자들이 TDD를 적극 채택하고 있습니다.

 

TDD의 핵심 원칙

TDD는 단순히 테스트 먼저 작성하기라는 개념을 넘어, 개발 프로세스 자체를 체계적으로 정리하는 원칙이 있습니다. 그 중에서도 가장 중요한 것이 바로 Red ⇒ Green ⇒ Refactor 사이클이며 이걸 이해하면 TDD의 90%는 익혔다고 봐도 과언이 아닙니다.

Red ⇒ Green ⇒ Refactor 사이클

TDD에서는 개발할 때 다음 세 가지 단계를 반복합니다.

  1. Red (실패하는 테스트 작성)
    • 기능을 구현하기 전 의도적으로 실패할 테스트를 먼저 작성
    • 테스트 실행시 기능이 없으니 당연히 실패
    • 시험 문제를 내놓고 이 문제를 푼 사람이 없어? 그럼 내가 풀어야지! 라는 느낌으로 이 테스트가 정말 필요한지 검증
  2. Green (테스트 통과할 최소한의 코드 작성)
    • 실패한 테스트를 통과할 수 있도록 최소한의 코드 작성
    • 여기서 중요한건 최소한의 코드
    • 즉 테스트를 통과할 정도로만 코드를 짜고, 기능을 과하게 구현하지 않음.
  3. Refactor (코드 개선 및 최적화)
    • 이제 테스트르를 통과했으니, 코드를 더 깔끔하고 효율적으로 다듬는 단계
    • 중복된 코드를 제거하고, 가독성을 높이며, 성능을 개선
    • 중요한 건, 리팩토링을 하더라도 테스트는 항상 통과되어야 함!

이 1~3단계를 반복하며 점점 더 견고한 코드를 만들어갑니다.

 

최소한의 코드 원칙

2번 Green 단계에서 보면 테스트를 통과하는 최소한의 코드를 작성하라는 부분이 있는데 왜 그렇게 해야할까요?

  • 불필요한 코드 작성을 줄일 수 있음 ⇒ 처음부터 기능을 과하게 작성하지 않음
  • 초기 개발 속도 유지 ⇒ 필요한 기능만 일단 빠르게 구현
  • 리팩토링할 여유 확보 ⇒ 구조를 깔끔하게 다듬을 시간 확보

즉, 처음부터 완벽한 코드를 짜겠다고 욕심내는게 아닌, 작은 단위로 개선해나가는게 포인트입니다.

(완벽 주의를 버리고 현실적으로 조금씩 개선해나가자)

 

단위 테스트 기반 개발

TDD에서 테스트는 보통 단위 테스트를 기반으로 작성하게 되는데 이는 코드의 작은 단위(함수, 클래스, 모듈 등)를 독립적으로 테스트하는 방식입니다.

이를 통해서 다음과 같은 이점을 얻을 수 있습니다.

  • 작은 단위로 개발하기 때문에 문제를 빨리 찾을 수 있음
  • 큰 기능을 만들기 전 작은 기능부터 탄탄하게 쌓을 수 있음
  • 테스트가 코드의 안전망 역할을 하니 리팩토링시 사이드 이펙트에 대한 걱정이 줄어듦

정리하자면 TDD의 핵심 원칙은 사이클만 기억하면 됩니다.

실패하는 코드 작성 ⇒ 테스트를 통과하는 최소한의 코드 작성 ⇒ 코드 개선

 

즉, TDD는 버그 걱정을 줄이고, 더 생산적으로 개발할 수 있는 방법 입니다.

 

TDD의 실제 적용

이론만 보면 TDD가 좋아 보이기는 하는데 실제 코딩시에는 어떻게 적용해야 하나?라고 막막할 수 있습니다. 그래서 간단하게 TDD를 실무에서 적용하는 방법에 대해 Red ⇒ Green ⇒ Refactor 로 설명해보겠습니다.

1. 요구사항 분석 및 테스트 작성 (🟥 Red)

먼저 요구사항에 맞는 기능을 정의하고, 실패하는 테스트를 먼저 작성합니다.

// 단순하게 두 개의 숫자를 더하는 add 메서드
@Test
public void testAdd() {
    Calculator calculator = new Calculator();
    assertEquals(5, calculator.add(2, 3));  // 2 + 3 = 5
    assertEquals(0, calculator.add(-1, 1)); // -1 + 1 = 0
}

이 때, 이 테스트는 Calculator 클래스와 add 메서드가 없기 때문에 당연히 실패합니다.

2. 최소한의 코드 작성 (🟩 Green)

실패하는 테스트를 작성했으니 이제 최소한의 코드로 통과하는 코드를 작성합니다.

public class Calculator {
    public int add(int a, int b) {
        return a + b; // 테스트 통과를 위한 최소한의 코드
    }
}

이제 테스트에 필요한 클래스와 메서드가 준비되었으니 이 Test는 성공합니다.

3. 코드 개선 및 최적화 (🛠 Refactor)

이제 코드르 더 깔끔하고 확장 가능하게 리팩토링을 진행합니다. 예제에서는 숫자를 여러 개 더할 수 있도록 기능을 확장합니다.

public class Calculator {
    public int add(int... numbers) { // 가변 인자 사용
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }
}

물론 기능을 확장했으니 테스트 코드 또한 같이 개선해 나갈 수 있겠죠?

@Test
public void testAddMultipleNumbers() {
    Calculator calculator = new Calculator();
    assertEquals(10, calculator.add(1, 2, 3, 4)); // 1 + 2 + 3 + 4 = 10
    assertEquals(0, calculator.add()); // 빈 값이면 0 리턴
}

사이클의 시작이 Red, 즉 실패를 시작으로 작성되니 실제 개발에서도 실패 없이 성공도 없다! 라는 마인드로 접근하면 조금 더 가벼운 마음으로 시작할 수 있습니다.

 

TDD, 완벽할 필요는 없다.

TDD에 대한 간략한 정의와 아주 기초적인 예제를 통해 살펴봤음에도 여전히 이런 생각이 드실 수 있습니다.

 

그래도 언제 배우고 매번 테스트 코드 짜기 귀찮을 것 같은데;;

 

뭔가 새롭게 배우려면 시간이 걸리고 불편함을 넘어야 하듯 TDD 또한 당연히 익숙해지는데 시간이 걸리는데 처음부터 100% 완벽한 TDD에 대한 부담을 가질 필요는 없다고 생각합니다.

 

글의 내용에서 계속 서술했듯 처음에는 작은 기능을 그 시작으로 습관을 들이는 것이면 충분합니다.

  • 중요한 핵심 로직에 테스트 먼저 작성
  • 기존 프로젝트에 테스트 코드 추가
  • 작은 기능 단위로 Red ⇒ Green ⇒ Refactor 사이클 연습

저 또한 항해 이전에 TDD를 경험하지 못했으나 반대를 무릅쓰고 사내에 적용하며 느낀점은 귀찮은 건 맞지만 가져다주는 이점이 더 크다! 입니다.

 

특히 Github Action의 간단한 설정을 통해 PR시 실패하는 테스트 코드가 있으면 Merge가 안되는 소소한 조건 하나만으로도 알아서 테스트가 걸러주니 사이드 이펙트에 대한 걱정이 줄어들어 자신감 있는 개발 및 PR을 올리게 되었습니다.

 

처음에는 귀찮고 시간이 걸리겠지만 장기적으로 보면 코드를 더 안전하고, 유지보수하기 쉽게 만들면서 개발자의 스트레스를 줄여주는 강력한 무기이니 아직 시작하지 않았다면 아주 작은 기능들부터 작성하는 습관을 들여 보는건 어떨까요?