1 minute read

전략 패턴을 사용하여 테스트 하기

자동차 경주 게임 에서, 자동차는 부여받는 Random 값이 4이상일 경우, 전진할 수 있고, 4 미만일 경우에는 전진하지 못한다라는 요구 사항이 있다. 랜덤값 같은 테스트하기 어려운 대상에 대해, 테스트 코드를 작성하기 쉬운 메서드로 만드는 방법은 테스트하기 어려운 대상을 매개변수를 통해 전달받도록 수정하여 메서드가 직접적인 의존을 가지지 않게 하는 방법이었다.

그러나, 테스트를 어렵게 만드는 대상을 상위로 이동시키는 것에 불과하다.

  • Car.java
public void move(int randomValue){
  if(randomValue > MOVE_CRITERIA) {
    this.position++;
  }
}
  • Cars.java
public CarsDto move(int randomValue) {
  for(Car car : cars){
    car.move(randomValue);
  }
  return CarsDto.from(cars);
}
  • CarController.java
public CarsDto play(int gameCount) {
  Random random = new Random();
  return cars.move(random.nextInt());
}

실제 랜덤값을 사용하고 있는 Car의 move() -> car객체들을 움직이게 해주는 cars의 move() -> 랜덤값을 생성해주는 CarController의 play()

이 처럼, 계속 테스트하기 어려운 대상을 상위로 올라가게 된다. 실제로 값을 사용하는 곳이 아닌 상위의 어떤 곳에서 값을 생성해서 넘겨주기 때문에 로직과 관련된 응집성이 떨어지게 된다. 그리고 GameController에서 random값을 생성하는 곳의 테스트가 어려웠을것이다.


그렇다면 Car의 응집도는 그대로 유지한채, 테스트하기 좋은 메서드로 어떻게 만들까? 그리고 move() 를 테스트하고자 하는게 무엇일까? move() 가 받는 값이 랜덤값이든 의도한 값이든 4 이상이면 Car의 position을 증가시키고 4 미만일 경우에는 position을 그대로 유지하면 된다. 그렇다면 프로덕션 코드가 동작하는 곳에서는 move()가 랜덤 값을 받게 하고 테스트 코드에서는 의도한 값을 받도록 한다. 이 방법을 가능하도록 하는 것이 인터페이스 분리다.

  • NumberGenerator.java
public interface NumberGenerator {
    public int generateNumber();
}
  • RandomNumberGenerator.java (프로덕션 코드에서 사용)
public class RandomNumberGenerator implements NumberGenerator {
    private static final int RANDOM_NUMBER_BOUND = 10;

    private final Random random = new Random();

    @Override
    public int generateNumber() {
        return random.nextInt(RANDOM_NUMBER_BOUND);
    }
}
  • MovableNumberGenerator.java (테스트 코드에서 사용 - 전진 테스트)
public class MovableNumberGenerator implements NumberGenerator {
    private static final int MOVABLE_VALUE = 4;

    @Override
    public int generateNumber() {
        return MOVABLE_VALUE;
    }
}
  • NonMovableNumberGenerator.java (테스트 코드에서 사용 - 전진X 테스트)
public class NonMovableNumberGenerator implements NumberGenerator {
    private static final int NON_MOVABLE_VALUE = 3;

    @Override
    public int generateNumber() {
        return NON_MOVABLE_VALUE;
    }
}
  • Car.java
public void move(NumberGenerator numberGenerator) {
	int number = numberGenerator.generateNumber();
		if (number >= MOVE_CRITERIA) {
    	position.increase();
    }
}

move() 메서드에서 숫자를 생성하는 역할을 담당하는 NumberGenerator 인터페이스의 구현체를 정의하였다. 랜덤값을 생성해주는 부분을 interface로 분리를 하게 되면서 테스트를 걱정 하지 않게 되었다. 이때 interface의 의존성을 어디에서 가질 것이냐만 고민하면 되는데, 이 의존성을 interface의 로직을 실제로 사용하는 Car에서 가짐으로써 응집도를 높일 수 있다.

Categories:

Updated: