왜 오늘(내일이 아닌) 테스트 주도 개발로 전환해야 하는가

Fred· AI Engineer & Developer Educator4 min read

대부분의 개발자는 테스트 주도 개발에 대해 들어봤습니다. 대부분은 진지하게 시도하지 않았습니다. 시도한 사람들은 좋아하거나 잘못 시도하고 포기했습니다. TDD는 테스트를 작성하는 것이 아닙니다. 더 나은 코드를 더 빠르게 작성하는 것입니다. 비결은 실행하는 것입니다.

모든 사람이 잘못 알고 있는 것

TDD는 "테스트를 작성하고, 그다음 코드를 작성하라"가 아닙니다. "실패하는 테스트를 작성하고, 통과하는 최소한의 코드를 작성하고, 리팩토링하라"입니다. 리팩토링 단계가 중요합니다. 대부분의 사람들이 이것을 건너뜁니다. 그래서 TDD가 느리다고 생각합니다.

Red-Green-Refactor가 사이클입니다. 실패하는 테스트를 작성하고(빨강). 통과하는 충분한 코드만 작성하고(초록). 만든 엉망을 정리합니다(리팩토링). 기능이 완료될 때까지 반복합니다. 테스트가 무언가를 깨뜨리지 않고 리팩토링할 수 있는 권한을 줍니다.

TDD를 한 번 시도하는 사람들은 대개 테스트를 작성하고, 그다음 전체 기능을 작성하고, 왜 테스트가 즉시 통과했는지 궁금해합니다. 먼저 실패하는 것을 봐야 합니다. 실패하는 테스트가 테스트가 무언가를 테스트한다는 것을 증명합니다.

왜 작동하는가

TDD는 코드를 작성하기 전에 코드가 어떻게 사용될지 생각하도록 강제합니다. 테스트를 작성하기 어렵다면 API가 아마도 나쁜 것입니다. API를 수정하세요. 이것이 설계 압력입니다. TDD의 주요 이점이며 대부분의 사람들이 완전히 놓칩니다.

# TDD 없이 이렇게 작성할 수 있습니다
def process_user_data(user_id):
    db = Database()
    conn = db.connect()
    user = conn.query("SELECT * FROM users WHERE id = ?", user_id)
    # 비즈니스 로직과 섞인 50줄 이상의 데이터베이스 로직
    return result

# TDD를 사용하면 테스트가 관심사 분리를 강제합니다
def test_process_user_data():
    user = User(id=1, name="Test")
    result = process_user_data(user)
    assert result.status == "processed"

두 번째 버전은 테스트가 데이터 액세스와 비즈니스 로직을 분리하도록 강제했기 때문에 테스트 가능합니다. 첫 번째 버전은 실제 데이터베이스에 접근하지 않고는 쉽게 테스트할 수 없습니다. TDD는 나쁜 설계를 테스트하기 고통스럽게 만들어 더 나은 설계로 밀어줍니다.

시간 문제

"테스트를 작성할 시간이 없다"는 거꾸로입니다. TDD는 시간을 절약합니다. 수학은 이렇습니다. 테스트 없이 코드를 작성하고, 수동으로 테스트하고, 배포하고, 프로덕션에서 버그를 발견하고, 다시 컨텍스트를 전환해서 수정하고, 다른 것을 깨뜨리지 않았기를 바랍니다.

TDD를 사용하면 테스트를 작성하고, 코드를 작성하고, 끝입니다. 테스트가 아직 코드에 있을 때 즉시 버그를 잡습니다. 컨텍스트 전환이 없습니다. 프로덕션 화재가 없습니다. 절대 일어나지 않는 "나중에 테스트하겠다"가 없습니다.

TDD의 첫 주는 느립니다. 배우는 중입니다. 그 후에는 이전보다 빠릅니다. 한 달 후에는 테스트 없이 어떻게 코드를 배포했는지 궁금해질 것입니다. 투자 수익률은 몇 달이 아닌 며칠로 측정됩니다.

무엇을 테스트할 것인가

구현이 아닌 동작을 테스트하세요. 함수가 다른 함수를 호출하는지 테스트하지 마세요. 함수를 호출할 때 올바른 결과를 얻는지 테스트하세요. 구현 세부사항은 변경될 수 있습니다. 동작은 일관되게 유지되어야 합니다.

