[실용주의 단위 테스트] 6장_무엇을 테스트할 것인가?

무엇을 테스트해야하는지 정리한다.
이것으로 쉽게 요약할 수 있다 : Right-BICEP

  1. Right : 결과가 올바른가 ?
  2. B : Boundary. 경계 조건은 맞는가 ?
  3. I : Inverse relationship. 역관계를 검사할 수 있나 ?
  4. C : Cross check. 다른 수단을 활용해서 교차 검사 가능한가 ?
  5. E : Error. 오류를 강제로 발생시킬 수 있는가 ?
  6. P : Performance. 성능 조건은 기준에 부합하나 ?

1. Right

테스트 코드는 무엇보다도 먼저, 기대한 결과를 산출하는지 검증 해야한다.
다음 코드의 ScoreCollection 에 더 많은 숫자나 더 큰 수를 넣어서 테스트를 강화할 수 있다. 하지만 이러한 테스트는 행복 경로 테스트의 영역일 뿐이다.

1
2
3
4
5
6
7
8
9
10
11
12
public class ScoreCollectionTest {
@Test
public void answersArithmeticMeanOfTwoNumbers() {
ScoreCollection collection = new ScoreCollection();
collection.add(() -> 5);
collection.add(() -> 7);

int actualResult = collection.arithmeticMean();

assertThat(actualResult, equalTo(6));
}
}

2. Boundary

대부분의 결함은 Corner Case 이다. 테스트로 이것들을 처리해야한다. 다음과 같은 경계 조건이 있다.

  1. 모호하고 일관성 없는 입력 값. ex) 특수문자가 포함된 파일 이름
  2. 잘못된 양식의 데이터
  3. Overflow 를 일으키는 계산
  4. 비거나 빠진 값. ex) 0, “”, null
  5. 이성적인 기대 값을 벗어나는 값. ex) 200 세의 나이
  6. 중복을 허용해서는 안되는 목록에 중복 값이 있는 경우
  7. 정렬이 안된 정렬 리스트 혹은 그 반대
  8. 시간 순이 맞지 않는 경우. ex) HTTP Server 가 OPTIONS 메서드의 결과를 POST 메서드 보다 나중에 반환

다음 클래스를 기준으로 테스트해보자.

1
2
3
4
5
6
7
8
9
10
11
12
public class ScoreCollection {
private List<Scoreable> scores = new ArrayList<>();

public void add(Scoreable scoreable) {
scores.add(scoreable);
}

public int arithmeticMean() {
int total = scores.stream().mapToInt(Scoreable::getScore).sum();
return total / scores.size();
}
}

Scoreable 인스턴스는 null 일 수 있다. 다음 테스트 코드로 확인 가능하다.

1
2
3
4
@Test(expected=IllegalArgumentException.class)
public void throwsExceptionWhenAddingNull() {
collection.add(null);
}

add() 에 Guard Clause 를 넣어 입력 범위를 다음과 같이 분명하게 하자.

1
2
3
4
public void add(Scoreable scoreable) {
if (scoreable == null) throw new IllegalArgumentException();
scores.add(scoreable);
}

또한, ScoreCollection 객체에 Scoreable 인스턴스가 없을 수도 있다. ArithmeticExeptoin 이 발생할 수 있다.

1
2
3
4
@Test
public void answersZeroWhenNoElementsAdded() {
assertThat(collection.arithmeticMean(), equalTo(0));
}

다음과 같이 조건문을 추가하자.

1
2
3
4
5
6
7
public int arithmeticMean() {
if (scores.size() == 0) return 0;
// ...

long total = scores.stream().mapToLong(Scoreable::getScore).sum();
return (int)(total / scores.size());
}

또한, 큰 정수 입력을 다루면 숫자들의 합이 Integer.MAX_VALUE (2147483647) 를 초과할 수 있다.

1
2
3
4
5
6
7
@Test
public void dealsWithIntegerOverflow() {
collection.add(() -> Integer.MAX_VALUE);
collection.add(() -> 1);

assertThat(collection.arithmeticMean(), equalTo(1073741824));
}

다음과 같이 long type 을 사용하면 된다.

1
2
3
4
5
6
7
public int arithmeticMean() {
if (scores.size() == 0) return 0;
// ...

long total = scores.stream().mapToLong(Scoreable::getScore).sum();
return (int)(total / scores.size());
}

3. Inverse relationship

종종 논리적인 역관계를 적용해서 행동을 검사할 수 있다. 수학 계산에서 많이 사용한다.
다음 클래스의 메서드는, 어떤 수의 제곱근을 구하는 기능을 한다.

1
2
3
4
5
6
7
8
9
10
11
public class NewtonTest {
static class Newton {
private static final double TOLERANCE = 1E-16;

public static double squareRoot(double n) {
double approx = n;
while (abs(approx - n / approx) > TOLERANCE * approx)
approx = (n / approx + approx) / 2.0;
return approx;
}
}

다음 테스트 코드에서는, 250의 제곱근을 유도하고, 그 결과를 제곱하면 다시 250이 나오도록 테스트한다.
즉, 역관계를 이용해서 테스트하는 것이다.

1
2
3
4
5
@Test
public void squareRoot() {
double result = Newton.squareRoot(250.0);
assertThat(result * result, closeTo(250.0, Newton.TOLERANCE));
}

4. Cross check

다른 수단을 활용해서 테스트할 수 있다.
위의 제곱근을 테스트하는 경우에, 자바 라이브러리 중에 제곱근을 구하는 라이브러리를 사용할 수 있다.

1
2
3
4
5
@Test
public void squareRootVerifiedUsingLibrary() {
assertThat(Newton.squareRoot(1969.0),
closeTo(Math.sqrt(1969.0), Newton.TOLERANCE));
}

5. Error

코드를 테스트하기 위해 도입할 수 있는 오류의 종류나 환경적인 제약들에는 다음과 같은 것들이 있다.

  1. 메모리가 가득 찰 때
  2. 디스크가 가득 찰 때
  3. 네트워크 가용성 및 오류들
  4. 시스템 로드

6. Performance

최적화를 하기 전에 먼저, 기준점을 잡기 위해 현재 경과 시간 (elapsed time) 을 측정해라. 이것을 몇 번 해보고 평균을 계산해라. 그리고, 코드를 변경하고 성과 테스트를 다시 실행해 결과를 비교해라.
단위 테스트 뿐만 아니라, JMeter 와 같은 도구를 사용할 수도 있다.


자바와 JUnit 을 활용한 실용주의 단위 테스트 <제프 랭어, 앤디 헌트, 데이브 토마스>

Comments