TDD로 개발하기 마지막!

카일·2020년 2월 17일
2
post-thumbnail

모든 코드는 여기를 클릭 하시면 확인 하실 수 있습니다.

안녕하세요. TDD로 개발하기 마지막 파트입니다. 전체 포스팅은 전체 사이클을 경험하는 느낌으로 작성하였고 다음 포스팅 번외편에서 객체지향적인 방법을 추가하여 리팩토링을 해보겠습니다. 이번 포스팅에서는 현재 저희가 아직까지 하지 않았던 아래의 할 일 목록들을 해결해 보겠습니다. 참고로 이번 포스팅 부분은 TDD로 개발 된 도메인을 서로 연결하기 위한 과정임으로 TDD와는 거리가 멉니다. 단순히 프로그램 실행을 위해 연결 및 출력과 관련된 부분을 포스팅 할 예정입니다. (물론 출력은 테스트가 되지만 TDD로 할 필요는 없다고 생각해서 개발 후 단위 테스트를 조금 추가하겠습니다. )

  • 현재 도메인만 개발된 상태인데 프로그램 실행을 위해 전체 도메인을 연결한다.
  • 매 라운드 별로 각 사용자의 이름과 포지션을 출력한다.
  • 매 라운드가 종료되면 최종 우승자의 이름을 출력한다.
  • 끝맺음말..?

할일 1 : 프로그램 실행을 위해 전체 도메인을 연결한다 ( 전체 글에 해당함 )

  • 현재 저희는 도메인 자체만을 개발하고 테스트 한 상태입니다. 프로그램을 실행하였을 때 도메인이 호출되어 작업할 수 있도록 클래스를 추가 해 보겠습니다. 전체적으로 MVC의 형태를 보이고 있기에 Controller의 기능을 Main에 두지 않고 RacingGame이라는 곳으로 이전하여 관리하겠습니다.
     public class Application {
    	public static void main(String[] args) {
    		new RacingGame().run();
    	}
    }
  • RacingGame 에서는 사용자가 입력한 이름을 StringUtils 클래스를 통해 , 를 기준으로 분리하고 각 사용자마다 자동차를 생성합니다. 이 과정은 for 문을 통해 입력 된 이름에 맞게 자동차를 Controller에서 생성해도 되지만 정적 팩토리 메서드의 형태로 작성하겠습니다. 현재로서는 굳이 사용해야 할 이유는 없지만 이름을 통해 의도를 명확히 드러내고 로직 자체가 Controller보단 따로 클래스로 분리하는 것이 확장성면에서 좋을 것 같아 분리하여서 진행하겠습니다.
    package kail.study.java.racing.domain;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class CarFactory {
    	public static List<Car> create(List<String> names) {
    		List<Car> racingCars = new ArrayList<>();
    		for (String name : names) {
    			racingCars.add(new Car(name));
    		}
    		return racingCars;
    	}
    }

매 라운드 별로 각 사용자의 이름과 포지션을 출력한다.

  • 위의 CarFactory Class 를 통해 경주에 참여 할 자동차를 만들고 아래에서 플레이하고자 하는 횟수를 입력 받고 전체 게임을 진행하겠습니다. 아래의 코드는 전반적인 진행순서를 보여주는 Controller의 역할을 하니 아래의 코드를 보고 추가 된 코드를 보시면 이해에 도움이 될 것 같습니다.
  • Car와 RacingCar를 생성할 때 발생 할 수 있는 예외를 던졌기 때문에 아래의 클래스에서 그 예외를 잡아 처리하는 방식으로 진행하였고 Round 클래스에서 전체 라운드가 종료되었는 지 판단해 프로그램을 종료시킵니다.
    public class RacingGame {
    	private RacingCars racingCars;
    	private Round round;
    
    	public void run() {
    		initializeRace();
    		playGame();
    	}
    
    	private void initializeRace() {
    		try{
    			List<String> carNames = StringUtils.parseByComma(InputView.getInput());
    			racingCars = CarFactory.create(carNames);
    			round = new Round(InputView.getTimes());
    		} catch (Exception e) {
    			System.out.println(e.getMessage());
    			initializeRace();
    		}
    	}
    
    	private void playGame() {
    		while(!round.isEnd()){
    			racingCars.move();
    			OutputView.printRoundResult(racingCars.getCars());
    			round.reduce();
    		}
    		OutputView.printWinner(racingCars.getWinner());
    	}
    }
  • Round 클래스에서는 전체 라운드가 종료되었는지, 그리고 매 라운드마다 라운드가 줄어들 수 있도록 isEnd() 메서드와 reduce() 메소드가 추가되었습니다.
    // Round Class
    
    public boolean isEnd() {
    		return this.round == 0;
    	}
    
    	public void reduce() {
    		this.round--;
    	}
  • 라운드 테스트 추가

	@Test
	@DisplayName("reduce 메소드를 통해 라운드가 감소하는 지 테스트")
	void reduce() {
		Round round = new Round("2");
		round.reduce();
		round.reduce();
		assertThat(round.isEnd()).isTrue();
	}

	@Test
	@DisplayName("프로그램 오작동으로 라운드가 종료되었는데도, reduce를 호출하는 경우")
	void isEnd() {
		Round round = new Round("1");
		round.reduce();
		assertThatThrownBy(() -> {
			round.reduce();
		}).isInstanceOf(RuntimeException.class)
			.hasMessageContaining("모든 라운드가 종료되었습니다.");
	}

