test

What is TDD (Test Driven Development)

TDD란 무엇인가?

TDD(Test Driven Development, 테스트 주도 개발) 는 테스트를 중심으로 코드를 작성하는 개발 방식입니다. 핵심 개념은 단순합니다. 먼저 실패하는 테스트를 작성하고, 그 테스트를 통과시키는 코드를 구현한 뒤, 마지막으로 코드를 다듬는 과정을 반복합니다. 이 순환 구조를 Red-Green-Refactor 사이클이라고 합니다.

TDD의 진짜 목적은 ‘테스트 코드 작성’이 아니라 ‘테스트를 통해 설계를 이끄는 것’에 있습니다. 테스트를 먼저 작성하면 자연스럽게 요구사항이 구체화되고, 불필요한 로직을 줄이면서 유지보수성과 신뢰성이 높은 코드를 만들 수 있습니다.


TDD 프로세스

TDD의 핵심은 짧고 반복적인 사이클입니다. 개발자는 먼저 실패할 테스트를 작성합니다(Red). 그 다음 해당 테스트를 통과시키기 위한 최소한의 코드를 구현합니다(Green). 마지막으로 리팩토링을 통해 코드의 중복을 제거하고 가독성을 높입니다(Refactor).

이 과정을 반복하면서 코드 품질은 점차 개선되고, 기능 변경에도 안정적으로 대응할 수 있습니다. 정리하면, TDD는 코드 품질을 ‘나중에 검증하는 것’이 아니라 개발 과정 속에서 함께 구축하는 방식입니다.


TDD 핵심 원칙

TDD에서는 항상 작고 실패하는 테스트로부터 출발합니다. 이 테스트는 단순한 검증 도구가 아니라, 구현해야 할 기능을 명세하는 문서 역할을 합니다. 이 과정을 통해 “무엇을 만들어야 하는가?”를 분명하게 정의할 수 있고, 불필요한 복잡성을 줄일 수 있습니다.

또한 하나의 테스트는 하나의 기능만 검증해야 합니다. 작고 명확한 테스트는 실행 시간이 짧고, 실패했을 때 원인 파악이 빠릅니다. 이로 인해 개발 과정에서 짧은 피드백 루프가 만들어지고, 이는 개발 효율을 크게 높입니다.

설계 측면에서도 TDD는 명확한 책임 분리와 느슨한 결합(Loose Coupling)을 중요하게 생각합니다. 예를 들어 회원가입 기능에서 모든 유효성 검증 로직을 서비스 계층에 몰아넣는 대신, 도메인 객체(User)가 자신의 데이터에 대한 유효성을 직접 보장하도록 구성하면, 각 객체가 자신의 역할에만 집중할 수 있습니다. 이는 시스템 전체 구조를 유연하게 만들고 변경 비용을 줄여줍니다.

외부 시스템(DB, 외부 API 등)에 의존하지 않고 독립적으로 테스트할 수 있는 단위 테스트(Unit Test) 작성도 TDD의 중요한 원칙입니다. 이때 Mock 객체를 활용하면 실제 외부 리소스를 사용하지 않고도 동일한 동작을 검증할 수 있어 테스트 속도가 향상되고, 반복적인 검증과 피드백이 쉬워집니다.

입력 검증에서 도메인 행동까지의 단계적 명확성 유지

TDD에서 구현되는 기능은 하나의 도메인 행동(Use Case)이 “어떻게 처리되는가”가 단계적으로 드러나야 합니다. 이 흐름은 테스트 시나리오와 구현 코드가 서로 1:1로 대응할 수 있도록 돕고, 요구사항 변경 시 영향 범위를 명확히 해줍니다. 예를 들어 결제 승인 기능을 살펴보면 다음과 같은 흐름이 자연스럽습니다

  1. 입력 값 검증 (Validation)

결제 금액, 결제 수단, 사용자 자격 등 외부로부터 전달된 데이터가 도메인의 불변 조건을 위반하지 않는지 확인합니다. 여기서의 핵심은 잘못된 요청은 가능한 한 초기에 차단합니다.

  1. 도메인 행동 수행 (Domain Behavior Execution)

결제 객체(Payment)가 자신의 규칙에 따라 “승인 가능 여부”를 판단하고 상태를 변경합니다. 이 단계는 단순 로직 처리가 아니라 도메인 규칙이 적용됩니다.

예: 결제 한도 초과 여부, 사용자 결제 이력 기반 승인 제한 등.

  1. 영속화 또는 상태 저장 (Persistence)

승인된 결제 객체를 저장하거나, 외부 결제 게이트웨이와 연동합니다. 이 단계는 도메인의 의사결정이 끝난 이후에 수행됩니다.

  1. 결과 반환 (Return Response)

최종 처리된 결제 상태(승인 성공/실패, 승인 ID 등)를 호출자(API, UI, 메시지 시스템)에게 반환합니다.

