[우아한테크코스 6기 백엔드] 자동차 경주 회고

2024. 12. 19. 14:32·우아한테크코스 6기 백엔드

⭐️ 들어가기 전

우아한테크코스 레벨1의 첫번째 미션은 자동차 미션이다.

이 미션의 목표는 단위테스트이다. 단위테스트란 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트이다.

기능 요구사항은 우아한테크코스 6기 프리코스와 거의 동일하고 자율적으로 기능을 추가할 수 있다.

단위 테스트에 초점을 맞춰 도메인과 그것들의 테스트 설명을 먼저 하고, 그 외의 프로그램 구조적인 부분을 분리해서 회고하려 한다.

⭐️ 도메인

우선 도메인들에 대해 설명하겠다.

🍀 자동차

자동차 경주 게임을 구현할 때 제일 먼저 생각나는 것은 역시 자동차이다.
자동차는 이름과 위치를 가지고, 0~9의 값 중 4가 나오는 경우 움직인다.
단위 테스트를 위해서 이름과 위치, 움직이는 조건을 분리했다.
자동차 이름과 위치는 원시값 포장해서 CarName, Position을 만들었다.
이것의 장점으로 검증 코드를 자동차에서 CarName, Position으로 분리해서 유지보수성이 좋아졌다.

🍀 위치

Position의 경우 value에 대해서 equals, hashCode를 Override하고, Comparable interface를 받아 compareTo를 Override해서 value값으로 위치를 비교할 수 있었다. Car 또한 compareTo를 Override해서 position 값으로 순서를 비교할 수 있게 했다.

CarName과 Position과 같이 원시값 포장한 것들은 Car에서만 사용하기 때문에 car 하위 폴더에 넣어주었다. 내가 생각하기엔 이게 중요한 부분인 것 같다.

🍀 움직이는 조건

자동차는 0~9 중 4가 나왔을 경우 앞으로 간다.
자동차가 특정 조건에 따라 움직인다에 의존하는 것 같아서 MovingStrategy를 만들어서 분리했다.
MovingStrategy는 리턴 타입이 boolean인 move 메서드를 가진다.
특정 조건(0~9 중 4)을 분리하고, 자동차가 앞으로 가거나, 안가거나를 받고 싶었다. 
0~9 중 4가 나오는 경우가 아니라 "CarStatus == A 인 경우 앞으로 간다"라는 조건으로 변경될 수 있기 때문에 boolean으로 해줬다.

🍀 경주 참여자들

자동차들을 모아놓은 RaceParticipants는 일급 컬렉션이다. 생성자와 getter에서 주소값에 따라 변하는 것을 막아주었다. 이름을 Cars나 RacingCars로 할 수 있었겠지만 의미있는 이름으로 정하고 싶었다.

🍀 경주 결과

마지막으로 RaceResults는 자동차들이 한 번 움직일 때마다 그 위치를 기록하고, 마지막에 우승자를 결정하기 위해서 만들었다. 경주를 기록하고 결과로 주지 않으면 I/O 작업이 너무 빈번하게 일어나기도 하고, 경주 결과를 얻는 것과 경주 결과를 출력하는 것에 의존이 생겨서 분리했다.

⭐️ 도메인 테스트

다음은 도메인의 테스트에 대해서 설명하겠다.
일반적인 예외 테스트는 간단하니 생략한다.
초점을 맞춘 부분은 "특정 조건일 경우 자동차가 간다"와 "모든 자동차들을 움직인다" 의 경우이다.
MovingStrategy의 canMove는 boolean이기 때문에 한 번 밖에 쓸 수 없다.
그래서 다음과 같이 movableList를 만들어서 canMove를 사용할 때마다 리스트의 특정 인덱스의 값을 가져오게 했다.

public class MockMovingStrategy implements MovingStrategy {
    private final List<Boolean> movableList;
    private int currentIndex = 0;

    public MockMovingStrategy(final List<Boolean> movableList) {
        this.movableList = new ArrayList<>(movableList);
    }

    public MockMovingStrategy() {
        this.movableList = new ArrayList<>();
    }

    @Override
    public boolean canMove() {
        if (currentIndex >= movableList.size()) {
            throw new IllegalStateException("더 이상 이동할 수 없습니다.");
        }
        return movableList.get(currentIndex++);
    }

}

움직인다라는 것을 리스트로 만들어서 여러번 움직임을 테스트할 수 있었다.

@Test
void 자동차_움직임_성공() {
    // given
    final List<Boolean> movableList = List.of(true, false, true, false, true);
    final Car car = new Car("car", new MockMovingStrategy(movableList));

    // when
    for (int i = 0; i < movableList.size(); i++) {
        car.move();
    }

    // then
    assertThat(car.getPosition()).isEqualTo(3);
}

⭐️ 구조적인 코드

도메인 외적인 프로그램 구조적인 코드에 대해 설명하겠다.

우선 나는 InputView나 OutputView나 움직임 전략, 숫자 생성 전략 등등을 interface로 만들었고, 대부분의 생성자들에서 interface로 받는다.
그래서 AppConfig만으르 조작해서 프로그램을 바꿀 수 있게 의도했다.