매 라운드가 종료되면 최종 우승자의 이름을 출력한다.

  • RacingCar에서는 각 라운드 별로 경기에 참여한 자동차를 이동시킬 수 있도록 move() 라는 매소드와 우승자를 출력하기 위한 getWinner() 매서드를 추가하였습니다. 승리자는 각각의 차의 포지션에 의해 결정됨으로 최대 포지션을 구한 이후 각 차에게 최대 포지션인지 물어보는 메세지를 보내어 True 가 반환되는 경우 추가하도록 하였습니다.
    public void move() {
    		for (Car car : cars) {
    			car.move(RandomGenerator.create());
    		}
    	}
    
    	public List<Car> getWinner() {
    		List<Car> winner = new ArrayList<>();
    		for(int i =0; i<cars.size(); i++) {
    			if(cars.get(i).isWinner(getMaxPosition()))
    				winner.add(cars.get(i));
    		}
    		return winner;
    	}
    
    	private int getMaxPosition() {
    		Car max = Collections.max(cars);
    		return max.getPosition();
    	}
    
    	public List<Car> getCars() {
    		return cars;
    	}
  • 자동차 클래스에서는 각 객체간의 포지션 비교를 위해 Comparable 인터페이스를 구현하였고 본인이 max 값과 같은지를 리턴하는 isWinner() 매서드를 추가하였습니다.
    public class Car implements Comparable<Car> {
    @Override
    	public int compareTo(Car anotherCar) {
    		return position - anotherCar.position;
    	}
    
    	public boolean isWinner(int maxPosition) {
    		return position == maxPosition;
    	}
    }
  • 마지막으로 출력을 담당하는 부분을 OutputView로 분리하여 도메인 객체 자체를 넘기는 방식으로 출력을 담당하고 있습니다.
    package kail.study.java.racing.view;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    import kail.study.java.racing.domain.Car;
    
    public class OutputView {
    	public static void printRoundResult(List<Car> cars) {
    		System.out.println("실행 결과");
    		for(Car car : cars) {
    			System.out.println(car.getName() + " : " + car.getPosition());
    		}
    	}
    
    	public static void printWinner(List<Car> winner) {
    		String winnerNames = winner.stream()
    			.map(Car::getName)
    			.collect(Collectors.joining(", "));
    		System.out.println(String.format("우승자는 %s입니다",winnerNames));
    	}
    }

끝맺음

  • 우아한 테크코스를 진행하며 했던 미션을 블로그에 정리하면서 진행 해 보았는데요. 제가 짰던 코드와 유사하게 작성하였음에도 불구하고 글로 생각을 정리하는 일은 어려운 일인 것 같습니다. 호옥시나 누군가 보셨다면 유용하셨길 바라며 TDD 로 커멘드라인 개발하기 프로젝트는 여기서 마무리하겠습니다.

  • 추가적으로 현재 코드에서는 리팩토링을 할 부분이 많습니다. 하지만 이 부분은 TDD보다는 객체지향에 가깝다고 생각이 들어 번외편으로 리팩토링 과정 부분을 추가 할 예정입니다. 객체지향적인 부분에서 부족한 점이 많기 때문에 추가적인 공부를 하면서 포스팅 하도록 하겠습니다.

1개의 댓글

comment-user-thumbnail
2020년 2월 18일

와 엄청 도움이 되네요 하하

답글 달기