← 목록으로 돌아가기

Kent Beck의 TDD와 Tidy First: 프론트엔드 개발에의 완벽한 적용 가이드

Development

TDD와 Tidy First

프론트엔드 개발 환경에서 Kent Beck의 TDD와 Tidy First 완벽 적용 가이드

소프트웨어 개발 분야의 살아있는 전설, 켄트 벡(Kent Beck)이 창시한 **테스트 주도 개발(Test-Driven Development, TDD)**은 단순한 개발 방법론을 넘어 개발자의 사고 방식을 근본적으로 바꾸는 철학입니다. 한 걸음 더 나아가, 그의 최근 저서인 **'Tidy First (단정하게 먼저)'**는 소프트웨어 설계의 본질인 '구조적 변경과 행위 변경의 분리'를 역설합니다.

오늘날 프론트엔드 개발은 상태 관리, 비동기 통신, 복잡한 UI 렌더링 등으로 인해 과거와 비교할 수 없을 만큼 거대해졌습니다. 이러한 환경에서 TDD와 Tidy First 원칙을 어떻게 적용하여 견고하고 유지보수하기 쉬운 애플리케이션을 구축할 수 있는지 상세히 알아보겠습니다.

1. TDD의 3단계 리듬: Red - Green - Refactor

TDD의 핵심은 피드백 루프를 극단적으로 짧게 가져가는 것입니다.

1단계: 실패하는 테스트 작성 (Red)

가장 작고 단순한 변화부터 시작합니다. 아직 구현하지 않은 기능에 대한 테스트를 작성하여, 의도적으로 실패하게 만듭니다. 이 단계에서는 '기능이 어떻게 동작해야 하는가'라는 사용자 관점의 명세(Specification)에 집중합니다. 프론트엔드에서는 React Testing Library와 Jest를 사용하여 사용자가 화면의 특정 버튼을 클릭했을 때 발생하는 상태 변화를 테스트로 먼저 작성할 수 있습니다.

2단계: 테스트를 통과하기 위한 최소한의 코드 작성 (Green)

이 단계의 유일한 목표는 방금 작성한 빨간색 테스트를 초록색으로 바꾸는 것입니다. 코드가 지저분해도, 하드코딩이 들어가도 상관없습니다. 가장 빠른 시간 안에 테스트를 통과시키는 것에만 집중합니다. 이것은 개발자에게 '내 코드가 동작한다'는 강력한 심리적 안정감을 제공합니다.

3단계: 구조 개선 (Refactor)

테스트가 모두 초록색이라면, 이제 코드를 아름답게 다듬을 시간입니다. 중복을 제거하고, 변수와 함수의 이름을 명확히 하며, 디자인 패턴을 적용합니다. 이 과정에서 우리는 코드가 망가질까 두려워하지 않습니다. 방금 작성한 촘촘한 테스트 코드가 안전망(Safety Net) 역할을 해주기 때문입니다.

2. Tidy First: 정리하고, 그 다음에 변경하라

켄트 벡의 'Tidy First' 접근법은 리팩터링을 언제, 어떻게 해야 하는지에 대한 명확한 해답을 제시합니다.

구조적 변경(Structural Change)과 행위 변경(Behavioral Change)의 완전한 분리

새로운 기능을 추가하거나 버그를 수정할 때, 기존 코드가 엉망이어서 작업하기 힘든 경우가 많습니다. 이때 기능을 추가하면서 코드를 동시에 정리하려고 하면, 에러가 났을 때 무엇 때문에 에러가 발생했는지(코드 정리 때문인지, 새로운 기능 때문인지) 알 수 없게 됩니다.

Tidy First는 이를 명확히 분리하라고 조언합니다.

  1. 먼저 정리하기 (Tidy First): 기능을 추가하기 쉬운 구조로 코드를 재배치합니다. 이때 외부에서 관찰 가능한 행위(Behavior)는 절대 변경해서는 안 됩니다. 기존의 방대한 컴포넌트에서 필요한 함수나 서브 컴포넌트를 분리(Extract Method/Component)합니다. 이 작업만 단독으로 커밋합니다.
  2. 이후에 기능 추가하기: 코드가 정리되어 기능을 추가하기 쉬워졌다면, 이제 기능(행위)을 변경합니다. 이 역시 분리된 커밋으로 기록합니다.

이러한 규율을 지키면, 리뷰어는 PR(Pull Request)을 리뷰할 때 구조적 변경과 행위 변경을 명확히 구분하여 훨씬 효율적으로 피드백을 줄 수 있습니다.

