TDD 이야기(TDD에 대한 오해와 진실)

TDD 를 현업에서 오랫동안 사용해온 경험많은 필자들이 TDD 적용을 어려워하거나 불필요하다고 생각하는 독자들을 위해 만든 ebook 이다.
비록 작은 책이지만 TDD 가 왜 필요하고 어떻게 적용해 볼 수 있는지에 대한 충분한 설득과 가이드를 담고 있는 책이라고 생각한다.
이 책을 읽는 동안 개인적으로 TDD 에 대한 개념을 다시금 머리 속에서 빠르게 정리해볼 수 있어서 좋은 기억으로 남았다.


TDD(Test-Driven Development)는 소프트웨어 개발방법론 중 하나이다.
쉽게 얘기하면 테스트코드를 먼저 작성한 후 구현하는 방법을 말하는데, 이렇게 테스트코드를 먼저 작성하면 어떤 장점들이 있는지에 대해서 깊이 있게 경험하고 생각해볼 필요가 있다.
TDD 적용에 실패하거나 고생한 경험이 있는 개발자들이 TDD 에 대한 부정적인 의견들을 내는 경우가 많이 있다.
혹시나 이런 경우 TDD 에 대한 행위에만 초점을 맞추고 그 본질적인 목적을 잊었던 것은 아니었는지 되돌아볼 필요가 있다.

먼저 TDD 를 단계적으로 명확히 정의해보자. (p.02)

  1. TDD 는 소프트웨어 개발 방법론 중의 하나이다.
  2. TDD 는 Test-Driven Development, 즉 테스트 주도 개발 방법론이다.
  3. 테스트 주도 개발은 테스트 코드를 먼저 작성함으로써 테스트 코드가 개발을 주도한다.
  4. 테스트 코드가 개발을 주도하기 위해서는 반드시 실패를 포함하는 테스트코드의 작성이 앞서야 한다.
  5. 앞서 작성된 테스트 코드를 통과할 수 있는 ‘최소한의 구현 코드’를 작성한다.
  6. 최소한의 구현 코드는 개선될 수 있는 많은 여지를 포함하고 있는 코드다. 단지 테스트만 패스하면 된다.
  7. 최소한의 구현 코드를 리팩토링 단계에서 개선한다.
  8. 테스트 코드 작성, 최소한의 구현 코드 작성, 구현 코드에 대한 리팩토링 순으로 짧은 주기를 반복하며 점증적으로 개발한다.

즉, TDD 는 테스트 코드 작성 -> 구현 코드 작성 -> 리팩토링 단계를 짧은 주기로 반복하여 개발하는 ‘테스트 주도 개발 방법론’이라고 정의할 수 있다.

TDD 적용을 위해서는 이를 통해 우리가 무엇을 얻을 수 있는지, 그리고 어떤 위험성이 있는지를 명확히 이해해야 한다.
TDD 를 통해 우리가 얻을 수 있는 것들은 무엇일까? (p.03)

  • 단기적인 목표와 장기적인 목표를 뚜렷하게 제시해주고 올바르게 잡아준다.
  • 반복되는 짧은 개발 패턴을 통해 개발 리듬을 만듦으로써 개발 집중력을 높여준다.
  • 테스트 코드는 사용설명서 혹은 API 문서로서 의사소통의 도구로 활용할 수 있다.
  • TDD 를 행함으로써 개발하고 있는 코드의 문제점을 빠르게 잡아낼 수 있다.
  • 성취감은 개발자의 개발 능률을 크게 향상시킨다.

TDD 방법론을 사용하게 되면 초기에 테스트 코드를 작성하는 부담으로 인해 비용이 더 들고 개발 속도를 저하시킨다고 생각하는 사람들이 있다.
하지만, 테스트코드를 먼저 작성하게 되면 개발해야하는 것에 대한 목적과 요소들을 좀 더 명확화할 수 있기 때문에, 구현체 코드를 작성할 때의 고민 시간을 덜어줄 수 있다.
또한, 테스트코드를 작성하기 어렵다는 것은 설계가 잘못되었다는 것을 암시해주기도 한다.
이는 잘못된 설계로 인해 향후 발생할 엄청난 유지 보수 비용을 줄여줄 수 있다는 점을 잊으면 안된다.