public class AppConfig {
    private AppConfig() {
    }

    public static InputView consoleInputView() {
        return new ConsoleInputView();
    }

    public static OutputView consoleOutputView() {
        return new ConsoleOutputView();
    }

    public static NumberGenerator randomNumberGenerator() {
        return new RandomNumberGenerator();
    }

    public static MovingStrategy defaultMovingStrategy() {
        return new DefaultMovingStrategy(randomNumberGenerator());
    }

    public static RacingController racingController() {
        return new RacingController(
                consoleInputView(),
                consoleOutputView(),
                defaultMovingStrategy()
        );
    }
}

예외 처리에 대해서 IllegalArgumentException을 상속받은 BaseException을 만들고 BaseException을 상속받아 예외들을 만들어 사용했다. ErrorMessage들은 enum으로 만들어 관리했다.

package racingcar.exception;

public class BaseException extends IllegalArgumentException {
    private static final String PREFIX = "[ERROR]";

    public BaseException(final String message) {
        super(String.format("%s %s", PREFIX, message));
    }
}
public enum ErrorMessage {
    INPUT_NOT_A_NUMBER("입력 값은 숫자여야 합니다."),
    INVALID_CAR_NAME_LENGTH(String.format("자동차 이름은 %d자 이하여야 합니다.", MAX_NAME_LENGTH)),
    INVALID_RACE_COUNT_RANGE(String.format("시도 횟수는 %d~%d이어야 합니다.", MIN_RACE_COUNT, MAX_RACE_COUNT)),
    DUPLICATE_CAR_NAMES("중복된 자동차 이름이 존재합니다."),
    INVALID_POSITION(String.format("위치는 %d 이상이어야 합니다.", MIN_POSITION)),
    INVALID_CAR_NAME_FORMAT("자동차 이름에 한글, 영어, 숫자만 가능합니다."),
    ;

    private final String message;

    ErrorMessage(final String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

request, response dto도 사용했다.
내가 생각하는 dto의 역할은 controller <-> view나 domain <-> view의 의존 관계를 줄이기 위해서 사용한다고 생각한다.

public record RaceParticipantsRequest(String input) {
    public RaceParticipants toRaceParticipants(final MovingStrategy movingStrategy) {
        final List<Car> cars = InputUtils.splitByComma(input).stream()
                .map(carName -> new Car(carName, movingStrategy))
                .toList();

        return new RaceParticipants(cars);
    }
}
public record RaceWinnersResponse(List<String> raceWinners) {
    public static RaceWinnersResponse from(final List<Car> raceWinners) {
        final List<String> raceWinnersResponse = raceWinners.stream()
                .map(Car::getName)
                .toList();

        return new RaceWinnersResponse(raceWinnersResponse);
    }
}

위와 같이 view가 주고 싶은 코드를 dto에 주고, controller가 받고 싶은 코드를 dto에서 받는다. 또는 controller가 주고 싶은 코드만 dto에 주고 view가 받고 싶은 코드를 dto에서 받는다.

⭐️ 결론

자동차 미션을 진행하면서, 좀 더 객체지향적으로 사고할 수 있었고, 어떻게 분리해야 테스트를 쉽게 할 수 있는지 알 수 있었다.
자동차 코드는 다음에서 확인할 수 있다.

자동차 경주 코드

'우아한테크코스 6기 백엔드' 카테고리의 다른 글

[우아한테크코스 6기 백엔드] 방탈출 예약 관리 회고  (0) 2024.12.19
[우아한테크코스 6기 백엔드] 체스 회고  (0) 2024.12.19
[우아한테크코스 6기 백엔드] 블랙잭 회고  (0) 2024.12.19
[우아한테크코스 6기 백엔드] 사다리 타기 회고  (0) 2024.12.19
[우아한테크코스 6기 백엔드] 최종 합격 후기  (0) 2024.12.19
'우아한테크코스 6기 백엔드' 카테고리의 다른 글
  • [우아한테크코스 6기 백엔드] 체스 회고
  • [우아한테크코스 6기 백엔드] 블랙잭 회고
  • [우아한테크코스 6기 백엔드] 사다리 타기 회고
  • [우아한테크코스 6기 백엔드] 최종 합격 후기
alstn113
alstn113
웹 프론트엔드, 서버 개발에 관한 이야기를 다룹니다 :D
  • alstn113
    alstn113's devlog
    alstn113
  • 전체
    오늘
    어제
    • 분류 전체보기 (50)
      • 서버 (21)
      • 웹 프론트엔드 (5)
      • 협업 (2)
      • 우아한테크코스 6기 백엔드 (12)
      • 책, 영상, 블로그 정리 (8)
      • 회고 (1)
  • 블로그 메뉴

    • 홈
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    회고
    글쓰기
    플러피
    우아한테크코스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
alstn113
[우아한테크코스 6기 백엔드] 자동차 경주 회고
상단으로

티스토리툴바