공부/자바 플레이그라운드 with TDD, 클린코드

숫자야구게임 구현(2) - 요구사항 작성, TDD 적용

Morian Kim 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 계산을 하였다.

댓글수0