개발자는 코드를 작성하고 빌드하여 이를 사용자가 사용할 수 있게 배포한다. 사용자는 배포한 소프트웨어를 이용해서 원하는 기능을 실행한다. 우리가 코드를 작성하는 이유는 사용자가 사용할 어떤 기능을 제공하기 위함이다.
기능에 대한 명세는 다양한 형태로 존재한다. 파워포인트를 이용한 스토리보드 형태도 있고 이메일과 같은 간단한 문장 형태도 있다. 지라와 같은 이슈를 통해 추가할 기능의 명세를 받기도 한다. 또는 회의 자리에서 기능 명세를 구두로 전달받을 때도 있다.
어떤 형태가 되든 간에 사용자에게 제공할 기능을 구현하려면 긴으을 크게 두 가지로 나누어 생각해 볼 수 있다. 바로 입력과 결과이다. 예를 들어 로그인 기능을 생각해보자. 로그인 기능에서 입력과 결과는 다음과 같이 정의할 수 있다.
입력은 보통 메서드의 파라미터로 전달한다. 결과는 여러 형식으로 정의할 수 있다. 가장 쉽게 생각할 수 있는 형식은 리턴 값이고 예외를 결과로 사용할 수도 있다.
설계는 기능 명세로부터 시작한다. 스토리보드를 포함한 다양한 형태의 요구사항 문서를 이용해서 기능 명세를 구체화한다. 그 동안 입력과 결과를 도출하고 이렇게 도출한 기능 명세를 코드에 반영한다. 이 과정에서 기능의 이름, 파라미터, 리턴 타입 등이 결정된다. 이는 곧 기능에 대한 설계 과정과 연결된다.
TTD는 테스트를 만드는 것부터 시작한다. 테스트 코드를 만들기 위해 다음 두 가지를 할 수 있어야 한다.
먼저 기능을 실행할 수 없으면 테스트를 할 수 없다. 그래서 테스트에서 실행할 수 있는 객체나 함수가 존재해야 한다.
실행할 객체가 존재하려면 객체를 생성할 때 사용할 클래스가 필요하고 실행할 메서드도 필요하다. 다시 말해 테스트 대상이 되는 클래스와 메서드 명을 결정해야 한다. 또한, 메서드를 실행할 때 사용할 인자의 타입과 개수를 결정해야 한다.
테스트는 실행 결과를 검증해야 하므로 이를 어떻게 검증할 수 있는지, 리턴 타입을 고민해야 한다.
위 4 가지를 결정하는 과정이 곧 설계 과정이다. TDD에서 테스트 코드를 작성할 때 고민하는 것과 설계 과정에서 고민하는 것은 겹치는 부분이 존재한다.
이름은 설계에서 매우 중요하다. 설계 과정에서 구현하는 기능을 정확하게 표현하는 이름을 사용하는 것만큼 중요한 것은 없다. 레거시 코드를 보면 이름에서 기대하는 것과 다르게 동작하는 코드를 종종 만나게 된다. 이런 이름은 개발자를 속일 뿐만 아니라 코드를 분석하는 시간을 증가시켜 코드 수정을 어렵게 만드는 원인이 된다.
테스트 코드는 시작부터 이름을 고민하게 만든다. 시간이 다소 걸리더라도 알맞은 이름을 찾아야 한다. 이름을 고민하는 시간을 아까워하지 말자.
TDD로 개발을 진행하면 현시점에서 테스트를 통과시키는데 필요한 만큼의 코드만 만들게 된다. 물론 모든 코드에 대해 테스트를 먼저 작성할 수는 없겠지만 TDD로 개발하는 코드 비율이 높아질수록 지금 시점에서 필요한 설계만 코드에 반영할 가능성이 커진다. 유연한 설계는 필요한 시점에 추가한다. 이를 통해 설계가 불필요하게 복잡해지는 것을 방지할 수 있다.
테스트 코드를 작성하기 위해 개발자는 기능 명세를 정리해야 한다. 보통 개발자는 기획자가 작성한 스토리보드나 와이어프레임과 같은 형태로 요구사항 명세를 전달받는다. 이런 문서는 사용자나 기획자가 보기에는 적당할지 모르나 개발자가 기능을 구현하기에는 생략된 내용이 많다.
테스트 코드를 작성하려면 입력과 결과가 명확해야 하므로 애매한 점을 발견하면 기획자나 실무 담당자와 얘기해서 상황에 따라 기능이 어떻게 동작해야 하는지 구체적으로 정리해야 한다. 개발자는 예를 통해 기능 명세를 구체화하게 된다.
또한 테스트 코드는 바로 실행할 수 있다. 이는 유지보수에 큰 도움이 된다. 특정 상황에서 코드가 어떻게 동작하는지 이해하고 싶다면 해당 상황을 검증하는 테스트를 실행하고 이해가 필요한 코드를 추적하면 된다. 디버거를 이용해서 추적할 수 있고 콘솔에 출력되는 로그를 보면서 이해를 높일 수 있다.