테스트 주도 개발. 테스트를 먼저 만들고 그 후에 프로덕션 코드를 짠다는 말이다.
우아한테크코스 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.