한가지 일만
하도록 할 것테스트 코드 작성
자세한 내용은 Pull Request(링크) 에서 참고 부탁드립니다.
equals()
를 재정의해서 Number의 객체끼리 비교하도록 하였다.public class Number {
private int number;
//생성자 private
private Number(int number) {
validateSize(number);
this.number = number;
}
//1. 지정한 숫자 생성
public static Number createNumber(int number) {
return new Number(number);
}
//2. 랜덤 숫자 생성
public static Number createRandomNumber() {
int randomNumber = Randoms.pickNumberInRange(RANDOM_NUMBER_MIN, RANDOM_NUMBER_MAX);
return new Number(randomNumber);
}
//검증
private void validateSize(int number) {
if ((number < 1) || (number > 9)) {
throw new IllegalArgumentException(Errors.NUMBER_RANGE.getValue());
}
}
//getter
public int getNumber() {
return this.number;
}
//equals 재정의
@Override
public boolean equals(Object obj) {
return this.number == ((Number)obj).getNumber();
}
}
List<Number>
를 관리하는 일급 컬렉션
int number
를 Number
라는 클래스로 감싸는 것과 List<Number>
를 클래스로 감씨는 것은public class Numbers {
//게임에 필요한 숫자 갯수 상수로 명시
private static final int NUMBER_COUNT = 3;
private final List<Number> numberList = new ArrayList<>();
//기본생성자 접근 막음
private Numbers() {}
//지정 숫자 3개로 생성
public static Numbers createNumbers(int input) {
Numbers numbers = new Numbers();
numbers.validateThreeDigits(input);
numbers.validateDuplicateNumber(input);
numbers.createNumberList(input);
return numbers;
}
//랜덤한 숫자 3개로 생성
public static Numbers createRandomNumbers() {
Numbers numbers = new Numbers();
numbers.pickNewRandomNumbers();
return numbers;
}
/*검증 로직 시작*/
private void validateThreeDigits(int input) {
if ((input < 111) || (input > 999)) {
throw new IllegalArgumentException(Errors.NUMBERS_THREE_DIGITS.getValue());
}
}
private void validateDuplicateNumber(int input) {
if (checkDuplicateNumber(input)) {
throw new IllegalArgumentException(Errors.NUMBERS_DUPLICATE_NUMBER.getValue());
}
}
private boolean checkDuplicateNumber(int input){
int firstNumberInt = input/100;
int secondNumberInt = input%100/10;
int thirdNumberInt = input%10;
return (firstNumberInt == secondNumberInt)
||(secondNumberInt == thirdNumberInt)
||(thirdNumberInt == firstNumberInt);
}
/*검증 로직 끝*/
private void createNumberList(int input) {
for (int exponent = NUMBER_COUNT - 1; exponent >= 0; exponent--) {
int decimalNumber = (int) Math.pow(10, exponent);
int number = input / decimalNumber;
addNumber(number);
input = input % decimalNumber;
}
}
private void addNumber(int numberInt) {
Number number = Number.createNumber(numberInt);
numberList.add(number);
}
private void pickNewRandomNumbers() {
for (int index = 0; index < NUMBER_COUNT; index++) {
numberList.add(newRandomNumber());
}
}
private Number newRandomNumber() {
Number newRandomNumber;
//랜덤 숫자 중복 방지
do {
newRandomNumber = Number.createRandomNumber();
} while (numberList.contains(newRandomNumber));
return newRandomNumber;
}
public Number findNumber(int index) {
return numberList.get(index);
}
}
User user1 = new User();
User user2 = new User();
이렇게 두 객체를 만들어서 사용해도되지만,1 게임
마다 새로운 랜덤 숫자
를 가져야한다.1 라운드
마다 새로운 숫자를 입력
받아야 한다.public class Computer {
private Numbers computerNumbers;
public Computer () {}
//새로운 랜덤 숫자를 고르는 메소드
public void pickRandomNumbers() {
this.computerNumbers = Numbers.createRandomNumbers();
}
public Number findComputerNumber(int index) {
return this.computerNumbers.findNumber(index);
}
}
public class User {
private Numbers userNumbers;
public User () {}
//새로운 입력받은 숫자로 새로운 Numbers를 할당하는 메소드
public void inputNewNumbers(int input) {
this.userNumbers = Numbers.createNumbers(input);
}
public Number findUserNumber(int index) {
return this.userNumbers.findNumber(index);
}
}
computer
와 user
로 새로운 게임을 켜는 기능public class Game {
private Computer computer;
private User user;
private Round round;
public Game() {}
public void turnOnGame(Computer computer, User user) {
this.computer = computer;
this.user = user;
this.round = new Round();
Print.printGameStart();
}
// 컴퓨터가 새로운 랜덤 넘버를 고르며 게임 시작 후 정답 시 게임 종료 멘트 출력
public void startNewGame() {
this.computer.pickRandomNumbers();
playGame();
Print.printGameEnd();
}
//3스트라이크가 나올 때 까지 새로운 라운드를 호출하는 메소드
private void playGame() {
do {
round.startNewRound(user, computer);
} while (!round.isThreeStrike());
}
public boolean replayGame() {
Print.printReplayGame();
int inputInt = Input.readInt();
ReplayNumber replayNumber = new ReplayNumber(inputInt);
return replayNumber.isReplay();
}
}
public class Round {
private final int COUNT_NUMBER = 3;
private Hints hints;
public Round() {}
//새 라운드를 시작하는 로직
public void startNewRound(User user, Computer computer) {
Print.printRoundStart();
readNumbers(user);
getHints(user, computer);
Print.printRoundResult(hints);
}
//숫자를 읽어서 user에 새로운 Numbers를 지정하는 메소드
private void readNumbers(User user) {
int inputInt = Input.readInt();
user.inputNewNumbers(inputInt);
}
// 3개 숫자의 결과를 판단하는 메소드
private void getHints(User user, Computer computer) {
hints = new Hints();
for (int index = 0; index < COUNT_NUMBER; index++) {
Hint hint = getHint(index, user, computer);
hints.addHint(hint);
}
}
// 현재 인덱스의 Strike, Ball 여부를 판단하는 메소드
private Hint getHint(int index, User user, Computer computer) {
if (isStrike(index, user, computer)) {
return Hint.STRIKE;
}
if (isBall(index, user, computer)) {
return Hint.BALL;
}
return Hint.NOTHING;
}
//이 라운드가 3스트라이크인지 판별
public boolean isThreeStrike(){
int countStrike = hints.findHintCount(Hint.STRIKE);
return (countStrike == 3);
}
//현재 인덱스가 볼인지 판별
private boolean isBall(int index, User user, Computer computer) {
// 이전 인덱스 : 0 -> 2
int prevIndex = (index + 2) % 3;
// 이후 인덱스 : 2 -> 0
int nextIndex = (index + 1) % 3;
Number prevComputerNumber = computer.findComputerNumber(prevIndex);
Number nextComputerNumber = computer.findComputerNumber(nextIndex);
Number userNumber = user.findUserNumber(index);
boolean isPrevBall = userNumber.equals(prevComputerNumber);
boolean isNextBall = userNumber.equals(nextComputerNumber);
return isPrevBall || isNextBall;
}
//현재 인덱스가 스트라이크인지 판별
private boolean isStrike(int index, User user, Computer computer) {
return computer.findComputerNumber(index)
.equals(user.findUserNumber(index));
}
}
public enum Hint {
NOTHING("낫싱"),
BALL("볼"),
STRIKE("스트라이크");
private String value;
Hint(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
private final HashMap<Hint, Integer> hints;
각 힌트가 몇개 있는지 저장하는 클래스public class Application {
public static void main(String[] args) {
Game game = new Game();
Computer computer = new Computer();
User user = new User();
game.turnOnGame(computer, user);
do {
game.startNewGame();
} while (game.replayGame());
}
}
Game
은 싱글톤으로 만들어야될 것 같았다.Game
을 싱글톤으로 구현하였다.Round
또한 매 라운드 마다 라운드 객체를 새로 생성하는 것이아니라 새 라운드를 시작하는 메소드를 실행하는 것이라 싱글톤으로 구현하였다.상속이 불가
하다.Numbers나 Number를 만들어 놓고
뭘 비교해야할지 몰라서 처음에는 .isInstanceOf() 메소드를 사용하여 Numbers나 Number가 맞는지 확인
하였다.@DisplayName(value = "Numbers, Number 입력 테스트")
@ParameterizedTest
@ValueSource(ints = {123})
void inputValueTest(int input) throws Exception {
Numbers numbers = Numbers.createNumbers(input);
int[] inputs = {1,2,3};
for (int index = 0; index < 3; index++) {
assertThat(numbers.findNumber(index))
.isEqualTo(Number.createNumber(inputs[index]));
}
}
Scanner scanner = new Scanner(System.in);
String input = scanner.next();
System.in
이 콘솔에 입력된 값을 InputStream에 담아주는 역할 을 하여 그것을 Scanner
가 받아드리는 것인데,InputStream
을 명시적으로 지정해주고 그것으로 System에 set
해줘야 한다.String input = "입력하고 싶은 값";
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
OutputStream
에 콘솔에서 출력되는 바이트스트림을 저장하여 그를 읽는 과정이 필요하다.OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
String 출력값 = out.toString();
@ParameterizedTest
어노테이션을 활용하여 테스트 할 수 있다.@ValueSource()
, @MethodSource()
, @CsvSource
어노테이션을 통해 어떠한 변수를 넣을 것인지 설정할 수 있다.@DisplayName(value = "게임 재시작 테스트")
@ParameterizedTest
@CsvSource(value = {"1,true", "2,false"})
void restartTest(String input, boolean isReplay) {
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
Game game = new Game();
boolean replayGame = game.replayGame();
assertThat(replayGame).isEqualTo(isReplay);
}
Mocking
이라는 것을 알게됐다.static MockedStatic<Randoms> randomsMockedStatic;
@BeforeAll
static void beforeAll() {
randomsMockedStatic = mockStatic(Randoms.class);
when(Randoms.pickNumberInRange(1,9)).thenReturn(1,2,3);
}
randomsMockedStatic
이 관여할 수 있는 범위 내에서는 Randoms.pickNumberInRange(1,9)
의 리턴 값은 1, 2, 3 순서로 된다는 뜻이다.public void startNewRound(User user, Computer computer) {
Print.printRoundStart();
readNumbers(user);
getHints(user, computer);
Print.printRoundResult(hints);
}
설계
→ 테스트코드 작성
→ 기능 구현
이 순서로 진행된 다는 것을 알게 되었는데, 사실 지금 당장 이 순서를 지키면서 프로그램을 짜는 건 나에게 좀 벅찰 것 같다.
잘보고갑니다!