언어 스터디 2주차를 마치며...
미션 기간: 2/8(토) - 2/14(금)
코드리뷰 기간: 2/8(일) - 2/11(화)
참여 인원: 13명 (스터디원 11명 + 진행자 2명)
평균 스터디 시간 : 2시간 10분
2주차 때는 의존성 분리를 통한 테스트에 주안점을 뒀다.
package racingcar.view;
import racingcar.utils.MessageConstants;
import racingcar.view.provider.InputProvider;
public class InputView {
private final InputProvider inputProvider;
public InputView(InputProvider inputProvider) {
this.inputProvider = inputProvider;
}
public String getCarName() {
(입력 로직)
}
public int getTrialNumber() {
(입력 로직)
}
이 코드는 뷰, 그중에서도 입력을 담당하는 InputView이지만, Console과 직접적인 의존성을 갖지 않도록 설계했다. InputProvider라는 인터페이스를 두고, 이에 대한 각각의 구현체를 주입받아 동작하는 식이다.
public interface InputProvider {
String readLine() throws IOException;
}
InputProvider라는 인터페이스에 뷰에서 쓸 readLine 메서드를 뒀다.
public class WoowaInputProvider implements InputProvider {
@Override
public String readLine() {
return Console.readLine();
}
}
프로그램을 동작시킬 때는 Console을 통해 입출력을 진행한다.
public class MockInputProvider implements InputProvider {
private final String[] inputs;
private int index = 0;
public MockInputProvider(String... inputs) {
this.inputs = inputs;
}
@Override
public String readLine() throws IOException {
if (index < inputs.length) {
return inputs[index++];
}
throw new IOException();
}
}
그리고 테스트를 할 때는 입력을 모방한 가짜 입력을 만들어 입력 기능을 테스트했다. 생성할 때 입력값을 넣어두고 readLine 메서드를 호출하면 하나씩 꺼내주는 방식이다. 테스트 코드는 다음과 같다.
@BeforeEach
void setUp() {
inputView = new InputView(new MockInputProvider("pobi,woni,jun", "5"));
outputView = new OutputView();
}
@Test
@DisplayName("차 이름 테스트")
void carNameInputTest() {
String carNames = inputView.getCarName();
assertEquals("pobi,woni,jun", carNames);
}
즉, 시작 전 모의 입력값을 정해두고 로직을 테스트하는 것이다.
물론 단순한 입력 테스트에 인터페이스를 쓰는게 코드의 복잡도를 증대시킬 수는 있겠지만, 솔직히 말하면 그러한 필요성을 따지기 전에 한 번 해보고 싶어서 해봤다.
유사하게 메서드의 파라미터에 다른 메서드를 주입받아 쓰도록해서 의존성을 분리하기도 했다.
public List<CarMovementDto> playTurn(Supplier<Boolean> movementFunction) {
race.advance(movementFunction);
return race.getResult();
}
(Race 클래스에 있는advance 메서드의 내부 로직)
public void advance(Supplier<Boolean> movementFunction) {
cars.forEach((car) -> {
if (movementFunction.get()) {
car.move();
}
});
turn++;
}
CarControlService 메서드에서 자동차를 이동시키는 로직이다. 자동차는 이동 행동에는 2가지 경우의 결과가 있다. 이동하거나, 이동하지 않거나. 이는 랜덤하게 정해지지만, 랜덤 로직 자체는 자동차의 이동과 분리되는 관심사라고 바라봤다. 이에 Supplier<Boolean>을 통해 진리값을 반환하는 함수를 가져왔다. 그리고 이것 역시 원하는 함수를 집어넣을 수 있기에 우연한 결과값에 의존되지 않은 채로, Mocking을 활용하여 효율적인 테스트를 구현할 수 있다.
-> 이 부분에 대해서는 이번 스터디에서 개선하기는 어려울 것 같다. 만약 다음 스터디를 할 기회가 있다면, 혹은 주변에 스터디를 할 사람이 있다면 스터디 전에 시스템을 구축해둘 필요가 있다.