만약 요구사항이 변경되는 상황이 발생하면 어떨까?
테스트코드가 없다면 변경된 요구사항들을 구현한 후 연관된 요소들을 모두 테스트했고 문제없을 것이라는 확신을 쉽게 가질 수 있는가?
이는 불가능하다.
TDD 는 코드 수정에 대한 이런 불안감을 떨쳐버리고 즉각적으로 확신을 얻을 수 있게 해주는 엄청난 강점을 가지고 있다.
TDD 를 적용하지 않을 경우 시간이 지나고 소프트웨어의 복잡도가 증가할수록 유지보수 비용이 기하급수적으로 증가하는 것이 일반적이다.
TDD 를 적용하게 되면 일정수준까지는 테스트 코드를 추가로 작성하는 부담때문에 비용이 증가하겠지만, 일정 시간 이후부터는 거의 비용이 증가하지 않고 일정하게 유지된다.

TDD 는 구현보다는 인터페이스에 집중하게 해준다.
즉, 테스트 코드를 먼저 작성하는 것은 목표를 시각화하는 작업이다.
기대하는 입출력 또는 메소드의 시그너처 등을 미리 고민하게 되는데, 이는 모듈 개발자나 사용자에게 모두 도움이 된다.
테스트 코드를 작성하면서 요구사항에 해당하는 객체 들의 관계나 기능에 대해 생각하고, 이는 작은 설계를 구현하면서 기능에 대한 추상화와 모듈화 사고를 유도할 수 있게 해준다.
대상 객체는 어떤 책임이 있으며 이를 실행하기 위해 어떤 부분들을 노출해야 되는지를 고민하는 과정에서, 자연스럽게 객체 지향적인 설계 능력을 향상시킬 수 있다.
TDD 를 수행하다 보면 ‘어떻게’ 보다는 ‘무엇’에 집중하게 된다.
객체가 어떻게 만들어졌는지 책임을 어떤 방식으로 수행하는지 보다는, 객체가 무슨 역할을 가져야 하는지 그리고 이러한 역할을 위해 무엇을 노출해야하는지 등에 집중할 수 있다.

테스트 코드를 작성해야 하는데 필요한 모듈이 미처 완성되지 않은 상황에서는 ‘Mock’ 이라고 부르는 객체를 이용할 수 있다.
Mock 객체를 생성하는 방법에는 두 가지가 있는데, 첫째는 테스트에 필요한 상태를 담고 있는 클래스를 직접 구현하여 인스턴스를 생성하는 방법이고, 둘째는 ‘Mockito’ 같은 Mock 프레임워크를 사용하는 방법이다.
하지만, Mock 객체를 남발해서는 안된다.
Mock 객체는 행위에 대한 기능 검증을 재빠르게 수행할 때는 도움이 되지만, 너무 남용하게 되면 테스트 코드 자체의 가치를 깨뜨릴 수 있기 때문이다.
만약 이전에 유효하던 메시지가 더 이상 유효하지 않은 메시지가 된다면?
이런 상황이 되면 더 이상 기존의 Mock 객체를 이용한 테스트 결과가 시스템이 올바르게 동작할 것이라고 보장할 수 없게 된다.

TDD 와 디자인 패턴은 서로 보완적인 관계이다.
TDD 로 디자인 패턴을 실현하거나, TDD 의 리팩토링 과정에서 문제를 해결하기 위해 적용되는 디자인 패턴이 보다 완벽한 구현체를 만들어내는 것처럼, 이 둘의 관계는 찰떡궁합인 경우가 많다.

