우아한 테크코스 7기 프리코스 2주차 미션이 끝났습니다.
2주차의 미션은 우테코의 단골문제인 자동차 경주 입니다. 문제 링크는 아래에서 확인할 수 있습니다.
GitHub - woowacourse-precourse/java-racingcar-7
Contribute to woowacourse-precourse/java-racingcar-7 development by creating an account on GitHub.
github.com
아래는 제출한 PR 주소 입니다.
[자동차 경주] 이동엽 미션 제출합니다. by dye0p · Pull Request #205 · woowacourse-precourse/java-racingcar-7
github.com
2주차 미션은 전통의 자동차 경주 였습니다! 이 전 기수에서도 꾸준하게 나왔던 단골문제인데요. 프리코스 준비를 하면서 풀었던 기억이 있어서 문제를 보자마자 안도의 한숨을 쉬었습니다..ㅎ
하지만 아는 문제라고 하더라도 최선을 다 해서 미션을 진행 했는데요, 코드에 계속해서 미련이 남아서 마감 20분 전 까지 코드를 계속해서 수정했습니다.. (+ 소감문 작성까지) 이번 회고에서도 미션을 진행하면서 느꼇던 부분을 두서없이 말씀 드리겠습니다!
추가된 요구사항과 공통 피드백
우테코의 본격적인 미션은 2주차부터 시작된다고 하는데요. 미션을 열어보니 1주차 공통 피드백이 함께 있었습니다! 그리고 드디어 요구사항이 구체적으로 제시 되었습니다.
이번 주차의 학습목표는
- 1. 함수를 분리하는 것
- 2. 테스트 코드를 작성하는 것
- 3. 공통 피드백을 최대한 반영하는 것
그리고 특이하게 '회고'에 대한 요구사항이 추가 되었습니다. 읽어보니까 소감문도 평가에 들어가는 것 같네요! 1주차 소감문을 거의 5000자를 작성했는데 다행이라고 생각이 들었습니다..!
그리고 공통 피드백에서 여러가지 피드백이 있었는데요 6기 분들의 회고를 보니 모두다 공통 피드백을 최대한 지키려고 노력했다고 하셔서 저도 이번에 최대한 공통 피드백을 준수하기로 했습니다.
이제부터 미션을 진행하면서 배운것과 느낌점들을 얘기해보려고 합니다😊
1. 제어할 수 없는 값에 대한 테스트
랜덤값을 생성해서 4 이상이라면 자동차가 움직인다는 요구사항이 있었습니다.
랜덤값은 랜덤으로 생성되기 때문에 개발자가 임의로 숫자를 지정해서 생성할 수 없습니다. 그럼 이러한 제어할 수 없는 값은 어떻게 테스트 해야할까요?
1. 랜덤값을 외부에서 생성해서 메서드 파라미터로 주입 받는다.
가장 간편한 방법입니다. 자동차의 이동 테스트를 임의로 진행하기 힘들었던 이유는 랜덤값을 메서드 내부에서 생성해주고 있었기 때문입니다.
그럼 랜덤값을 외부에서 생성해서 자동차의 이동 메서드의 매개변수로 넣어주면 랜덤값을 내부에서 생성하지 않아 랜덤값을 임의로 지정하여 테스트할 수 있습니다. 즉 랜덤값을 메서드의 바깥으로 밀어내는 것 입니다!
🤔 그렇다면 여기서 의문이 생깁니다.
"랜덤값을 어디까지 밀어내야 할까?" 제어할 수 없는 값은 상위 계층으로 갈수록 테스트하기 수월해집니다. 그렇다면 어디까지 밀어내야 할까요? 계속해서 밀어내다보면 컨트롤러까지 밀어내야 하지 않을까요?
위 그림처럼 의존성을 바깥으로 밀어내면 Racing과 Car 는 테스트하기 쉬워집니다. 물론 RacingController도 Mokito를 활용하면 모킹을 통해서 테스트 할 수 있습니다.
이러한 방법 외에도 다른 방법이 있습니다. 의존성 역전(DI)을 이용한 방법으로 해결할 수 있습니다.
2. 의존성 역전을 이용한 전략 패턴을 사용한다.
위의 방법을 곰곰히 생각해보면 결국엔 의존성을 외부로 드러내는 방법이라고 할 수 있습니다. 그럼 의존성 외부로 드러내면 어떤 장점이 있는걸까요?
장점을 알아보기 전에 의존성이 무엇인지 알아보고, 의존성이 높았을때의 단점부터 알아보겠습니다.
의존성이란?
의존성은 하나의 모듈(라이브러리, 클래스, 패키지)이 다른 모듈에 의존하는 관계를 말합니다.
그럼 의존성이 높으면 어떤 단점을 가져올 수 있을까요?
높은 의존성이 불러오는 단점
- 모듈간의 결합도가 높아져서 하나의 모듈이 변경될때 같이 변경될 가능성이 있다. -> 연쇄적인 변경
- 결합도가 높아져 시스템 전체에 걸쳐 변경의 영향이 확대되어, 버그의 수정이나 업데이트가 힘들어진다. -> 유지보수의 어려움
- 하나의 단위테스트를 위한 여러개의 의존성이 필요할 수 있다. -> 단위 테스트의 어려움
- 특정 구현에 강하게 결합되어 있어 다른 프로젝트나 모듈에서 재사용하기 어렵습니다. -> 재사용성 감소
결국 의존성이 높다는것은 모듈끼리 강하게 결합되어 있다는 것이고. 이는 곧 재사용성이나, 유지보수의 어려움으로 확대됩니다.
이러한 문제를 해결하기 위해서는 의존성을 낮추어야 합니다.
이제 의존성을 외부로 드러낸다는 말을 이해할 수 있습니다.
의존성을 외부로 드러내는 방법에는 의존성 주입(DI)을 생각할 수 있습니다. 객체에게 필요한 의존성을 외부에서 주입 하는 것 입니다.
public interface RandomNumberStrategy {
int generateNumber();
}
자동차는 랜덤값이 필요하기 때문에 랜덤값에 대한 의존성을 인터페이스로 추상화 합니다.
public class RandomNumberGenerator implements RandomNumberStrategy {
private static final int MIN_RANGE = 0;
private static final int MAX_RANGE = 9;
@Override
public int generateNumber() {
return Randoms.pickNumberInRange(MIN_RANGE, MAX_RANGE);
}
}
이제 해당 인터페이스를 구현하는 구현체를 생성합니다.
public class Car {
private static final int DEFAULT_POSITION = 0;
private static final int MIN_MOVE_CONDITION = 4;
private final Name name;
private final Position position;
private final RandomNumberStrategy randomNumberStrategy;
private Car(String name, RandomNumberStrategy randomNumberStrategy) {
this.name = Name.from(name);
this.position = Position.from(DEFAULT_POSITION);
this.randomNumberStrategy = randomNumberStrategy;
}
...
Car 객체는 인터페이스를 필드로 가짐으로써 외부에서 RandomNumberStrategy의 구현체를 생성자를 통해서 주입 받을 수 있게 됩니다.
public class Cars {
private final List<Car> cars;
private Cars(List<Car> cars) {
this.cars = cars;
}
...
private static List<Car> createCars(List<String> carNames) {
List<Car> cars = new ArrayList<>();
for (String carName : carNames) {
cars.add(Car.of(carName, new RandomNumberGenerator()));
}
return cars;
}
Car를 생성할때 필요한 구현체를 외부에서 직접 넣어주고 있습니다! (RandomNumberGenerator를 new 키워드로 생성해주고 있음)
이제 car 객체는 랜덤값을 외부에서 주입받기 때문에 랜덤값 테스트를 자유롭게 할 수 있습니다.
public class StubRandomNumberGenerator implements RandomNumberStrategy {
private final int randomNumber;
public StubRandomNumberGenerator(int randomNumber) {
this.randomNumber = randomNumber;
}
@Override
public int generateNumber() {
return this.randomNumber;
}
}
임의의 랜덤값을 생성하는 stub 구현체를 생성합니다.
@DisplayName("랜덤값의 크기가 4이상이라면 전진할 수 있다.")
@ParameterizedTest
@CsvSource(value = {"4,1", "3,0"})
void tryMove(int randomNumber, int expectedPosition) {
//given
Car car = Car.of("pobi", new StubRandomNumberGenerator(randomNumber));
//when
car.tryMove();
//then
CarStatusDto carStatusDto = car.mapStatusToDto();
assertThat(carStatusDto.position()).isEqualTo(expectedPosition);
}
이제 랜덤값을 마음껏 외부에서 임의로 설정하여 테스트 할 수 있습니다.
2. 다양한 테스트 도구의 활용
2주차 미션을 진행하면서 테스트 코드를 작성하라는 피드백이 있었습니다. 그리고 Junit5와 AssertJ를 사용하라는 피드백이 함께있어 다양한 테스트 도구를 알게되었습니다.
1. @ParameterizedTest
여러개의 테스트를 한번에 실행할때 유용한 테스트 도구 입니다.
1-1. @NullAndEmptySource
자동차 이름을 입력할때 null 인경우를 테스트 해야할때 유용하게 사용할 수 있었습니다.
@DisplayName("자동차의 이름이 null이거나 빈 문자열이면 예외를 발생한다.")
@ParameterizedTest
@NullAndEmptySource
void carNamesNullOrEmpty(String input) {
//when //then
assertThatThrownBy(() -> InputValidation.validateCarNames(input))
.isInstanceOf(IllegalArgumentException.class);
}
@NullAndEmptySource 를 사용하여 null 입력에 대한 테스트를 할 수 있었습니다.
1-2.@CsvSource
다양한 input에 대한 expected 값을 테스트할 수 있습니다.
@DisplayName("주어진 최대 전진값과 현재 자동차의 전진값을 비교하여 참, 거짓을 반환한다.")
@ParameterizedTest
@CsvSource(value = {"3, true", "2, false"})
void isMaxPosition(int maxPosition, boolean expectedValue) {
//given
Car car = Car.of("pobi", new StubRandomNumberGenerator(4));
car.tryMove();
car.tryMove();
car.tryMove();
//when
boolean result = car.isMaxPosition(maxPosition);
//then
assertThat(result).isEqualTo(expectedValue);
}
@CsvSource 를 사용하여 여러개의 input에 대한 expected 값을 지정하여 테스트를 할 수 있었습니다.
3. 느낀점
1주차 미션의 코드리뷰를 경험하면서 자신의 코드를 잘 이해하고 작성하는것도 중요하지만, 다른 사람의 코드를 보고 이해하는 능력도 중요하다는 것을 느꼈습니다. 지금껏 혼자만 보는 코드를 작성해왔기 때문에 어떤 코드가 좋은 코드인지, 나쁜 코드인지 판단할수가 없었습니다. 그래서 2주차 미션을 구현할때는 읽기 좋은 코드에 대한 고민을 많이 했습니다. "메서드 네이밍", "메서드 배치 순서", "클래스 네이밍" 등등 가독성 측면에서 고민을 많이 했습니다. 요구사항과 기능도 잘 만들고 읽기 좋은 코드까지 작성한다는것은 힘든 일 이었습니다. 아마 미션의 난이도가 높았다면 신경쓰지 못했을것 같습니다.
이제 프리코스의 절반이 흘렀지만 배운것들이 많은 것 같습니다. 3주차에는 어떤 미션이 주어질지 기대가 됩니다! 힘들지만 힘든만큼 보람과 성취가 따라오니 힘이 나는것 같습니다! 남은 2주차도 잘 수행했으면 좋겠습니다! 감사합니다😊
'우아한 테크코스 > 프리코스' 카테고리의 다른 글
[우아한 테크코스 7기 백엔드] 1차 합격 & 최종 코딩테스트 회고 (6) | 2025.01.02 |
---|---|
[우아한 테크코스 7기 백엔드] 프리코스 3주차 회고 - 로또 - (2) | 2024.11.23 |
[우아한 테크코스 7기 백엔드] 프리코스 4주차 회고 - 편의점 - (1) | 2024.11.23 |
[우아한 테크코스 7기 백엔드] 프리코스 1주차 회고 -문자열 덧셈 계산기- (0) | 2024.10.29 |