- 아주 예전 개발자들은 실제 코드가 돌아간다는 사실을 확인하고 테스트 코드를 버렸다.
- 하지만 지금 같은 경우엔 코드가 제대로 도는지 황긴하는 테스트 코드를 수 없이 작성하고, 모든 테스트 케이스를 통과한 후엔 실제 제품 코드와 같은 소스 패키지로 확실하게 묶는다.
- 하지만 많은 프로그래머들이 제대로 된 테스트 케이스를 작성해야 한다는 좀 더 미묘한 (그리고 더욱 중요한) 사실을 놓쳐버렸다.
TDD 세 가지 법칙
- TDD 는 실제 코드를 짜기 전에 단위 테스트부터 짜라고 요구한다.
- 이 외에 다음 세 가지 법칙을 살펴보자.
- 1)첫번째 법칙: 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 2)둘째 법칙: 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 3)셋째 법칙: 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
- 위 세 가지 규칙을 따르면 개발과 테스트가 대략 30초 주기로 묶인다.
- 테스트 코드와 실제 코드가 함께 나올뿐더러 테스트 코드가 실제 코드보다 불과 몇 초 전에 나온다.
- 이렇게 일하면 매일 수십개, 매달 수백개, 매년 수천 개에 달하는 테스트 케이스가 나온다.
- 이렇게 이랗면 실제 코드를 사실상 전부 테스트하는 테슽트 케이스가 나온다.
- 하지만 실제 코드와 맞먹을 정도로 방대한 테스트 코드는 심각한 관리 문제를 유발하기도 한다…
✨깨끗한 테스트 코드 유지하기✨
- 지저분한 테스트 코드를 내놓는 것은 테스트를 안 하는 것보다 더 못하다.
- 문제는 실제 코드가 진화하면 테스트 코드도 변해야 한다는데 있다.
- 그런데 테스트 코드가 지저분할수록 변경이 어려워진다.
- 테스트 코드가 복잡할수록 실제 코드를 짜는 시간보다 테스트 케이스를 추가하는 시간이 더 걸리기 십상이다.
- 실제 코드를 변경해 기존 테스트 케이스가 실패하기 시작하면, 지저분한 코드로 인해, 실패하는 테스트 케이스를 점점 더 통과하기 어려워진다.
- 그래서 테스트 코드는 계속해서 늘어나는 부담이 된다..
- 그렇다고 테스트 슈트가 없으면 개발자는 자신이 수정한 코드가 제대로 도는지 확인할 방법이 없다.
- 시스템 이쪽을 수정해도 저쪽이 안전하다는 사실을 검증하지 못한다.
- 그래서 시스템이 커질수록 결함율이 높아지기 시작하고 의도하지 않은 결함 수가 많아지면 개발자는 변경을 주저하게 된다.
- 변경하면 득보다 해가 더 크다 생각해 더 이상 코드를 정리하지 않게 되고 코드가 망가지기 시작한다..
- 결국 테스트 슈트도 없고, 얼기설기 뒤섞인 코드에, 좌절한 고객과 테스트에 쏟아 부은 노력이 허사였다는 실망감만 남게 된다….
- 이러한 현상의 근본적인 원인은 테스트 코드를 막 짜도 좋다고 허용한 결정 및 마인드다.
- 필자가 위와 같이 얘기하는 이유는 필자가 참여하고 조언한 많은 팀이 깨끗한 단위 테스트 코드로 성공했기 때문이다.
- 테스트 코드는 실제 코드 못지 않게 중요하고 깨끗하게 짜야한다.
- 이류 시민이 아니다.
- 사고와 설계와 주의가 필요하다.
테스트는 유연성, 유지보수성, 재사용성을 제공한다.
- 테스트 코드를 깨끗하게 유지하지 않으면 결국은 잃어버리고 테스트케이스가 없으면 실제 코드를 유연하게 만드는 버팀목도 사라진다.
- 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위 테스트다.
- 이유는 단순하다. 테스트 케이스가 있으면 변경이 두렵지 않기에!!!
깨끗한 테스트 코드
- 가독성만 있으면 되고 이는 명료성, 단순성, 풍부한 표현력이 필요하다.
- 테스트 코드는 최소의 표현으로 많은것을 나타내야 한다.
- 테스트 코드와 무관하며 테스트 코드의 의도를 흐리는것은 제거해야한다. (p.159 목록9-1 참고)
- BUILD-OPERATE-CHECK 패턴을 위 케이스에 적절히 활용하자. 첫 부분은 테스트 자료를 만들고 두번째 부분은 테스트 자료를 조작하며, 세번째 부분은 조작한 결과가 올바른지 확인하는 패턴이다. (p.160 목록9-2 참고)
- 코드를 읽는 사람을 위해 잡다하고 세세한 코드를 없애도록 하자. 테스트 코드는 본론에 돌입해 진짜 필요한 자료 유형과 함수만(ex. ` WikiPage page = makePage(“pageOne”); makePages(“PageOne.ChildOne”, “PageTwo”);`)사용한다.
이중 표준
- 테스트코드는 단순하고, 간결하고, 표현력이 풍부해야 하지만, 실제 코드만큼 효율적일 필요는 없다.
- 실제 환경이 아닌 테스트 환겨엥서 돌아가기 때문이다.
- 일부 그릇된 정보를 나타내는 테스트 코드더라도 적절할때가 있다. 일단 의미만 안다면 눈길이 문자열을 따라 움직이며 결과를 재빨리 판단하게 되기 때문인다. 테스트 코드를 읽기가 즐거워지고 이해하기 쉬워진다. (p.162 목록9-4)
- StringBuffer 는 보기흉한다. 실제 코드에서도 저자는 크게 무리가 가지 않는선에서 StringBuffer를 피한다. 하지만 테스트 코드에선 자원이 제한적일 가능성이 낮기에 큰 상관없다.
- 이것이 ‘이중 표준’의 본질이다. 실제 환경에선 절대로 안되지만 테스트 환경에선 전혀 문제 없는 방식이 있다. 대개 메모리나 CPU 효율과 관련 있는 경우다. 코드의 깨끗함과는 철절히 무관한다.
테스트당 assert 하나
- 함수마다 assert문을 단하나만 사용해야 한다고 주장하는 학파가 있다. 결론이 하나라 코드를 이해하기 쉽고 빠르다는 주장이다.
- 하지만 그렇게 하면 중복 코드가 많아져 templte method 패턴과 @Before 함수에 given, when 절을 넣어 중복 코드를 해결하게되는데 이는 배보다 배꼽이 더 큰 격이다.
- 이를 감안하면 저자는 assert 문을 여럿 사용하는 편이 좋다 생각하며 최대한 assert 문을 줄이는걸 지양해야 한다고 한다.
테스트당 개념 하나
- 가장 좋은 규칙은 “개념 당 assert 문 수를 최소로 줄여라”와 “테스트 함수는 하나는 개념 하나만 테스트하라” 이다.
FIRST
- Fast(빠르게): 테스트는 빨라야 한다. 느리면 자주 돌릴 엄두를 못 내게 되어 초반에 문제를 찾아 고치지 못한다. 그리고 코드를 마음껏 정리하지도 못하게 된다.
- Independent(독립적으로): 각 테스트는 서로 의존하면 안된다. 한 테스트가 다음 테스트가 실행될 환경을 준비해선 안된다. 이렇게 되면 원인이 어디서 발생했는지 찾기 어려워진다.
- Repeatable(반복 가능하게): 어떤 환경에서든 반복 가능해야 한다. 테스트가 실패한 이유를 환경 탓으로 돌리게 해선 안된다.
- Self-Validating(자가 검증하는): 부울(bool)값으로 결과를 내야 한다. 성공 아니면 실패다. 통과 여부를 알려고 로그 파일을 읽어선 안된다. 테스트 성공 여부에 대한 판단이 주관적이 되어 수작업 평가가 필요해져선 안된다.
- Timley(적시에): 적시에 작성해야 한다. 단위 테스트는 실제 코드를 구현하기 직전에 구현한다. 실제 코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 어렵다는 사실을 발견할지도 모른다.
결론
- 테스트 코드는 실제 코드만큼이나 프로젝트 건강에 중요하다.(어쩌면 더 중요할지도)
- 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화하기 때문이다.
- 그러므로 테스트 코드는 지속적으로 깨끗하게 관리하자. 표현력을 높이고 간결하게 정리하자.
- 테스트 API를 구현해 도메인 특화 언어(Domain Specific Language, DSL)를 만들자. 그러면 그만큼 테스트 코드를 짜기가 쉬워진다.
Reference
- 예제 코드 및 이미지 출처