공부/자바 플레이그라운드 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 계산을 하였다.