TDD (테스트 주도 개발) 에 대하여

@Hudi · February 24, 2022 · 4 min read

서론

우아한테크코스를 시작하기 전에는 TDD가 좋다고 어디서 한번 들어본 정도였지, 그 전에는 테스트 코드 한줄 작성해본 적 없었다. 우아한테크코스에서는 첫번째 미션부터 TDD로 진행할 정도로 TDD를 굉장히 강조한다.

TDD 와 만난지 얼마되지 않았지만, 그간 느낀 TDD의 장점에 대해 글을 작성해보려 한다.

TDD (Test Driven Development) 란

소프트웨어 개발 방법론 중 하나이다. 기존에 테스트 코드의 작성은 프로덕션 코드가 작성된 이후에 이루어졌다. 하지만 TDD 를 적용하면, 프로덕션 코드 보다 실패하는 테스트 코드를 먼저 작성하고, 코드를 테스트를 통과하기 위해 최소한으로 개선한다. 이후 테스트를 통과한 프로덕션 코드를 리팩토링 한다.

TDD 는 테스트만을 위한 기술이 아니다. TDD 는 오히려 소프트웨어 설계 방법론에 가깝다.

TDD의 아이러니 중 하나는 테스트 기술이 아니라는 점이다. TDD는 분석 기술이며, 설계 기술이기도 하다. - 켄트 벡

TDD 사이클

TDD 는 RED → GREEN → REFACTOR 세가지 사이클을 반복하는 일정한 리듬 속에서 진행된다.

1. RED: 테스트 코드 작성

동작하는 프로덕션 코드가 없는 상황에서 테스트 코드를 먼저 작성한다. 앞으로 작성될 프로덕션 코드가 어떤 동작을 하면 좋을지 적는다. 이는 마치 요구사항 같을 것 이다. 당연히 테스트 코드를 먼저 작성하기 때문에 테스트 코드가 기대하는 로직이 실행되지 않아 테스트는 실패할 것 이다. 핵심은 실패하는 테스트 코드를 작성하는 하는 것, 즉 RED 를 띄우는 것이다.

@Test
void plusTest() {
    // given
    Calculator calculator = new Calculator();

    // when
    int result = calculator.plus(1, 3);

    // then
    assertThat(result).isEqualTo(4);
}

위 테스트 코드는 무조건 실패할 것 이다. Calculator 클래스를 작성하지 않았기 때문이다.

2. GREEN: 테스트를 통과하는 최소 코드 작성

방금 작성한 실패하는 테스트 코드를 통과하도록 만드는 최소한의 코드를 작성한다. 지금 필요한 것은 가장 빠르게 GREEN 을 보는 것 이다. 빠르게 구현할 수 있는 깔끔한 코드가 있다면 바로 작성하면 된다. 하지만, 깔끔하게 코드를 작성하는 일이 오래 (몇분 정도) 걸릴 것 같다면 일단 테스트 코드를 통과하는데에만 집중하여 코드를 작성하자. 켄트벡은 이를 죄악이라고 표현한다. GREEN 을 보기 위해서는 어떤 죄악을 저질러도 상관없다.

GREEN 을 보기 위해서는 명백한 실제 구현을 입력하는 방법도 있지만, 최대한 빨리 GREEN 을 보기 위해서는 상수를 반환하는 코드를 작성하고 점진적으로 변수로 바꾸어가는 방법도 있다.

public class Calculator {
    public int plus(int a, int b) {
        return 4;
    }
}

위 테스트 코드는 실제 더하기 연산을 수행하지 않지만, 위에서 작성한 plusTest 를 통과하는 프로덕션 코드이다. 위는 조금 극단적인 예시이긴 하지만, 한번에 하나의 일만 집중하기 위해서는 일단 빠르게 GREEN 을 보는 것이 좋다. 좀 더 복잡한 기능을 요구하는 코드라도 일단 동작하는 쓰레기 코드를 작성하자.

3. REFACTOR: 리팩토링

방금 GREEN 을 통과하기 위해 저지른 죄악 (더러운 코드) 를 수습한다. 빠르게 GREEN 을 보기 위해 작성한 더러운 코드를 좋은 코드로 리팩토링 한다.

public class Calculator {
    public int plus(int a, int b) {
        return a + b;
    }
}

위 코드는 테스트만 통과하기 위한 코드를 리팩토링 한 코드이다.

TDD 의 원칙

  1. 실패하는 단위 테스트를 작성하기 전에는 프로덕션 코드를 작성하지 않는다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

TDD 의 필요성

아직 완벽히 공감한다고 이야기하기는 어렵지만, TDD 를 적용해보며 느낀 TDD 의 필요성을 정리해보면 아래와 같다.

변화에 대한 불안감 해소

프로덕션 코드에 대한 테스트 코드가 이미 작성이 되어 있으므로, 변화에 대한 영향력을 빠르게 파악할 수 있다. 즉, 변경에 의한 문제를 사전에 빠르게 파악할 수 있다. 코드가 예상한 대로 동작한다는 자신감을 얻을 수 있다. TDD 의 핵심은 불안감 해소와 용기가 아닐까 생각한다.

켄트 벡은 TDD 를 '불안함을 지루함으로 바꾸는 마법의 돌' 이라고 표현했다.

한번에 하나의 일만 집중할 수 있다

TDD 를 적용하지 않은 상황에서는 코드를 작성하다, 다른 코드에 한눈이 팔리고, 다른 모듈을 건드리게 되다 보면 '내가 뭘 하고 있었더라?' 라는 생각이 들기 쉽다. TDD 를 적용하면, 프로덕션 코드를 작성하며 현재 실패한 테스트 케이스를 통과하는 데에만 집중할 수 있다. 야크털 깎기 (Yak Shaving) 를 하지 않을 수 있다.

빠른 피드백

TDD 를 적용하면 작성한 테스트 코드로부터 빠른 주기로 피드백 받고, 리팩토링 하며 점진적으로, 지속적으로 코드를 개선할 수 있다. 따라서 버그를 찾는 시점이 빨라질 수 있다. 또한 처음부터 완벽한 설계를 하는 것에 집중하기 보다는 설계를 점진적으로 개선해 나가는데 집중할 수 있다.

테스트 코드 자체가 문서가 될 수 있다

TDD 를 적용하며 작성한 테스트 코드는 다른 관점에서 보면 '프로덕션 코드에 대한 요구사항' 이다. 이는 테스트 코드 자체가 문서가 될 수 있음을 알려준다.

테스트를 나중에 작성하는 것은 귀찮다

프로덕션 코드를 작성한 상태에서 테스트 케이스를 작성하는 것은 귀찮다. 따라서 프로덕션 코드를 작성한 다음 테스트 케이스를 작성하게 되면, 프로덕션 코드에 맞춰 통과하는 테스트 케이스를 만들게 될 수 있다. 이렇게 되면 테스트 코드의 의미가 무색해질수도 있다.

코드 퀄리티

TDD 를 기반으로 테스트 코드를 작성하면, 의존성이 높은 테스트 코드를 작성하기 어려우므로 모듈 간 결합도를 낮추고 응집도를 높일 수 있다.

@Hudi
꾸준히, 의미있는 학습을 기록하기 위한 공간입니다.