// 나쁜 테스트 - 구현을 테스트함
test('user registration calls database.insert', () => {
  const db = mock(Database)
  registerUser(db, 'test@example.com')
  expect(db.insert).toHaveBeenCalled()
})

// 좋은 테스트 - 동작을 테스트함
test('user registration creates an account', () => {
  registerUser('test@example.com', 'password')
  const user = findUserByEmail('test@example.com')
  expect(user).toBeDefined()
  expect(user.email).toBe('test@example.com')
})

첫 번째 테스트는 사용자 저장 방식을 리팩토링할 때마다 깨집니다. 두 번째 테스트는 등록이 작동을 멈출 때만 깨집니다. 동작을 테스트하세요.

TDD가 어려울 때

TDD는 탐색적 코드에서 어려움을 겪습니다. 무엇을 빌드하고 있는지 아직 확실하지 않다면 먼저 버릴 코드를 작성하세요. 알아낸 후에 모든 것을 삭제하고 TDD로 다시 하세요. 두 번째가 항상 더 빠르고 깔끔합니다.

UI 코드는 까다롭습니다. UI 뒤의 로직은 TDD할 수 있지만 "버튼이 파란색이다"를 테스트하는 것은 대개 가치가 없습니다. 버튼을 클릭하면 올바른 것을 하는지 테스트하세요, 버튼이 올바르게 보이는지가 아니라.

테스트가 없는 레거시 코드는 악몽입니다. 테스트되지 않은 코드에 대한 변경을 쉽게 TDD할 수 없습니다. 해결책은 코드가 현재 하는 것을 문서화하는 특성화 테스트를 추가한 다음 새 기능을 TDD하는 것입니다. 시간이 지남에 따라 코드베이스가 좋아집니다.

도구는 별로 중요하지 않다

모든 언어에 테스팅 프레임워크가 있습니다. JavaScript는 Jest. Python은 pytest. Java는 JUnit. PHP는 PHPUnit. 모두 잘 작동합니다. 언어에서 가장 인기 있는 것을 선택하고 넘어가세요. 도구가 TDD를 작동하게 만드는 것이 아닙니다. 규율이 합니다.

빠른 테스트가 프레임워크보다 더 중요합니다. 테스트가 10초 걸리면 자주 실행하지 않을 것입니다. 가능하면 테스트를 1초 미만으로 유지하세요. 데이터베이스와 HTTP 호출 같은 느린 것에 목을 사용하세요. 빠른 피드백은 TDD를 즐겁게 만듭니다. 느린 피드백은 고통스럽게 만듭니다.

내일 시작하는 것은 틀리다

TDD를 시작하는 가장 좋은 시간은 첫 번째 프로젝트였습니다. 두 번째로 좋은 시간은 바로 지금입니다. 작성해야 할 다음 함수를 선택하세요. 먼저 테스트를 작성하세요. 실패하는 것을 지켜보세요. 통과시키세요. 리팩토링하세요. 다시 하세요.

즉시 모든 것을 TDD하려고 하지 마세요. 변경하지 않는 한 오래된 코드에 테스트를 추가하러 돌아가지 마세요. 새 코드로 시작하세요. 한 번에 하나의 함수. 점차 습관을 기르세요. 몇 주 후에는 자동이 됩니다.

TDD를 시작하지 않는 개발자는 같은 실수를 계속합니다. 하루 만에 TDD를 시작하고 포기하는 개발자는 진정한 기회를 주지 않았습니다. 2주 동안 버티는 개발자는 대개 영원히 버팁니다.

TDD는 은탄환이 아닙니다. 나쁜 코드나 나쁜 아키텍처를 그 자체로 고치지 않을 것입니다. 하지만 코드를 더 좋게, 버그를 더 적게, 자신감을 더 높게 만들 것입니다. 오늘 시작하세요.

관련 읽기

개발 경력을 레벨업하고 싶으신가요? 확인해보세요:

Fred

Fred

AUTHOR

Full-stack developer with 10+ years building production applications. I write about cloud deployment, DevOps, and modern web development from real-world experience.

Need a developer who gets it?

POC builds, vibe-coded fixes, and real engineering. Let's talk.

Hire Me →