테스트 코드는 실제 코드만큼, 어쩌면 실제 코드보다 더 중요할 수 있다.
테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존 강화한다.
테스트 코드도 클린하게 유지해야 실제 코드가 망가지지 않는다.
실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
이를 통해 테스트 코드와 실제 코드가 함께 나오고, 실제 코드를 전부 테스트할 수 있게 된다.
지저분한 테스트 코드를 만드는 것은 테스트를 하지 않는 것만 못하다.
실제 코드처럼 테스트 코드도 깨끗하게 짜야 한다.
깨끗한 테스트 코드는 깨끗한 실제 코드와 마찬가지로 가독성이 최우선이다.
최소한의 표현으로 많은 것을 나타내야 한다.
// BAD
public void testGetPageHierarchyAsXml() trhows Exception
{
crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(
new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
}
위 예시는 중복된 코드가 많고 자질구레한 사항이 많아 표현력이 떨어지므로 아래와 같이 수정하는 것이 좋다.
// GOOD
public void testGetPageHierarchyAsXml() trhows Exception {
// BUILD
makePages("PageOne", "PageOne.ChildOne", "PageTwo");
// OPERATE
submitRequest("root", "type:pages");
// CHECK
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}
수정된 코드는 BUILD-OPERATE-CHECK 패턴을 사용해 수행하는 작업을 명확히 나누었다. 또한 잡다한 코드를 없애, 정말 필요한 자료 유형과 함수만 사용하고 있다.
도메인 특화된 언어
수정된 코드에서는 도메인 특화된 언어(DSL)을 통해 API 위에 함수와 유틸리티를 구현한 후 그 함수와 유틸리티를 사용함으로써 테스트 코드를 짜기도, 읽기도 쉽게 만들었다.
이중 표준
테스트 코드는 실제 코드만큼이나 깔끔해야 하지만, 효율성의 측면에서는 실제 코드만큼 효율적일 필요는 없다.
assert문이 하나이면 코드를 이해하기 쉽고 빠르다.
다만 이를 위해 테스트를 쪼개야 할 경우 중복되는 코드가 많아진다는 단점이 있다.
이 때는 TEMPLATE METHOD 패턴을 사용해 중복을 제거할 수 있다.
'테스트 당 assert 하나' 원칙은 절대적인 것은 아니지만 assert문은 최대한 줄이는 것이 바람직하다.
Fast: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다. 자주 돌려야 초반에 문제를 발견하기 쉽다.
Independent: 각 테스트는 독립적이어야 한다. 테스트가 서로 의존하면 하나가 실패할 때 나머지도 잇따라 실패하므로 원인을 진단하기 어려워진다.
Repeatable: 테스트는 어떤 환경에서도 반복 가능해야 한다. (네트워크가 없는 환경에서도)
Self-validating: 테스트는 bool 값으로 결과를 내야 한다. 성공 or 실패.
Timely: 테스트는 적시에 작성한다. 여기서 적시는 실제 코드를 구현하기 직전이다.