구분유지되는 원칙효과
검증과 규칙은 도메인 내부로 집중도메인 주도 설계(Domain-Driven Design)규칙 변경 시 영향 범위 최소화
서비스 계층은 흐름만 조율단일 책임 원칙 (SRP)모듈 간 결합도 감소
영속화는 인프라 레이어에서만 처리계층 분리 (Layered Architecture)테스트 대체 용이, Mock 사용 가능
반환은 표현 계층이 활용 가능한 형태로 변환표현-도메인 역할 구분API 스펙 변경 대응 유연

TDD는 단순히 “테스트 먼저 작성하는 기법”이 아니라 기능을 작은 검증 단위로 분해하여 구현 흐름을 구조화하는 방법론입니다. 흐름이 명확해야 다음과 같은 테스트 구조가 자연스럽게 성립합니다.

  • 주어진 입력이 유효한가?
  • 유효하다면 도메인 규칙에 따라 어떤 상태 변화가 발생하는가?
  • 그 상태가 저장 또는 외부 시스템과의 상호작용으로 어떻게 이어지는가?
  • 호출자에게 어떤 결과가 반환되어야 하는가?

단계적 명확성은 테스트가 요구사항을 그대로 설명하는 형태를 가능하게 합니다.

지속적 리팩토링

TDD는 단순히 테스트를 작성하는 개발 방식이 아니라, 지속적으로 설계를 개선하는 과정을 포함합니다. 기능이 테스트를 통과했다고 끝이 아니라, 테스트가 보장하는 안전망을 기반으로 코드 구조를 더 나은 방향으로 정리합니다. 이 단계에서 네이밍 정리, 중복 제거, 응집도 향상 같은 리팩토링이 이루어집니다.

리팩토링 이후에도 테스트는 계속해서 올바르게 기능이 동작하고 있음을 확인해줍니다. 덕분에 안정적으로 구조를 개선하고 유지보수성 높은 코드를 만들어갈 수 있습니다.


TDD의 장단점

TDD의 가장 큰 장점은 코드 품질 향상과 설계 안정성 확보입니다. 테스트를 기반으로 개발하면 버그 발생 가능성이 줄어들고, 코드 리팩토링 시에도 기능이 깨질 위험이 거의 없습니다. 또한 테스트 코드가 곧 문서 역할을 하므로, 신규 개발자나 다른 팀원이 코드를 이해하기 쉬워집니다.

설계적인 측면에서도 TDD는 큰 도움을 줍니다. 테스트를 먼저 작성하다 보면 복잡한 구조보다 단순하고 명확한 설계를 선택하게 됩니다. 이로 인해 의존성이 줄고, 코드가 자연스럽게 모듈화됩니다.

물론 단점도 존재합니다. 테스트 코드 작성으로 인해 초기 개발 속도가 느려질 수 있고, 프로젝트 규모가 커질수록 테스트 코드 관리가 부담될 수 있습니다. 또한 TDD 접근법에 익숙하지 않은 개발자에게는 학습 곡선이 존재합니다. 하지만 일단 TDD의 숙련도가 올라가면, 전체적인 개발 속도와 안정성이 동시에 향상되는 것을 경험할 수 있습니다.


TDD 실무 적용

사실 TDD를 실무에 적용할 때는 완벽함보다 실천의 지속성이 더 중요합니다. 처음부터 모든 기능을 테스트로 감싸려 하기보다, 핵심 로직부터 점진적으로 적용해 나가는 것이 현실적입니다.

테스트는 작고 명확해야 하며, 실행 속도가 빠를수록 좋습니다. 통합 테스트보다 단위 테스트 중심으로 접근하고, 테스트 이름은 기능의 의도를 드러내도록 짓는 것이 바람직합니다. 가장 중요한 점은 항상 실패하는 테스트부터 작성하는 습관을 들이는 것입니다. 이 습관은 개발자에게 “이 기능이 어떤 결과를 가져와야 하는가?”라는 질문을 스스로 던지게 만들어, 코드 품질의 방향성을 잡아줍니다.

TDD는 팀의 개발 문화를 바꾸는 일입니다. 처음엔 낯설고 느릴 수 있지만, 꾸준히 반복하다 보면 테스트와 코드가 하나로 연결된 건강한 개발 생태계가 만들어집니다.


정리

TDD는 테스트를 위한 방법이 아니라, 품질을 설계하는 방법입니다. Red-Green-Refactor 사이클을 통해 기능을 구현하고 리팩토링을 반복함으로써, 개발자는 안정성과 확신을 갖춘 코드를 만들어냅니다.

단기적으로는 시간이 더 들 수 있지만, 장기적으로 TDD는 유지보수 비용을 줄이고 코드의 신뢰성을 극대화합니다. 테스트는 귀찮은 절차가 아니라, 안정적인 시스템을 위한 최소한의 투자라고 할 수 있습니다.