테스트 케이스를 작성하다 보면 private 메소드에 대한 테스트 때문에 상당한 고민이 되는 경우들이 있다.
테스트를 작성해야 할까? 한다면 어떻게 해야 하지?
이럴 때는 해당 private 메소드에 대한 리팩토링의 냄새를 의심해보아야 한다.
테스트를 해야 한다는 느낌을 받을 정도로 중요한 역할을 담당하고 있다면, public 으로 이를 변경하거나 새로운 클래스를 하나 생성해 역할을 분리하고 공개할 필요는 없는지 고민해야 한다.

“private 메소드를 테스팅한다는 것은 다른 클래스로 이동해야 한다는 조짐일 수도 있습니다. (Testing private methods may be an indication that those methods should be moved into another class to promote reusability).”

TDD 에 대한 오해와 진실 p.78

private 메소드에 대한 테스트 코드를 작성할 것인가 말 것인가에 대한 가장 간단한 해답은 ‘그냥 하지 않는 것’이다.
왜 우리가 메소드를 private 으로 선언했을까?
혹시 캡슐화를 위해 노출하기 싫은 부분을 작성해둔 것이 아닌가?
만약 세부 구현이 바뀐다면 어떻게 할 것인가?
반드시 테스트를 해야하는데 private 을 포기할 수 없는 경우라면, 우리는 대부분의 경우에 public 메소드를 통해 private 메소드를 간접적으로 테스트할 수 있다.

마지막으로, TDD 를 실무에서 사용하는 데 있어 올바른 사용 및 사용 습관을 4 가지로 정리해본다.

  • Top-Down 으로 방향을 잡고, Bottom-Up 으로 구현에 집중하자.
    이는 Top-Down 방식으로 설계 및 시나리오를 잡고, Bottom-Up 으로 기능에 대한 코드 구현을 구체화시키면서 문제점을 차례차례 해결해나간다.
    즉, “디자인은 Top-Down 으로, 기능은 Bottom-Up 으로”
  • 바보 단계 거치기
    중간 과정에서의 바보같은 코드를 부끄러워할 필요가 없다. 오히려 처음부터 완벽한 코드를 작성하려고 한다면 개발 속도에도 저하가 발생하며 개발자의 정신건강에도 좋지 않다. 일단 클라이언트 관점에서 기대하는 바를 충족시킬 수 있도록 빠르게 구현한 후, 테스트 케이스가 깨지지 않는지 확인하며 리팩토링을 해나간다.
  • 시나리오 구상하기
    Given / When / Then 템플릿을 통해 기대 행위에 대한 명세화에 초점을 맞춘다. 기대 행위에 초점을 맞춰서 생각하면 테스트 메소드 각각의 시나리오를 작성하는 것이 한결 수월해진다.
  • TDD 의 단위 테스트를 문서화하자.
    코드에 구구절절 기록되어 있는 문서는 시간이 지나면서 거짓말을 하게 될 가능성이 높다. 코드와 문서의 일치성이 보장되지 않기 때문이다. 하지만, 테스트 코드는 항상 거의 최신을 유지할 가능성이 높으며, 테스트 코드 자체가 메소드에 대한 사용법이나 예외 등을 담고 있으므로 좋은 샘플 코드로서도 가치가 있다.
    테스트 코드가 종이 문서보다 좋은 점은 두 가지인데, 하나는 일반적으로 테스트 코드는 메소드 당 작성되기 때문에 해당 메소드에 대한 예외 상황을 파악하기 쉽다는 것이다.
    그리고, 또 하나는 다른 구현체와의 의존성을 쉽게 파악할 수 있다는 것이다. Given / When / Then 구조의 코드를 통해 테스트 코드를 읽는 것만으로 별다른 노력없이 의존성이 파악되는 경우가 많다. (물론 가독성이 확보된 테스트 코드에 해당되는 이야기일 것이다.)

You may also like...