- 
                Notifications
    
You must be signed in to change notification settings  - Fork 97
 
[그리디] 서현진 자동차 경주 미션 3, 4단계 제출합니다. #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: nonactress
Are you sure you want to change the base?
Changes from 42 commits
fbfab99
              1c54f83
              dee665e
              a9af148
              c6d9acc
              560ac39
              aeb4ab7
              dcb4ec5
              c11d098
              480f751
              c0d037a
              62b3941
              864e6c0
              97b4d95
              035ecdd
              0de29df
              f660a28
              7f5f429
              630df3a
              22dc5a0
              704cf09
              c19c73a
              b608f5b
              b471111
              cd70b8a
              66e64b6
              68814e7
              4f1b377
              ced1092
              fd0a317
              76330fc
              03ef929
              3e12240
              cd944bc
              5ca9b26
              fb563ce
              482cf13
              d5ef197
              ad763d8
              cab4c5f
              b160676
              2831966
              ada34f1
              eb25bf2
              c2e519f
              43190b6
              f6c6e25
              3fb578e
              7d22b1d
              72497ef
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import controller.RacingController; | ||
| 
     | 
||
| public class RacingGame { | ||
| public static void main(String[] args) { | ||
| RacingController racingController = new RacingController(); | ||
| racingController.run(); | ||
| } | ||
| } | ||
| 
     | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package controller; | ||
| 
     | 
||
| import model.Cars; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
| 
     | 
