9장. 단위 테스트

TDD 법칙 세가지

  1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

위 세가지 규칙을 따르면 개발과 테스트가 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 (적시에)

테스트는 적시에 작성해야 한다.
실제 코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 테스트하기 어렵다는 사실을 발견할지도 모른다.
혹은 테스트가 불가능하도록 실제 코드를 설계할지도 모른다.

결론

  • 테스트 코드는 실제 코드만큼이나 중요하다.
  • 테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화해준다.
  • 테스트 코드의 표현력을 높이고 간결하게 정리하자.
Last Updated: