회사에서 Golang 응용의 모니터링 라이브러리를 작성하는 업무를 맡게 되었다. 고객이 사용할 라이브러리이기 때문에 panic이나 hang을 절대 일으키면 안 된다는 요구사항이 있었다. 처음 도전하는 낯선 분야인데 엄밀함까지 필요했다. 다행히도 명확한 데드라인은 존재하지 않아 시간을 유연하게 쓸 수 있었다. 전 회사에서 적용하려다 실패한 TDD를 다시 한 번 시도할만한 절호의 기회라는 생각이 들었다. 결론부터 말하면 또 다시 실패하였고 한동안은 적용하지 않으려 한다. 전과 다른 점은 TDD의 효용에 대한 근본적인 의문이 생겼다는 것이다.
TDD의 장점은 이미 많은 자료에서 찾을 수 있으니 굳이 나까지 보탤 필요는 없을 것 같다. 대신 TDD를 실무에 적용하면서 겪었던 어려운 점들을 공유하려 한다.
모니터링 라이브러리의 핵심 코드는 'trace'라는 모듈에 이미 작성되어 있었다. 유닛 테스트를 작성하려면 이 모듈의 특정 구조체를 mocking 해야 했다. Golang은 Duck typing 언어여서 Mocking 자체는 어렵지 않다. 메소드의 매개 변수와 리턴 타입을 그대로 따서 인터페이스를 선언하면 된다.
그런데 인터페이스를 통해 의존성을 주입받는 방식을 기존의 다른 코드에서 사용하고 있지 않았다. 구조체를 직접 사용하는 코드들 가운데 의존성 주입과 Mocking으로 무장한 코드를 끼워넣는 것이 맞는 것일까? TDD를 연습하기 위해 다른 개발자를 당황시키고 싶진 않았다.
TDD 특유의 짧은 피드백은 중독성이 있다. 우선 통과하지 못하던 테스트를 통과하게 만드는 행위 자체가 재미있다. 또 한 번에 긴 코드를 작성할 때에 비해 실수를 금방 발견할 수 있다. 이런 경험을 하다 보니 테스트 결과를 최대한 자주 확인하려는 습관이 생겼다. 그런데 이 습관은 내가 알고 있던 원칙과 부딪혔다.
예를 들어 숫자야구 게임의 AI를 담당하는 BaseballAI 라는 객체가 있다고 생각해보자. 이 객체에서 public으로 드러내야 할 부분은 다음의 두 가지일 것이다.
그런데 위 두 연산 모두 사용하는 알고리즘이 꽤 복잡하다. public 메소드만 테스트 하면서 빠른 피드백을 받을 방법이 잘 생각나지 않는다. 쉽게 생각나는 방법은 원래 private이어야 할 코드를 public으로 드러내어 테스트 하는 방법이다.
예를 들어 플레이어의 공격 내용을 저장하는 동적 배열 'predicts'가 있다고 하자. 플레이어의 공격 이후 predicts 배열에 올바른 값이 추가되는가를 테스트할 수 있다. 그런데 정상적인 설계라면 predicts는 public 멤버가 아니어야 한다. 이런 모순적인 상황이 자주 등장하자 머리가 복잡해졌다.
최근 기존에 작업했떤 MongoDB 관련 라이브러리를 완전히 다시 작성하고 있다. 훨씬 좋은 설계를 발견하게 되었기 때문이다. 기존에 작성해둔 테스트 코드 역시 전혀 쓸모가 없게 되었다. 내가 본 대부분의 자료에서는 프로젝트의 시작부터 TDD를 적용하는 것을 제일 모범적인 방법으로 삼고 있었다. 그런데 코드를 뒤엎는 일은 프로젝트의 초창기에 제일 많이 일어난다.
TDD 사용 초기에는 20% 정도의 시간을 더 들여야 하지만, 개발이 진행될수록 미적용 시의 생산성을 훨씬 초과하게 된다는 말이 있다. 너무 많은 사람들이 이야기해서 근거를 따로 찾아볼 생각을 하지 않았다. 이런 일을 겪고보니 TDD의 생산성을 누가 어떤 방식으로 연구하였는지 궁금해진다. 정말 초창기의 생산성 손실이 20% 밖에 되지 않을까?
내가 아는 한 TDD를 실무에 적용하는 회사는 매우 드물다. 심지어 오픈소스에서도 엄격한 TDD를 하는 프로젝트는 많지 않다. TDD를 적용하기 꺼려하는 개발자들은 '니가 잘 하는 집을 안 가봐서 그래'나 '아 TDD 그렇게 하는거 아닌데...'류의 비판을 자주 듣는다. 최근에는 제대로 된 TDD를 가르쳐주겠다는 유료 강의까지 생겼다.
TDD는 팀원 전체가 사용하지 않으면 그 의미가 많이 퇴색된다. 따라서 이번 달에 입사한 신입 사원도 짧은 교육을 거쳐 사용할 수 있어야 한다. TDD를 제대로 이해하고 적용 경험도 많은 '잘하는 집'으로만 팀을 채울 수 있는 회사가 과연 현실에 존재할까? 이러한 의문이 해소될때까지 TDD 지상락원으로 가는 길에서 하차하고 인간계에서 개발하려 한다.
근본적인 설계가 바뀐다는건 '갈아엎는다' 라는 의미라서 TDD가 문제가 아니에요.
팀원들이 아무도 안한다고 못한다는데 이유가 되진 않아요.
그리고 이틀 이상 개발한다면 TDD를 하는게 당연히 생산성이 압도적으로 높아요.
그래서 많은 기업이 최근엔 TDD가 아닌 BDD를 기반으로한 functional testing 을 진행하는 걸로 알고 있습니다. 말씀하신 대로 unit test 의 경우 격리된 환경에서 테스팅을 진행해야하기 때문에 관련된 모듈은 모킹을 진행하고 리팩토링 시 구조의 종속성이 높아진다는 단점이 있는데요.
시간이 괜찮으시면 unit test 와 functional test 에 대해 찾아보면 좋은 것 같습니다~~!
최근 Unit Testing 이라는 책을 읽고 있는데 이 책에서 두 유닛 테스트 분파를 소개합니다. 말씀하신 Mocking 위주의 TDD 는 그 중 한쪽에만 해당하는 것 같아요. 혹시 본문을 읽고 TDD는 무조건 mocking 을 쓴다고 생각하시는 분이 있을까봐 남깁니다.