BooleanGenerator
, Result
, Input
의 구현체를 Controller를 만들 때 주입했다.컨트롤러는 단순히 뷰와 도메인을 이어주는 역할을 할 뿐, 어떤 뷰로 입력을 받거나 출력해야되는지 알 필요가 없다.
컨트롤러의 의존성을 모두 외부에서 주입하고 있네요 👍
한발 더 나아가서, 컨트롤러에 대한 테스트를 작성해볼 수도 있을까요? 만약 어렵다면 어떤 이유 때문일까요?
테스트가 필요하다.
그렇다면 어떻게?
System.setIn()
, System.setOut()
등을 테스트 코드에서 활용하여 콘솔에 입력할 내용이나, 출력할 내용에 대해서 관리를 하고자 했다.System.setIn()과 같은 방식으로 콘솔에 입력할 값을 조작하여 테스트를 하는게 과연 Controller의
run()
기능에 대한 테스트일까?
@DisplayName("자동차 경주 통합 정상 작동 테스트")
@Test
void playGameTest() {
String carNames = "헤나, 썬샷, 루카"+System.lineSeparator()+"5";
inputStream = new ByteArrayInputStream(carNames.getBytes(UTF_8));
out = new ByteArrayOutputStream();
outputStream = new PrintStream(out);
System.setIn(inputStream);
System.setOut(outputStream);
numberGenerator = new RandomNumberGenerator();
outputView = new OutputView();
inputView = new InputView();
racingCarController = new RacingCarController(inputView, outputView, numberGenerator);
racingCarController.newCarNames();
racingCarController.newGameRound();
racingCarController.play();
assertThat(out.toString()).contains("최종 우승했습니다.");
}
inputView
, outputView
가 잘 동작하는지에 대한 테스트도 포함된게 된다.public class MockInputView implements Input {
private final List<List<String>> inputPlayersNames;
private final List<Integer> inputHeightOfLadder;
private final List<List<String>> inputRewards;
private final List<List<String>> inputTargetPlayers;
private final List<String> inputContinue;
private int orderOfInputPlayerNames;
private int orderOfInputHeightOfLadder;
private int orderOfInputRewards;
private int orderOfInputTargetPlayers;
private int orderOfInputContinue;
public MockInputView(List<List<String>> inputPlayersNames,
List<Integer> inputHeightOfLadder,
List<List<String>> inputRewards,
List<List<String>> inputTargetPlayers,
List<String> inputContinue) {
this.inputPlayersNames = inputPlayersNames;
this.inputHeightOfLadder = inputHeightOfLadder;
this.inputRewards = inputRewards;
this.inputTargetPlayers = inputTargetPlayers;
this.inputContinue = inputContinue;
}
@Override
public List<String> inputPlayerNames() {
if (inputPlayersNames.size() == orderOfInputPlayerNames) {
orderOfInputPlayerNames = 0;
}
return inputPlayersNames.get(orderOfInputPlayerNames++);
}
@Override
public int inputHeightOfLadder() {
if (inputHeightOfLadder.size() == orderOfInputHeightOfLadder) {
orderOfInputHeightOfLadder = 0;
}
return inputHeightOfLadder.get(orderOfInputHeightOfLadder++);
}
// ...
}
orderOfInputXXX
의 변수의 값을 하나씩 올려가며 체크하였다.inputHeightOfLadder()
호출할 때 반환 값이 3, 5, 6, 3, 5, 6, 3, ... 순서대로 되도록 하였다.public class MockResultView implements Result {
private List<String> players;
private List<Line> ladder;
private List<Reward> rewards;
private Map<Player, Reward> gameResult;
private Boolean hasError;
@Override
public void printError(String errorMessage) {
this.hasError = true;
}
@Override
public void printLadder(Players players, Ladder ladder, Rewards rewards) {
this.players = players.getNames();
this.ladder = ladder.getLadder();
this.rewards = rewards.getRewards();
}
@Override
public void printGameResult(Map<Player, Reward> gameResult) {
this.gameResult = gameResult;
}
public Boolean hasError() {
return hasError;
}
public List<String> getPlayers() {
return players;
}
public List<Line> getLadder() {
return ladder;
}
public List<Reward> getRewards() {
return rewards;
}
public Map<Player, Reward> getGameResult() {
return gameResult;
}
}
public class MockBooleanGenerator implements BooleanGenerator {
private final List<Boolean> generatedBoolean;
private int orderOfBoolean;
public MockBooleanGenerator(List<Boolean> generatedBoolean) {
this.generatedBoolean = generatedBoolean;
}
@Override
public boolean generateBoolean() {
if (generatedBoolean.size() == orderOfBoolean) {
orderOfBoolean = 0;
}
return generatedBoolean.get(orderOfBoolean++);
}
}
@Test
@DisplayName("사다리 컨트롤러 정상 테스트")
void ladderControllerTest() {
//given
MockInputView inputView = new MockInputView(
List.of(List.of("a", "b", "c", "d")),
List.of(3),
List.of(List.of("1", "2", "3", "4")),
List.of(List.of("all")),
List.of("n"));
MockResultView resultView = new MockResultView();
MockBooleanGenerator booleanGenerator = new MockBooleanGenerator(List.of(true, false));
ladderController = new LadderController(inputView, resultView, booleanGenerator);
//when
ladderController.run();
List<String> resultPlayers = resultView.getPlayers();
List<Line> resultLadder = resultView.getLadder();
List<Reward> rewards = resultView.getRewards();
Map<Player, Reward> gameResult = resultView.getGameResult();
//then
assertThat(resultPlayers).isEqualTo(List.of("a", "b", "c", "d"));
assertThat(resultLadder.get(0).getLine()).isEqualTo(List.of(MOVABLE_BAR, UNMOVABLE_BAR, UNMOVABLE_BAR));
assertThat(resultLadder.get(1).getLine()).isEqualTo(List.of(MOVABLE_BAR, UNMOVABLE_BAR, UNMOVABLE_BAR));
assertThat(resultLadder.get(2).getLine()).isEqualTo(List.of(MOVABLE_BAR, UNMOVABLE_BAR, UNMOVABLE_BAR));
assertThat(rewards.get(0).getReward()).isEqualTo("1");
assertThat(rewards.get(1).getReward()).isEqualTo("2");
assertThat(rewards.get(2).getReward()).isEqualTo("3");
assertThat(rewards.get(3).getReward()).isEqualTo("4");
assertThat(gameResult.get(new Player(new Name("a"))).getReward()).isEqualTo("2");
assertThat(gameResult.get(new Player(new Name("b"))).getReward()).isEqualTo("1");
assertThat(gameResult.get(new Player(new Name("c"))).getReward()).isEqualTo("3");
assertThat(gameResult.get(new Player(new Name("d"))).getReward()).isEqualTo("4");
}
controller.run()
은 많은 입력 값과 그에 대한 많은 실행 결과가 있어서 일 것 같다.어떠한 입력에서 의도한 대로 동작하여 관측 가능한 반환 값이나 상태를 변경시키는 지를 검증한다
고 생각하여 짜도록 해보자.