이 글에서 분류한 기준은 책의 내용을 바탕으로 주관적인 견해로 재정리해본 것입니다.
TDD
TDD 법칙 세 가지
- 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
방대한 테스트 코드의 문제
- 위 법칙을 모두 지키면서 테스트 케이스를 만들면 실제 코드와 맞먹을 정도로 방대한 테스트 코드가 나온다.
- 이러한 방대한 코드는 심각한 관리 문제를 유발하기도 한다.
깨끗한 테스트 코드
지저분한 테스트 코드
핵심: 테스트 코드는 실제 코드 못지 않게 중요하므로 깨끗하게 짜야 한다.
실제 코드가 진화하면 테스트 코드도 변해야 한다. 그런데, 테스트 코드가 지저분할 수록 변경하기 어려워 진다.
- 테스트 코드가 복잡할 수록 실제 코드를 짜는 시간보다 테스트 코드를 추가하는 시간이 더 걸린다.
- 실제 코드를 변경해 기존 테스트 케이스가 실패했을 경우, 지저분한 코드로 인해 실패하는 케이스를 점점 더 통과시키기 어려워진다.
- 새 버전을 출시할 대마다 팀이 테스트 케이스를 유지하고 보수하는 비용도 늘어난다.
결국 테스트 슈트를 폐기하지 않으면 안 되는 상황에 처한다.
- 테스트 슈트가 없으면 결합율이 높아지기 시작한다.
- 결함 수가 많아지면 개발자는 변경을 주저하고, 더 이상 코드를 정리하지 않는다.
- 코드가 망가지기 시작한다.
깨끗한 테스트 코드
테스트가 제공하는 것
테스트 케이스가 있으면 변경이 두렵지 않다.
테스트는 아래 세 가지를 제공한다.
단위 테스트
실제 코드를 점검하는 자동화된 단위 테스트 슈트는 설계와 아키텍처를 최대한 깨끗하게 보존하는 열쇠다.
그러나 테스트 코드를 깨끗하게 유지하지 않으면 결국은 잃어버린다. 이 세 가지를 제공하는 버팀목이 바로 단위 테스트이다.
- 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다.
- 반대로 말해, 테스트 케이스가 있다면 아키텍처가 부실한 코드나 설계가 모호하고 엉망인 코드라도 우려 없이 변경할 수 있다.
깨끗한 테스트의 핵심은 가독성
- 테스트 코드에서 가독성을 높이려면 명료성, 단순성, 풍부한 표현력이 필요하다.
- 테스트 코드는 최소의 표현으로 많은 것을 나타내야 한다.
- BUILD-OPERATE-CHECK 패턴을 사용하여 잡다하고 세세한 코드를 거의 다 없애고 진짜 필요한 자료 유형만 함수만 사용하도록 한다.
- BUILD: 첫 부분은 테스트 자료를 만든다.
- OPERATE: 두 번째 부분은 테스트 자료를 조작한다.
- CHECK: 세 번째 부분은 조작한 결과가 올바른지 확인한다.
도메인에 특화된 언어 (DSL)
- 흔히 쓰는 시스템 조작 API를 사용하는 대신, API 위에 함수와 유틸리티를 구현한다.
- 이렇게 구현한 함수와 유틸리티는 테스트 코드에서 사용하는 특수 API가 된다.
- 즉, 테스트를 구현하는 당사자와 나중에 테스트를 읽어볼 독자를 도와주는 테스트 언어다.
- 이러한 테스트 API는 처음부터 설계된 API가 아니다.
- 잡다하고 세세한 사항으로 범벅된 코드를 계속 리팩터링하다가 진화된 API다.
이중표준
- 테스트 API 코드에 적용하는 표준은 실제 코드에 적용하는 표준과 확실히 다르다.
- 실제 환경과 테스트 환경은 요구사항이 판이하게 다르다.
- 테스트 코드는 실제 코드만큼 효율적일 필요가 없다.
- 이중 표준의 본질은 실제 환경에서 절대로 안 되지만 테스트 환경에서는 전혀 문제없는 방식이 있다는 것이다.
- 대개 메모리나 CPU 효율과 관련 있는 경우
- 코드의 깨끗함과는 철저히 무관하다.
테스트 코드와 assert 개수
테스트당 assert 하나
- JUnit으로 테스트 코드를 짤 때는 함수마다 assert 문을 단 하나만 사용해야 한다.
- 결론이 하나라서 코드를 이해하기 쉽고 빠르다.
- 테스트 코드를 분리하면 중복되는 코드가 많아지는데, 이 경우엔 Template Method 패턴을 사용하여 중복을 제거할 수 있다.
- given-when-then 관례를 사용한다면 given과 when부분은 부모 클래스에 두고 then 부분은 자식 클래스에 둔다.
- 혹은 완전히 독자적인 테스트 클래스를 만들어
@Before
함수에 given/when 부분을 넣고 @test
함수에 then 부분을 넣어도 된다.
- 하지만 어떠한 방식을 사용하더라도 배보다 배꼽이 더 크다.
테스트 당 assert 여러 개
- 이것저것 감안해 보면 결국 assert 문을 여럿 사용하는 편이 좋다.
- ‘단일 assert 문’이라는 규칙을 지키되, 불가피한 경우 여러 assert 문을 넣는 것이 낫다.
- 그래도 assert 개수는 최대한 줄여야 좋다는 것을 기억하자.
테스트당 개념 하나
- 테스트 함수마다 한 개념만 테스트하라.
- 이렇게 표현하면 장황한 테스트 코드 속에 감춰진 일반적인 규칙이 보인다.
결론
- 테스트 함수 하나는 개념 하나만 테스트 하라
- 개념 당 assert 문 수를 최소로 줄여라
F.I.R.S.T
깨끗한 테스트는 다음 다섯 가지 규칙을 따른다.
- 빠르게 Fast
- 테스트는 빨리 돌아야 한다.
- 테스트는 자주 돌려야 하므로, 빨라야 한다.
- 독립적으로 Independent
- 각 테스트는 서로 의존하면 안 된다.
- 한 테스트가 다음 테스트가 실행될 환경을 준비해서는 안 된다.
- 테스트가 서로에게 의존하면 하나가 실패할 때 나머지도 잇달아 실패하므로 원인을 진단하기 어려워진다.
- 반복가능하게 Repeatable
- 테스트는 어떤 환경에서도 반복 가능해야 한다.
- 실제 환경, QA 환경, 네트워크에 연결되지 않은 노트북 환경 등
- 자가검증하는 Self-Validating
- 테스트는 bool 값으로 결과를 내야 한다. (성공 아니면 실패)
- 통과 여부를 알려고 로그 파일을 읽게 만들거나, 텍스트 파일 두 개를 수작업으로 비교하게 만들어서는 안 된다.
- 적시에 Timely
- 테스트는 적시에 작성해야 한다.
- 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다.
Reference
참고 서적
📔 Clean Code