[번역] TDD 변절자: TDD는 설계 기법이 아니다.
얼마 전, 페이스북에서 트립스토어 CTO 이신 규원님의 글을 보았다. ‘TDD는 안 해도 되지만 설계는 안하면 안돼요.’ 라는 글과 함께 본인의 블로그 글을 공유하셨다. TDD를 적극적으로 공부하게끔 영향을 주신 분의 글이기에 블로그 글을 정독했고, 마지막에 소개해주신 글 또한 정독해보고자 하는 마음에서 Mark Seemann의 ‘The TDD Apostate’라는 글을 번역했다.
나는 2003년부터 테스트 주도 개발(Test-Driven Development)을 하고 있다. 여전히 테스트 주도 개발을 좋아하며, 앞으로도 계속할 것으로 생각한다. 수년간 테스트 주도 개발을 테스트 주도 개발(Test-Driven Development)로 간주할지 혹은 테스트 주도 설계(Test-Driven Design)로 간주해야 할지 논의가 반복됐다. 나는 오랜 시간 테스트 주도 개발은 둘 다를 의미한다고 생각했지만, 더 이상은 그렇게 생각하지 않는다.
테스트 주도 개발은 좋은 설계 방법론이 아니다.
나는 수년간 테스트 주도 개발을 통해 정말 많은 코드를 작성해왔다. 테스트가 설계를 움직이는 코드를 작성해왔고, 오랜 시간 신중함의 결과인 설계를 위한 코드를 작성해왔으며, 테스트는 이미 잘 짜여진 아이디어의 표현일 뿐이었다.
테스트만으로 설계를 작성한 코드는 특별히 좋은 결과가 아니었다고 말할 수 있다. 비록 테스트가 가능하고 어느 정도 느슨한 결합이라는 결과가 있었지만, 일관성 및 좋은 추상화 관점에서는 여전히 스파게티 코드(Spaghetti Code)였다.
반면에, 자전거로 출퇴근하는 몇 시간 동안 신중하게 생각한 결과였던 AutoFixture 2.0와 같은 결과물은 매우 만족스럽다. 이 역시 코드는 테스트 우선으로 작성되었지만, 설계는 미리 충분히 고려된 것이었다.
나는 이런 생각을 하게 되었다: 나는 테스트 주도 설계에 실패한 것인가? 아니면 전체적으로 개념을 잘못 생각하고 있던 것일까?
이것은 대답하기 쉬운 질문이 아니다. 무엇이 좋은 설계를 만들까? SOLID 설계 원칙이 좋은 설계를 위한 꽤 좋은 지표라고 가정해보자. 그렇다면, 테스트 주도는 우리를 SOLID 설계로 이끌 수 있을까?
테스트 주도 개발 vs 단일 책임 원칙(Single Responsibility Principle)
테스트 주도 개발은 ‘단일 책임 원칙’의 적용을 보장할 수 있을까? 이 질문에 대한 대답은 ‘아니다!’ 라고 어렵지 않게 이야기할 수 있다. 테스트 주도 개발이 ‘God 클래스’로 가는 것을 전혀 예방하지 못하는 사례를 많이 보았고, 나 역시 그러했다.
오히려 단일 책임 원칙 위배를 더 어렵게 만드는 생성자 주입(Constructor Injection)이 도움이 된다. (역주: 생성자 주입을 위한 매개변수가 많아질 때, 해당 클래스에서 많은 책임을 지고 있다는 신호로 받아들일 수 있기 때문이라고 생각)
현재까지 테스트 주도 개발의 점수: 0
- 단일 책임 원칙: 하나의 클래스는 하나의 책임을 가지며, 변경 이유 역시 하나여야 함
- God 클래스: 너무 많은 책임과 역할을 담당하고 있는 클래스
테스트 주도 개발 vs 개방 폐쇄 원칙(Open-Closed Principle)
테스트 주도 개발은 ‘개방 폐쇄 원칙’을 따르도록 유도할까? 이에 대한 대답은 조금 어렵다. 나는 예전에 테스트 용이성(Testability)이 개방 폐쇄 원칙의 다른 이름일 뿐이라고 주장했으며, 이는 테스트 주도 개발이 개방 폐쇄 원칙을 이끈다는 의미를 내포한다. 하지만, 개방 폐쇄 원칙을 다루기 위한 몇 가지 방법들이 존재하기 때문에 이 문제는 단순하게 대답할 수 없다.
- 상속 (Inheritance)
- 합성 (Composition)
Roy Osherove의 책 The Art of Unit Testing에 따르면, 추출(Extract) 및 오버라이드(Override)는 일반적으로 단위 테스트의 요령이다. 개인적으로는 잘 사용하지 않지만, 상속을 통해 간접적으로 개방 폐쇄 원칙으로 향할 수 있다.
하지만 우리는 상속보다는 합성을 선호해야 한다는 사실을 알고 있다.(역주: 상속을 적절히 사용하지 못했을 때 상위 클래스에 깊게 의존적인 하위 클래스를 만들 수 있는 등의 문제 때문이라고 생각) 과연 테스트 주도 개발은 이 방향으로 갈 수 있을까? 예전에 언급했듯이, 테스트 주도 개발은 테스트 대역(Test Double)을 사용하는 경향이 있고, 테스트 대역은 합성을 통해 개방 폐쇄 원칙을 달성하는 방법 중 하나이다.
내가 좋아하는 또 다른 합성을 위한 기술은 ‘데코레이터(Decorator) 패턴’을 이용하여 기능을 추가하는 것이다. 이 기술은 원래의 타입이 인터페이스를 구현하는 경우에만 가능하다. ‘테스트 대상(System Under Test, SUT)’이 인터페이스를 구현하도록 테스트를 작성할 수는 있지만, 테스트 주도 개발 자체가 이 방향으로 이끄는 것은 아니다.
하지만, 테스트 주도 개발이 개방 폐쇄 원칙에 대해 일부 점수를 얻어야 함을 부정할 수는 없다.
현재까지 테스트 주도 개발의 점수: 0.5
- 개방 폐쇄 원칙: 확장에는 열려있고, 수정에는 닫혀있어야 한다는 원칙
- 데코레이터 패턴: 객체에 추가적인 요건을 동적으로 추가할 때 이용하는 패턴
테스트 주도 개발 vs 리스코프 치환 원칙(Liskov Substitution Principle)
테스트 주도 개발은 ‘리스코프 치환 원칙’을 따르도록 유도할까?
블랙박스 테스트는 테스트 대상(SUT)이 종속성에 깊게 관여하는 것을 막을 수는 없지만, 적어도 그 방향으로 이끌지는 않는다. 테스트 주도 개발은 테스트 대상의 종속성에 대해 어느 방향으로도 이끌지 않는다.
실수로 리스코프 치환 원칙을 위반하는 인터페이스 구현을 테스트할 수 있을까? 물론, 쉽게 가능하다. 이전 글에서 논의했듯이, 헤더 인터페이스(Header Interface)의 사용은 리스코프 치환 원칙 위반으로 쉽게 끌어들인다. 인터페이스에 구성 요소가 많을 때, 리스코프 치환 원칙 위반 가능성은 커진다.
테스트 주도 개발은 헤더 인터페이스로 이끌 가능성이 크며, 실제로 나는 이러한 일을 여러 번 겪었다. 테스트 주도 개발은 리스코프 치환 원칙 준수를 권장하지 않는다.
현재까지 테스트 주도 개발의 점수: 0.5
- 리스코프 치환 원칙: 상위 타입을 하위 타입으로 치환해도 상위 타입을 사용하는 프로그램은 정상 동작해야 한다는 원칙
테스트 주도 개발 vs 인터페이스 분리 원칙(Interface Segregation Principle)
테스트 주도 개발은 ‘인터페이스 분리 원칙’을 지향할까? 그렇지 않다. 테스트 주도 개발은 쉽게 헤더 인터페이스가 되도록 테스트 대상(SUT)을 테스트할 수 있다.
현재까지 테스트 주도 개발의 점수: 0.5
- 인터페이스 분리 원칙: 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 말아야 한다는 원칙 (한 개의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스)
테스트 주도 개발 vs 의존성 역전 원칙(Dependency Inversion Principle)
테스트 주도 개발은 ‘의존성 역전 원칙’을 따르도록 유도할까? 그렇다.
테스트 용이성(Testability)을 위한 모든 노력 — 즉, 테스트 대역(Test Double)으로 의존성을 대체할 수 있는 능력 — 은 의존관계 역전 원칙과 같은 방향으로 이끈다.
우리는 이러한 느슨한 결합(loose coupling)을 적절한 애플리케이션 설계로 착각하는 경우가 있고, 이것이 테스트 주도 개발과 좋은 설계를 혼동하는 이유이다. 느슨한 결합을 좋은 설계를 위한 전제 조건으로 생각하지만, 이 자체로는 충분하지 않다.
현재까지 테스트 주도 개발의 점수:1.5
- 의존성 역전 원칙: 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다는 원칙
테스트 주도 개발은 SOLID 설계 원칙을 보장하지 않는다.
테스트 주도 개발에게 5점 만점에 1.5점의 점수를 줬다. 나는 테스트 주도 개발 자체가 SOLID 설계 방향으로 이끌지 않는다고 확신한다. SOLID 설계를 지향하기 위해 테스트 주도 개발을 사용하는 것은 가능하지만, 테스트 주도 개발 자체로 부족한 것을 보완하려는 노력을 항상 병행해야 한다. 이는 테스트 주도 개발 자체에 본질적으로 포함된 개념이 아니기 때문이다.
분명, SOLID 설계 원칙 자체가 적절한 API 설계를 위한 전부가 아니라고 주장할 수 있으며, 나 역시 동의한다. 하지만 테스트 주도 개발에 대한 나의 경험을 기반으로 나는 결론을 내릴 수 있다. 테스트 주도 개발은 우리를 좋은 설계로 이끌지 않으며, 이는 설계 기법이 아니다.
나는 좋은 생산성을 위해 여전히 테스트 코드를 먼저 작성하지만, 설계는 해당 영역 밖에서 결정한다. 나는 테스트 주도 개발 변절자다.
오역 혹은 설명이 부족한 내용은 계속해서 보완할 예정입니다. 읽으시는 분들의 피드백 환영합니다.