||
| public class RacingController { | ||
| public void run() { | ||
| String[] carNames = InputView.getCarNames(); | ||
| int raceCount = InputView.getRaceCount(); | ||
| 
     | 
||
| Cars cars = new Cars(carNames); | ||
| 
     | 
||
| OutputView.printResultMessage(); | ||
| RacingService racingService = new RacingService(); | ||
| racingService.startRace(cars, raceCount); | ||
| 
     | 
||
| OutputView.printWinners(cars.findWinners()); | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package controller; | ||
| 
     | 
||
| import model.Cars; | ||
| import view.OutputView; | ||
| 
     | 
||
| public class RacingService { | ||
| public void startRace(Cars cars, int raceCount) { | ||
| for (int i = 0; i < raceCount; i++) { | ||
| cars.moveAll(); // Model의 상태 변경 요청 | ||
| OutputView.printRoundResult(cars.getCars()); // View에 출력 요청 | ||
| } | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package model; | ||
| 
     | 
||
| public class Car { | ||
| 
     | 
||
| private final String name; | ||
| private int position = 0; | ||
| 
     | 
||
| public Car(String name) { | ||
| this.name = name; | ||
| } | ||
| 
     | 
||
| public void move() { | ||
| this.position++; | ||
| } | ||
| 
     | 
||
| public String getName() { | ||
| return name; | ||
| } | ||
| 
     | 
||
| public int getPosition() { | ||
| return position; | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package model; | ||
| 
     | 
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import model.random.RandomMoveStrategy; | ||
| import model.random.RandomGenerator; | ||
| 
     | 
||
| public class Cars { | ||
| private final List<Car> cars; | ||
| RandomGenerator generateRandom = new RandomMoveStrategy(); | ||
| //model.random.GenerateRandom testGenerateRandom = new model.random.TestGenerateRandom(); //단위 테스트용 코드 | ||
| 
     | 
||
| public Cars(String[] carNames) { | ||
| this.cars = new ArrayList<>(); | ||
| for (String name : carNames) { | ||
| cars.add(new Car(name)); | ||
| } | ||
| } | ||
| 
     | 
||
| public void moveAll() { | ||
| for (Car car : cars) { | ||
| move(car); | ||
| } | ||
| } | ||
| 
     | 
||
| private void move(Car car) { | ||
| if (generateRandom.generate()) { | ||
| car.move(); | ||
| } | ||
| // if (testGenerateRandom.generate()) { | ||
| // car.move(); | ||
| // } | ||
| } | ||
| 
     | 
||
| public List<String> findWinners() { | ||
| int maxPosition = findMaxPosition(); | ||
| List<String> winners = new ArrayList<>(); | ||
| for (Car car : cars) { | ||
| checkAndAddWinner(winners, car, maxPosition); | ||
| } | ||
| return winners; | ||
| } | ||
| 
     | 
||
| private void checkAndAddWinner(List<String> winners, Car car, int maxPosition) { | ||
| if (car.getPosition() == maxPosition) { | ||
| winners.add(car.getName()); | ||
| } | ||
| } | ||
| 
     | 
||
| private int findMaxPosition() { | ||
| int maxPosition = 0; | ||
| for (Car car : cars) { | ||
| maxPosition = Math.max(car.getPosition(), maxPosition); | ||
| } | ||
| return maxPosition; | ||
| } | ||
| 
     | 
||
| public List<Car> getCars() { | ||
| return Collections.unmodifiableList(cars); | ||
| 
     | 
||
| /*return this.cars; | ||
| *위 방식을 안쓴 이유는 객체를 받아서 다른 메소드나 클래스에서 | ||
| *수정이 가능해 지기 때문에 위 컬렉션 메소드로 반환값 설정 | ||
| * */ | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package model.random; | ||
| 
     | 
||
| public class FixedNumberMoveStub implements RandomGenerator { | ||
| private final int TEST_NUM = 5; | ||
| 
     | 
||
| @Override | ||
| public boolean generate() { | ||
| if (TEST_NUM > MOVE_CONDITION) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package model.random; | ||
| 
     | 
||
| public interface RandomGenerator { | ||
| static final int MOVE_CONDITION = 4; | ||
| 
     | 
||
| boolean generate(); | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package model.random; | ||
| 
     | 
||
| import java.util.Random; | ||
| 
     | 
||
| public class RandomMoveStrategy implements RandomGenerator { | ||
| private Random random = new Random(); | ||
| 
     | 
||
| @Override | ||
| public boolean generate() { | ||
| if (random.nextInt(10) > RandomGenerator.MOVE_CONDITION) { | ||
| return true; | ||
| } | ||
| return false; | ||
| 
     | 
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package view; | ||
| 
     | 
||
| import java.util.Scanner; | ||
| 
     | 
||
| public class InputView { | ||
| private static final Scanner scanner = new Scanner(System.in); | ||
| 
     | 
||
| public static String[] getCarNames() { | ||
| System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); | ||
| String[] splitCar = scanner.nextLine().split(","); | ||
| while(true) | ||
| { | ||
| String[] splits = getStrings(splitCar); | ||
| if (splits != null) return splits; | ||
| splitCar = scanner.nextLine().split(","); | ||
                
       | 
||
| } | ||
| 
     | 
||
| } | ||
| 
     | 
||
| private static String[] getStrings(String[] splitCar) { | ||
| if(isValid(splitCar)){ | ||
| return splitCar; | ||
| } | ||
| return null; | ||
| } | ||
| 
     | 
||
| public static int getRaceCount() { | ||
| System.out.println("시도할 회수는 몇회인가요?"); | ||
| int count = scanner.nextInt(); | ||
| scanner.nextLine(); // 개행 문자 제거 | ||
| return count; | ||
| } | ||
| 
     | 
||
| public static boolean isValid(String [] cars){ | ||
| if(cars.length == 1 ){ | ||
| System.out.println("한 개 이상의 자동차를 입력해주세요!"); | ||
                
       | 
||
| return false; | ||
| } | ||
| 
     | 
||
| for (String car : cars) { | ||
| if (isEmpty(car)) return false; | ||
| } | ||
| return true; | ||
| } | ||
| 
     | 
||
| private static boolean isEmpty(String car) { | ||
| if(car.trim().isEmpty()) | ||
| { | ||
| System.out.println("자동차의 이름이 공백일 순 없습니다!"); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package view; | ||
| 
     | 
||
| import model.Car; | ||
| 
     | 
||
| import java.util.List; | ||
| 
     | 
||
| public class OutputView { | ||
| 
     | 
||
| public static void printResultMessage() { | ||
| System.out.println("\n실행 결과"); | ||
| } | ||
| 
     | 
||
| public static void printRoundResult(List<Car> cars) { | ||
| for (Car car : cars) { | ||
| System.out.print(car.getName() + " : "); | ||
| printCarPosition(car); | ||
| System.out.println(); | ||
| } | ||
| System.out.println(); | ||
| } | ||
| 
     | 
||
| private static void printCarPosition(Car car) { | ||
| System.out.print("-".repeat(car.getPosition())); | ||
| } | ||
| 
     | 
||
| public static void printWinners(List<String> winners) { | ||
| String winnerNames = String.join(", ", winners); | ||
| System.out.println("최종 우승자 : " + winnerNames); | ||
| } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package controller; | ||
| 
     | 
||
| public class RacingControllerTest { | ||
                
       | 
||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package model; | ||
| 
     | 
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| 
     | 
||
| class CarTest { | ||
| 
     | 
||
| @Test | ||
| @DisplayName("자동차 생성 시 이름과 초기 위치(0)를 올바르게 설정한다.") | ||
| void createCar() { | ||
| // given: "pobi"라는 이름을 가진 자동차를 준비하고 | ||
| String carName = "pobi"; | ||
| 
     | 
||
| // when: 해당 이름으로 자동차를 생성하면 | ||
| Car car = new Car(carName); | ||
| 
     | 
||
| // then: 이름은 "pobi"이고 위치는 0이어야 한다. | ||
| assertEquals(carName, car.getName()); | ||
| assertEquals(0, car.getPosition()); | ||
| } | ||
| 
     | 
||
| @Test | ||
| @DisplayName("move 메서드를 호출하면 위치가 1 증가한다.") | ||
| void moveCar() { | ||
| // given: "woni"라는 이름의 자동차를 생성하고 | ||
| Car car = new Car("woni"); | ||
| 
     | 
||
| // when: move 메서드를 한 번 호출하면 | ||
| car.move(); | ||
| 
     | 
||
| // then: 자동차의 위치는 1이 되어야 한다. | ||
| assertEquals(1, car.getPosition()); | ||
| } | ||
| 
     | 
||
| @Test | ||
| @DisplayName("move 메서드를 여러 번 호출하면 호출한 횟수만큼 위치가 증가한다.") | ||
| void moveCarMultipleTimes() { | ||
| // given: "jun"이라는 이름의 자동차를 생성하고 | ||
| Car car = new Car("jun"); | ||
| int moveCount = 5; | ||
| 
     | 
||
| // when: move 메서드를 5번 호출하면 | ||
| for (int i = 0; i < moveCount; i++) { | ||
| car.move(); | ||
| } | ||
| 
     | 
||
| // then: 자동차의 위치는 5가 되어야 한다. | ||
| assertEquals(moveCount, car.getPosition()); | ||
| } | ||
| } | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. give-when-then으로 테스트 잘 작성해주셨네요 👍 void move_메서드를_호출하면_위치가_1_증가한다()  | 
||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package model.random; | ||
| 
     | 
||
| import model.random.*; | ||
| import model.Car; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| 
     | 
||
| public class RandomMoveStrategyTest { | ||
| 
     | 
||
| @Test | ||
| @DisplayName("값이 랜덤한지 확인") | ||
| void checkRandom (){ | ||
| // given: "pobi"라는 이름을 가진 자동차를 준비하고 | ||
| String carName = "pobi"; | ||
| FixedNumberMoveStub fixedNumberMoveStub = new FixedNumberMoveStub(); | ||
| 
     | 
||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 테스트가 어떤 걸 테스트하고 싶은지 잘 모르겠습니다! 실제로 랜덤한 값이 나오는 것을 테스트하고 싶으신 건가요?? 만약 그렇다면, 그건 저희가 직접 테스트하기 아주 어려운 영역이라고 생각합니다. '진짜 랜덤'은 테스트를 실행할 때마다 결과가 계속 바뀌기 때문에, 결과를 예측하고 검증하는 자동화된 테스트를 만들기 거의 불가능하기 때문입니다. 이건 저희 코드의 로직보다는 Java의 Random 클래스 자체의 기능을 테스트하는 셈이라 테스트하지 않아도 되는 영역 같습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아아 위 코드를 작성한 이유는 만약에 제가 RandomGenerator를 만들지 않았다면 랜덤한 값이 나오는 메소드인지 확인해보지 않을까?? 하는 생각에서 작성한 코드 였습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 결론부터 말씀드리면, 프로젝트 규모가 아무리 커져도  이유를 크게 두 가지로 생각해봤는데요, 
 저희는 외부 라이브러리의 기능 자체를 검증하기보다는 '그 라이브러리를 사용하는 우리 코드가 생각대로 잘 동작하는가'에 집중하는 것이 일반적인 테스트 전략인 것 같아요! 덕분에 테스트의 역할과 범위에 대해 다시 한 번 생각해볼 수 있었네요. 좋은 질문 감사합니다! 😊  | 
||
| } | ||
| 
     | 
||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드를 보니
RandomMoveStrategy의generate()메서드가boolean값을 반환하도록 설계하셨는데요, 혹시 이렇게 구현하신 이유가 있을까요?random.nextInt(10)으로 생성한 숫자를 바로 반환하지 않고, 내부에서MOVE_CONDITION과 직접 비교해서true나false로 만들어주신 의도가 궁금합니다!저는
Random관련 객체들의 역할을 "랜덤 숫자 생성"에만 딱 집중시키는 건 어떨까 제안하고 싶어요.RandomMoveStrategy는 주사위 같은 역할이라고 생각했는데 1에서 6 사이의 숫자를 사용자에게 보여주는 것까지가 주사위의 역할인 것 같아요! “주사위 숫자가 4 이상이니 전진하세요"라고 말하는 것은 주사위가 아니라, 규칙을 아는 플레이어의 역할이라고 생각합니다.현재는 약간의 코드 중복(if문)도 있어서, 만약 판단 비즈니스 요구사항이 4보다 작은 경우로 바뀐다면 모든 전략을 수정해줘야하는 번거로움이 생길 수도 있을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
리뷰어님이 말씀하신 내용처럼 위 객체를 생성할 때 return 값에 대해 많이 고민해보았습니다! 하지만 저는 위 객체를 주사위 같은 역할의 객체로 생각하기 보단 전진 가능한 조건에 초점을 맞추고 만든 객체인 만큼 플레이어한테 어떤 랜덤한 값이 나왔는지 알려줄 이유가 없다고 생각했습니다!
위 객체는 'RandomGenerator' 인터페이스로 구현되어 있어서 안에 static final int MOVE_CONDITION = 4; 에서 정수 값만 변경 한다면 충분히 조건변경이 용이하다고 생각했습니다!!!