3. 프론트엔드 실무 적용 사례와 심화 가이드

3.1 컴포넌트 단위의 TDD

React 애플리케이션에서 TDD를 수행할 때는, 상태(State)와 부작용(Side Effects)을 분리하는 것이 유리합니다. 순수 함수로 이루어진 비즈니스 로직(예: 날짜 포맷 변경, 금액 콤마 추가 등)은 단위 테스트하기 매우 쉽습니다. 반면 UI 컴포넌트는 사용자의 상호작용(클릭, 타이핑)을 모방하는 통합 테스트에 가깝습니다.

3.2 켄트 벡의 소프트웨어 설계 원칙

  • 의도 노출(Intention-Revealing): 코드를 처음 보는 사람도 이 코드가 왜 작성되었는지 그 '의도'를 바로 파악할 수 있도록 이름과 구조를 정해야 합니다.
  • 가장 단순한 해결책(Simplest Solution that could possibly work): 복잡한 아키텍처나 미래를 대비한 과도한 설계(Over-engineering)를 경계합니다. 지금 당장의 문제를 해결하는 가장 단순하고 직관적인 해법을 채택합니다.
  • 중복의 최소화(Minimize Duplication): 똑같은 로직이 여기저기 흩어져 있으면, 규칙이 바뀔 때 모든 곳을 수정해야 하는 재앙이 발생합니다.

3.3 끊임없는 개선의 문화

TDD와 Tidy First는 개인의 역량뿐만 아니라 팀의 문화로 정착되어야 합니다. '이 코드는 누가 짰어?'라는 비난 대신, 실패하는 테스트를 통해 안전하게 시스템을 보호하고, 지속적으로 코드를 단정하게 유지하는 문화를 만들어야만 장기적으로 살아남는 프로덕트를 만듭니다.

결론적으로, TDD는 우리에게 코드를 수정할 용기를 주며, Tidy First는 그 수정을 체계적이고 우아하게 만드는 지침서입니다. 이 두 가지를 실무 프론트엔드 환경에 완벽히 녹여낸다면, 어떠한 요구사항 변경이나 복잡한 비즈니스 로직 앞에서도 흔들리지 않는 시니어 개발자로 거듭날 수 있을 것입니다.

4. TDD의 실전 적용 패턴: 프론트엔드 테스트 전략 수립

프론트엔드에서 TDD를 실제로 도입할 때 가장 중요한 것은 **"무엇을 테스트할 것인가"**를 명확히 정의하는 것입니다. 모든 코드에 테스트를 작성하는 것은 비현실적이며, 투자 대비 효율이 높은 영역에 집중해야 합니다.

4.1 테스트 피라미드(Test Pyramid) 전략

마틴 파울러(Martin Fowler)가 제안한 테스트 피라미드는 프론트엔드에서도 유효합니다. 피라미드의 바닥부터 위로 올라갈수록 테스트 비용은 증가하고 속도는 느려집니다.

  • 단위 테스트(Unit Test): 순수 함수, 유틸리티, 커스텀 훅의 로직을 검증합니다. 실행 속도가 빠르고 작성이 용이하므로 가장 많은 비중을 차지해야 합니다. 예를 들어, 가격을 포맷팅하는 formatPrice(10000) 함수가 "10,000원"을 반환하는지 테스트합니다.
  • 통합 테스트(Integration Test): 여러 컴포넌트가 조합되어 하나의 기능을 수행하는 것을 검증합니다. React Testing Library(RTL)를 사용하여 사용자의 상호작용(클릭, 입력)을 시뮬레이션하고, DOM의 변화를 확인합니다. 이것이 프론트엔드 TDD의 핵심 전장입니다.
  • E2E 테스트(End-to-End Test): Cypress나 Playwright를 사용하여 실제 브라우저 환경에서 전체 사용자 흐름을 검증합니다. 비용이 크므로 핵심 사용자 시나리오(로그인, 결제 등)에만 선별적으로 적용합니다.

4.2 테스트 가능한 코드 설계하기 (Testable Architecture)

