경계 조건과 관련된 결함을 방지할 수 있는 방법들을 정리한다. 약어로, CORRECT.
기대하는 구조에 입력 데이터가 맞는지 확인해라. 예를 들면, 이메일 주소의 경우에 @ 기호가 없는 경우나 @ 의 앞부분이 비어있는 경우에 대처해야한다.
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 )); } @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 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
메서드의 호출 순서가 맞지 않을 때는 어떤 상황이 발생하는지 고려해라.
Timeout 을 고려하여 무한 대기에 빠지지 않도록 고려해라.
같은 객체를 동시에 다수의 스레드가 접근하면 어떤 일이 발생할지 고려해라.
자바와 JUnit 을 활용한 실용주의 단위 테스트 <제프 랭어, 앤디 헌트, 데이브 토마스>