자바 레이싱 게임 - TDD
포스트
취소

자바 레이싱 게임 - TDD

테스트 주도 개발. 테스트를 먼저 만들고 그 후에 프로덕션 코드를 짠다는 말이다.

우아한테크코스 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를 모두 집어넣을 수 있어 정상적으로 랜덤하게 생성된 숫자를 넣을 수도, 테스트를 위해 원하는 숫자를 넣을 수도 있다.

This post is licensed under CC BY 4.0 by the author.

자바 enum 뜯어보기

일급 컬렉션의 장점과 사용 이유

Comments powered by Disqus.