코드가 테스트하기 어렵다면, 그것은 코드의 설계가 잘못된 것입니다. 테스트 용이성은 좋은 설계의 결과물입니다.

  • 부작용(Side Effects) 격리: API 호출, 타이머, 로컬 스토리지 접근 등의 부작용은 명확한 경계를 가진 함수로 분리하고, 테스트 시에는 Mock으로 대체합니다.
  • 의존성 주입(Dependency Injection): 컴포넌트가 직접 의존성을 생성하는 대신 외부에서 주입받도록 설계하면, 테스트 환경에서 쉽게 가짜(Fake) 의존성을 제공할 수 있습니다.
  • Presentational/Container 분리 패턴: 데이터를 가져오고 가공하는 로직(Container)과 순수하게 UI를 렌더링하는 부분(Presentational)을 분리하면, UI 컴포넌트는 주어진 props에 대한 렌더링 결과만 테스트하면 되므로 테스트가 극도로 단순해집니다.

5. Tidy First 실전: 레거시 코드에서 살아남기

현실 세계의 프론트엔드 프로젝트에서 우리는 대부분 레거시 코드와 마주합니다. Tidy First 원칙은 이 전쟁에서 가장 강력한 무기가 됩니다.

5.1 작은 정리(Tidying)의 카탈로그

켄트 벡은 "정리하기" 작업을 작은 단위로 분류합니다. 각 작업은 독립적으로 수행되며 행위를 변경하지 않습니다.

  • 보호 구문(Guard Clauses): 깊은 중첩의 if-else를 조기 반환(Early Return)으로 평탄화합니다.
  • 죽은 코드 제거(Dead Code Elimination): 더 이상 사용되지 않는 변수, 함수, import를 제거합니다.
  • 설명 변수(Explaining Variable): 복잡한 조건식이나 계산식을 의미 있는 이름의 변수에 할당하여 코드가 스스로를 설명하게 만듭니다.
  • 코드 순서 맞추기(Reorder Code): 관련된 코드를 물리적으로 가까이 배치합니다.

5.2 언제 정리하고, 언제 정리하지 말아야 하는가?

모든 코드를 무조건 정리하는 것은 "완벽주의"의 함정입니다. Tidy First는 다음 질문에 "예"라고 답할 수 있을 때만 정리하라고 조언합니다.

  1. 이 정리가 다음 행위 변경을 더 쉽게 만들어 주는가?
  2. 이 정리의 비용(시간, 위험)은 행위 변경으로 얻는 이익보다 작은가?

6. 결론: TDD와 Tidy First가 만드는 선순환

TDD의 Red-Green-Refactor 사이클과 Tidy First의 구조/행위 분리 원칙은 별개의 방법론이 아니라, 하나의 유기적인 워크플로우를 형성합니다. 테스트가 안전망을 제공하고, 안전망 위에서 자신 있게 코드를 정리하며, 정리된 코드 위에서 다음 기능 테스트를 작성합니다. 이 선순환이 반복될수록 코드는 점점 더 깨끗해지고, 개발 속도는 점점 빨라집니다.

X. 깊게 파헤치는 TDD와 Tidy First 심화 (Deep Dive)

TDD의 아름다움은 코드가 단순히 동작한다는 확신을 넘어서, 개발자에게 심리적인 안정감을 제공하는 데 있습니다. 켄트 벡이 주장한 이 안정감은 개발자 리소스가 가장 고갈되기 쉬운 대규모 유지보수 단계에서 빛을 발합니다.

1. 테스트 대역(Test Doubles)과 의존성 주입의 예술

프론트엔드 환경에서 TDD를 고도화할 때 피할 수 없는 장벽이 하나 있습니다. 바로 무수히 많은 사이드 이펙트(Side Effect)입니다. 브라우저 API (localStorage, 윈도우 객체), 비동기 네트워크 통신, 타이머 등은 언제나 순수한 테스트를 가로막습니다.
이를 위해 모킹(Mocking)과 스터빙(Stubbing) 기법이 활용됩니다. Jest나 Vitest가 제공하는 jest.mock() 같은 도구를 적절히 결합하면, 외부 의존성(External Dependencies)에 휩쓸리지 않는 순수한 오프라인 테스트 환경을 구축할 수 있습니다.
나아가 의존성을 외부에서 주입받는(DI - Dependency Injection) 형태로 컴포넌트나 함수를 리팩터링하면, TDD가 코드의 구조 자체를 "테스트하기 좋은(Testable)" 우아한 형태로 강제하는 놀라운 경험을 하게 됩니다.

2. Tidy First: 코드베이스의 지질학적 진화 구조 통제하기

