ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 숫자야구게임 구현(2) - 요구사항 작성, TDD 적용
    공부/자바 플레이그라운드 with TDD, 클린코드 2022. 2. 16. 22:33

    SI업무를 할 때 ProductionCode 를 먼저 작성하는것뿐만 아니라 TestCode는 매번 Controller 호출하기 싫어서 Service 메서드 호출하는 값만 확인하는 용도로 썼었다.

    정말 우물안 개구리라는걸 절실히 느꼈다.

    또한 Intellij를 사용하니 TDD방법론을 적용하면서 역으로 TestCode -> ProductionCode 만드는 것이 놀랍도록 쉬워서 이젠 Eclipse도 떠나보낼 때가 온 것같다..


    github

    요구사항 분석

    #기능 요구 사항
    
    기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
    Ball
    
    BallPosition position
    BallNumber num
    [ ✔ ] 1 ~ 9 숫자범위
    BallNumber.validateNumber
    Balls
    
    [ ✔ ] 3자릿수
    Balls.validateLength
    [ ✔ ] 3가지 숫자는 서로 다른 수
    Balls.hasSameNumber
    [ ✔ ] List -> List
    Balls().createBalls
    같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱
    같은 수, 같은 자리 : 스트라이크
    같은 수, 다른 자리 : 볼
    없으면 낫싱
    Balls <-> Balls Balls <-> Ball Ball <-> Ball
    
    [ ✔ ] Ball끼리 비교 Ball.compare, 스트라이크, 볼, 낫싱 상태 반환
    [ ✔ ] Balls <-> Ball 비교 Balls.compareBallsAndBall
    [ ✔ ] Balls끼리 비교 Balls.compareBetweenBalls
    컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게 임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
    랜덤 3가지
    BallUtil.generateBalls
    이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
    게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.

    - 핵심 기능만 만들고 넘어갔다.


    TestCode

    public class BallsTest {
    
        @Test
        void validateLengthTest() {
            assertThat(Balls.validateLength(Arrays.asList(1, 2, 3))).isTrue();
        }
    
        @Test
        void hasSameNumberTest() {
            assertThat(Balls.hasSameNumber(Arrays.asList(1,2,3))).isFalse();
            assertThat(Balls.hasSameNumber(Arrays.asList(1,2,2))).isTrue();
            assertThat(Balls.hasSameNumber(Arrays.asList(1,2,1))).isTrue();
        }
    
        @Test
        void compareBallsTest() {
            Balls computer = new Balls(Arrays.asList(1, 2, 3));
            assertThat(computer.compareBallsAndBall(new Ball(0, 1))).isEqualTo(BallStatus.STRIKE);
            assertThat(computer.compareBallsAndBall(new Ball(2, 1))).isEqualTo(BallStatus.BALL);
            assertThat(computer.compareBallsAndBall(new Ball(0, 4))).isEqualTo(BallStatus.NOTHING);
        }
    
        @Test
        void compareBetweenBallsTest() {
            Balls computer = new Balls(Arrays.asList(1, 2, 3));
            assertThat(computer.compareBetweenBalls(new Balls(Arrays.asList(4, 5, 6)))).isEqualTo("낫싱");
            assertThat(computer.compareBetweenBalls(new Balls(Arrays.asList(1, 5, 6)))).isEqualTo("1 스트라이크");
            assertThat(computer.compareBetweenBalls(new Balls(Arrays.asList(2, 5, 6)))).isEqualTo("1 볼");
            assertThat(computer.compareBetweenBalls(new Balls(Arrays.asList(2, 1, 3)))).isEqualTo("1 스트라이크 2 볼");
            assertThat(computer.compareBetweenBalls(new Balls(Arrays.asList(1, 2, 3)))).isEqualTo("3 스트라이크");
        }
    }

    생성한 클래스

     

    Balls

    public class Balls {
    
        private final List<Ball> balls;
    
        public Balls(List<Integer> balls) {
            if (!validateLength(balls))
                throw new RuntimeException("숫자는 3자릿수만 가능합니다.");
            if (hasSameNumber(balls))
                throw new RuntimeException("같은 숫자가 중복될 수 없습니다.");
            this.balls = createBalls(balls);
        }
    
        public static boolean validateLength(List<Integer> balls) {
            return balls.size() == 3;
        }
    
        public static boolean hasSameNumber(List<Integer> balls) {
            return new HashSet<>(balls).size() != balls.size();
        }
    
        private List<Ball> createBalls(List<Integer> balls) {
            List<Ball> result = new ArrayList<>();
            for (int position=0; position<balls.size(); position++) {
                result.add(new Ball(position, balls.get(position)));
            }
            return result;
        }
    
        public BallStatus compareBallsAndBall(Ball otherBall) {
            return balls.stream().map((ball) -> ball.compare(otherBall))
                    .filter((status) -> status.isStrike() || status.isBall())
                    .findFirst()
                    .orElse(BallStatus.NOTHING);
        }
    
        public String compareBetweenBalls(Balls otherBalls) {
            BallScore ballScore = new BallScore();
            for (Ball otherBall : otherBalls.balls) {
                ballScore.setScore(this.compareBallsAndBall(otherBall));
            }
            return ballScore.toString();
        }
    }

     

    BallScore

    public class BallScore {
    
        private final Map<BallStatus, Integer> ballStatusMap = BallStatus.createBallStatusMap();
    
        public void setScore(BallStatus ballStatus) {
            ballStatusMap.put(ballStatus, ballStatusMap.get(ballStatus) + 1);
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            
            if (hasStrike()) {
                sb.append(ballStatusMap.get(BallStatus.STRIKE));
                sb.append(" 스트라이크 ");
            }
            if (hasBall()) {
                sb.append(ballStatusMap.get(BallStatus.BALL));
                sb.append(" 볼");
            }
            if (sb.length() <= 0)
                sb.append("낫싱");
            
            return sb.toString().trim();
        }
    
        private boolean hasBall() {
            return ballStatusMap.get(BallStatus.BALL) > 0;
        }
    
        private boolean hasStrike() {
            return ballStatusMap.get(BallStatus.STRIKE) > 0;
        }
    }

    BallStatus를 Key로 하고 Integer Value를 갖는 Map으로 Score 계산을 하였다.

    댓글

Designed by Tistory.