9장. 단위 테스트
TDD 법칙 세가지
- 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
위 세가지 규칙을 따르면 개발과 테스트가 30초 주기로 묶인다. (즉, 테스트 코드와 실제 코드가 서로 보완되며 지속적으로 개발이 진행됨)
깨끗한 테스트 코드 유지하기
- 테스트는 유연성, 유지보수성, 재사용성을 제공한다.
- 테스트 코드를 깨끗하게 유지하지 않으면 결국은 잃어버린다.
- 테스트 코드를 깨끗하게 유지하기 위해 가장 필요한것은 가독성이다.
이중 표준
실제 환경에서는 절대 안되지만 테스트 환경에서는 전혀 문제없는 방식을 말한다. String을 붙여쓰는 경우는 StringBuffer
를 사용하는것이 좋지만 테스트코드를 실행하는 환경에서는 메모리나 CPU의 스펙이 제한적일 확률이 낮기 때문에 가독성을 위해서라도 String
을 사용해도 된다.
public String getState() {
String state = "";
state += heater ? "H" : "h";
state += blower ? "B" : "b";
state += cooler ? "C" : "c";
state += hiTempAlarm ? "H" : "h";
state += loTempAlarm ? "L" : "l";
return state;
}
테스트 당 assert 하나
테스트 당 assert를 하나만 넣게 되면 함수가 하나라서 코드를 이해하기 쉬울 수 있다.
하지만 로직을 나눠야할 것 같은 경우에는 assert 하나만 사용하기엔 불편하게 느낄 수도 있다.
그럴 땐 아래처럼 테스트를 쪼개서 각자가 assert를 수행하면 된다.
public void testGetPageHierarchyAsXml() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldBeXML();
}
public void testGetPageHierarchyHasRightTags() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldContain(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
}
하지만 코드를 두개로 쪼개면 위와 같이 중복되는 코드가 생기기 마련이다.
이럴 때는 TEMPLATE METHOD
패턴을 사용하면 중복을 제거할 수 있다.
give-when
부분을 부모 클래스에 두고 then
부분만 자식 클래스에 두면 된다.
하지만 이러한 것들은 배보다 배꼽이 더 크다. 최대한 간단하게 테스트코드를 구현하는것이 좋다.
물론 이것들은 본인이 선택하면 된다. 꼭 위처럼 할 필요도 없고, assert문을 하나만 유지할 필요도 없다.
테스트 당 개념 하나
테스트 함수마다 한 개념만 테스트 하라라는 규칙이라고 볼 수 있다.
아래와 같이 연속으로 검증을 하는 것은 피하는 것이 좋다.
세 개념을 한 함수로 몰아넣으면 독자가 각 절이 거기에 존재하는 이유와 각 절이 테스트하는 개념을 모두 이해해야 한다.
public void testAddMonths() {
SerialDate d1 = SerialDate.createInstance(31, 5, 2004);
SerialDate d2 = SerialDate.addMonths(1, d1);
assertEquals(30, d2.getDayOfMonth());
assertEquals(6, d2.getMonth());
assertEquals(2004, d2.getYYYY());
SerialDate d3 = SerialDate.addMonths(2, d1);
assertEquals(31, d3.getDayOfMonth());
assertEquals(7, d3.getMonth());
assertEquals(2004, d3.getYYYY());
SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1));
assertEquals(30, d4.getDayOfMonth());
assertEquals(7, d4.getMonth());
assertEquals(2004, d4.getYYYY());
}
F.I.R.S.T
깨끗한 테스트는 다음 다섯가지 규칙을 따른다.
Fast (빠르게)
테스트는 빨라야 한다. 테스트는 빨리 돌아야 한다는 말이다. 빨리 돌아야 자주 테스트해볼 수 있다.
Independent (독립적으로)
각 테스트는 서로 의존하면 안된다.
테스트가 서로에게 의존하면 하나가 실패할 때 나머지도 잇달아 실패하므로 원인을 진단하기 어려워진다.
Repeatable (반복 가능하게)
테스트는 어떤 환경에서도 반복 가능해야 한다.
Self-Validating (자가검증하는)
테스트는 부울 값으로 결과를 내야 한다. 성공 아니면 실패다.
통과 여부를 알려고 로그 파일을 읽게 만들어서는 안 된다.
Timely (적시에)
테스트는 적시에 작성해야 한다.
실제 코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 테스트하기 어렵다는 사실을 발견할지도 모른다.
혹은 테스트가 불가능하도록 실제 코드를 설계할지도 모른다.
결론
- 테스트 코드는 실제 코드만큼이나 중요하다.
- 테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화해준다.
- 테스트 코드의 표현력을 높이고 간결하게 정리하자.