Tidy First의 본질적인 메시지는 "소프트웨어 설계는 한 번에 완성되는 대리석 조각과 같은 것이 아니라, 매일 깎고 다듬는 지점토 예술과 같다"는 것입니다.

  • 병렬 변경(Parallel Change): 하위 호환성을 유지하면서 기존 함수를 남겨둔 채 새로운 구조의 함수를 병렬적으로 구현하고 마이그레이션합니다.
  • 동작의 변경인가, 구조의 변경인가: 이 두 가지는 절대 같은 커밋 또는 같은 PR에 묶여서는 안 됩니다. 구조를 다듬는 PR(Tidy)이 먼저 병합된 후, 그 아름다워진 구조 위에서 동작을 추가하는 PR(Feature)이 올라가야 합니다. 이것은 단순히 깔끔함의 취향 문제가 아닙니다. 배포 사고가 났을 때 롤백 지점을 찾고, 코드 리뷰어의 인지 부하를 줄여주는 완벽한 방어 기제입니다.

결론적으로, TDD는 프로그래머에게 코드 작성의 호흡을 조절해 줍니다. 1분 간격의 "레드-그린-리팩터" 심장 박동은 아무리 거대한 레거시의 혼돈 속에서도 길을 잃지 않게 해주는 등대입니다. 아직도 프론트엔드에서 TDD가 시기상조라고 생각하시나요? 지금 바로 가장 단순한 순수 함수 하나부터 실패하는 테스트를 작성해 보십시오. 개발 패러다임이 뒤집히는 순간을 맞이하게 될 것입니다.

X. 깊게 파헤치는 TDD와 Tidy First 심화 (Deep Dive)

TDD의 아름다움은 코드가 단순히 동작한다는 확신을 넘어서, 개발자에게 심리적인 안정감을 제공하는 데 있습니다. 켄트 벡이 주장한 이 안정감은 개발자 리소스가 가장 고갈되기 쉬운 대규모 유지보수 단계에서 빛을 발합니다.

1. 테스트 대역(Test Doubles)과 의존성 주입의 예술

프론트엔드 환경에서 TDD를 고도화할 때 피할 수 없는 장벽이 하나 있습니다. 바로 무수히 많은 사이드 이펙트(Side Effect)입니다. 브라우저 API (localStorage, 윈도우 객체), 비동기 네트워크 통신, 타이머 등은 언제나 순수한 테스트를 가로막습니다.
이를 위해 모킹(Mocking)과 스터빙(Stubbing) 기법이 활용됩니다. Jest나 Vitest가 제공하는 jest.mock() 같은 도구를 적절히 결합하면, 외부 의존성(External Dependencies)에 휩쓸리지 않는 순수한 오프라인 테스트 환경을 구축할 수 있습니다.
나아가 의존성을 외부에서 주입받는(DI - Dependency Injection) 형태로 컴포넌트나 함수를 리팩터링하면, TDD가 코드의 구조 자체를 "테스트하기 좋은(Testable)" 우아한 형태로 강제하는 놀라운 경험을 하게 됩니다.

2. Tidy First: 코드베이스의 지질학적 진화 구조 통제하기

Tidy First의 본질적인 메시지는 "소프트웨어 설계는 한 번에 완성되는 대리석 조각과 같은 것이 아니라, 매일 깎고 다듬는 지점토 예술과 같다"는 것입니다.

  • 병렬 변경(Parallel Change): 하위 호환성을 유지하면서 기존 함수를 남겨둔 채 새로운 구조의 함수를 병렬적으로 구현하고 마이그레이션합니다.
  • 동작의 변경인가, 구조의 변경인가: 이 두 가지는 절대 같은 커밋 또는 같은 PR에 묶여서는 안 됩니다. 구조를 다듬는 PR(Tidy)이 먼저 병합된 후, 그 아름다워진 구조 위에서 동작을 추가하는 PR(Feature)이 올라가야 합니다. 이것은 단순히 깔끔함의 취향 문제가 아닙니다. 배포 사고가 났을 때 롤백 지점을 찾고, 코드 리뷰어의 인지 부하를 줄여주는 완벽한 방어 기제입니다.

결론적으로, TDD는 프로그래머에게 코드 작성의 호흡을 조절해 줍니다. 1분 간격의 "레드-그린-리팩터" 심장 박동은 아무리 거대한 레거시의 혼돈 속에서도 길을 잃지 않게 해주는 등대입니다. 아직도 프론트엔드에서 TDD가 시기상조라고 생각하시나요? 지금 바로 가장 단순한 순수 함수 하나부터 실패하는 테스트를 작성해 보십시오. 개발 패러다임이 뒤집히는 순간을 맞이하게 될 것입니다.