테스트 주도 개발. 테스트를 먼저 만들고 그 후에 프로덕션 코드를 짠다는 말이다.
우아한테크코스 2주차로 받은 미션은 간단한 문자열 덧셈 계산기와 프리코스 때 했던 자동차 경주 게임에 TDD를 접목시키는 것이었다.
그 동안에 익숙해진 ‘설계 후 개발’에서 ‘테스트 작성 후 조건에 맞는 프로덕션 코드 작성’으로 바뀌니, 프리코스 때 요구 사항에 TDD가 추가된 것 만으로도 꽤 어려워졌다.
` @Test
@DisplayName("Car 클래스를 검사")
void carTest() {
Car car = new Car("pobi");
}
@ParameterizedTest
@DisplayName("car가 4 이상의 숫자를 받을 때만 전진한다")
@ValueSource(ints = {0, 1, 2, 3})
void doesCarProceed(int value) {
Car car = new Car("hiro");
assertFalse(car.canMove(value));
}`
가능한 모든 메소드에 대해 테스트 로직 짜기
…가 가능하면 좋겠으나, private 접근 제어자를 가진 메소드는 외부 클래스에 위치한 테스트 코드가 접근할 수 없다.
이를 억지로 검사하는 방법도 있지만, 이 private 메소드에 의존성을 가진 public 메소드를 간접 테스트하는 방법이 더 많이 사용된다고 한다.
무작위 결과를 내는 메소드에 대해 테스트 로직 짜기
이번 자동차 경주 게임 미션에서, 각각의 자동차는 0에서 9까지 중 랜덤 값을 하나 받아서 그 값이 4 이상일 때 1칸 전진한다. 문제는 랜덤 값이 있는 이상 항상 통과하는 테스트를 만들 수 없다는 것인데, 이를 해결하기 위해서는 상황에 따라 랜덤 값 대신 지정된 값을 넣을 수 있어야 한다. 그 방법으로서 인터페이스를 사용해 보았다.
`/**
NumberGenerator.java
숫자 생성기를 정의한 인터페이스
*/
package racingcar.util;
public interface NumberGenerator {
int generateNumber();
}`
`/**
RandomNumberGenerator.java
무작위 숫자를 생성하는 클래스
*/
package racingcar.util;
import java.util.Random;
public class RandomNumberGenerator implements NumberGenerator {
private static final int MAX = 9;
@Override
public int generateNumber() {
return new Random().nextInt(MAX + 1);
}
}
`
`/**
TestNumberGenerator.java
원하는 숫자가 담긴 int 배열을 받아 순서대로 리턴하는 테스트용 클래스
*/
package racingcar;
import racingcar.util.NumberGenerator;
public class TestNumberGenerator implements NumberGenerator {
private final int[] numbers;
private int index = 0;
public TestNumberGenerator(int[] numbers) {
this.numbers = numbers;
}
@Override
public int generateNumber() {
return numbers[index++];
}
}
`
구현과 사용은 이렇게 하게 된다.
`...
// 구현
public void playTurn(NumberGenerator number) {
cars.forEach(car -> {
if(car.canMove(number.generateNumber())) {
car.proceed();
}
});
}
...
// 프로덕션 코드에서 사용
public void playGame() {
RandomNumberGenerator random = new RandomNumberGenerator();
for (int i = 0; i < iteration; i++) {
cars.playTurn(random);
ConsoleOutput.printStatus(cars.notifyStatus());
}
ConsoleOutput.printResult(cars.findWinner());
}
...
// 테스트 코드에서 사용
@Test
@DisplayName("결과 발표 테스트")
void getResultTest() {
TestNumberGenerator test = new TestNumberGenerator(new int[]{4, 2, 5})
CarFactory carFactory = new CarFactory("alan, bart, carol");
Cars cars = carFactory.enrollCars();
cars.playTurn(test);
assertThat(cars.findWinner()).containsExactly("alan", "carol");
}
...`
구현 부분에서 NumberGenerator 객체를 매개 변수로 가져오는데, 사용할 때는 이 인터페이스를 Implement한 RandomNumberGenerator와 TestNumberGenerator를 모두 집어넣을 수 있어 정상적으로 랜덤하게 생성된 숫자를 넣을 수도, 테스트를 위해 원하는 숫자를 넣을 수도 있다.
Comments powered by Disqus.