[실용주의 단위 테스트] 7장_경계 조건

경계 조건과 관련된 결함을 방지할 수 있는 방법들을 정리한다. 약어로, CORRECT.

1. Conformance

기대하는 구조에 입력 데이터가 맞는지 확인해라.
예를 들면, 이메일 주소의 경우에 @ 기호가 없는 경우나 @ 의 앞부분이 비어있는 경우에 대처해야한다.

2. Ordering

데이터의 순서나 Collection 에 있는 데이터의 위치가 코드를 잘못되게 할 수 있다.

3. Range

기본형의 과도한 사용에 의한 code smell 을 Primitive Obsession 이라고 한다. 객체 지향 언어를 사용하면, 사용자 정의 추상화로 이 문제를 해결할 수 있다.
예를 들면, 원은 360 도 이다. 이동 방향을 자바 기본형으로 저장하기 보다 Bearing 클래스로 범위를 제약하는 로직을 캡슐화 할 수 있다.
다음 테스트 코드는, 유효하지 않은 값으로 Bearing 을 생성할 때 어떤 일이 발생하는지 보여준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class BearingTest {

@Test(expected=BearingOutOfRangeException.class)
public void throwsOnNegativeNumber() {
new Bearing(-1);
}

@Test(expected=BearingOutOfRangeException.class)
public void throwsWhenBearingTooLarge() {
new Bearing(Bearing.MAX + 1);
}

@Test
public void answersValidBearing() {
assertThat(new Bearing(Bearing.MAX).value(), equalTo(Bearing.MAX));
}

@Test
public void answersAngleBetweenItAndAnotherBearing() {
assertThat(new Bearing(15).angleBetween(new Bearing(12)), equalTo(3));
}

@Test
public void angleBetweenIsNegativeWhenThisBearingSmaller() {
assertThat(new Bearing(12).angleBetween(new Bearing(15)), equalTo(-3));
}
}

제약 사항은 생성자에 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Bearing {
public static final int MAX = 359;
private int value;

public Bearing(int value) {
if (value < 0 || value > MAX) throw new BearingOutOfRangeException();
this.value = value;
}

public int value() { return value; }

public int angleBetween(Bearing bearing) { return value - bearing.value; }
}

또 다른 예로, 점 두 개를 x, y 정수로 유지하는 클래스가 있다고 하자. 범위에 대한 제약은, 각 변이 100 이하이다. 좌표에 영향을 주는 동작에 대하여 범위를 단언해보자. @After 메서드로 테스트가 완료될 때마다 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RectangleTest {
private Rectangle rectangle;

@After
public void ensureInvariant() {
assertThat(rectangle, constrainsSidesTo(100)); // constrainsSidesTo : 사용자 정의 햄크레스트 매처 (코드 생략)
}

@Test
public void answersArea() {
rectangle = new Rectangle(new Point(5, 5), new Point (15, 10));
assertThat(rectangle.area(), equalTo(50));
}

@Ignore
@ExpectToFail
@Test
public void allowsDynamicallyChangingSize() {
rectangle = new Rectangle(new Point(5, 5));
rectangle.setOppositeCorner(new Point(130, 130));
assertThat(rectangle.area(), equalTo(15625));
}
}

4. Reference

어떤 상태를 가정할 때는, 그 가정이 맞지 않으면 코드가 합리적으로 동작하는지 테스트해야한다.
예를 들어, 차량이 이동중일 때와 아닐 때 변속기 동작이 어떻게 달라지는지 테스트해보자. 시나리오는 다음 세가지이다.

  1. 가속 이후에 변속기를 주행으로 유지하나
  2. 주행 중에 주차로 바꾸는 요청을 무시하나
  3. 차량이 움직이지 않으면 주차로 변속기 변경을 허용하나

위 세 시나리오를 각각 테스트 메서드로 표현하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TransmissionTest {
private Transmission transmission;
private Car car;

@Before
public void create() {
car = new Car();
transmission = new Transmission(car);
}

@Test
public void remainsInDriveAfterAcceleration() {
transmission.shift(Gear.DRIVE);
car.accelerateTo(35);
assertThat(transmission.getGear(), equalTo(Gear.DRIVE));
}

@Test
public void ignoresShiftToParkWhileInDrive() {
transmission.shift(Gear.DRIVE);
car.accelerateTo(30);

transmission.shift(Gear.PARK);

assertThat(transmission.getGear(), equalTo(Gear.DRIVE));
}

@Test
public void allowsShiftToParkWhenNotMoving() {
transmission.shift(Gear.DRIVE);
car.accelerateTo(30);
car.brakeToStop();

transmission.shift(Gear.PARK);

assertThat(transmission.getGear(), equalTo(Gear.PARK));
}
}

5. Existence

주어진 값이 존재하는지 테스트해라. 값이 null 이나 0 이나 비어 있는 경우라면 어떤 일이 일어날지 생각해라.

6. Cardinality

0-1-n 법칙을 고려해라. 즉, 어떤 것이 없거나, 한 개만 있거나, 여러 개가 있는 경우를 고려해라.

7. Time

  1. 메서드의 호출 순서가 맞지 않을 때는 어떤 상황이 발생하는지 고려해라.
  2. Timeout 을 고려하여 무한 대기에 빠지지 않도록 고려해라.
  3. 같은 객체를 동시에 다수의 스레드가 접근하면 어떤 일이 발생할지 고려해라.

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

Comments