ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TDD 기반 야구 게임 개발하기 4. 사용자 입력 숫자열 생성기
    24년 11월 이전/레거시-야구 게임 (Feat. TDD) 2018. 8. 8. 20:09
    반응형


    * 먼저 이 프로젝트의 지적 자산은 '코드 스쿼드'에 있음을 밝힙니다. 필자의 프로젝트의 소스코드는 https://github.com/gurumee92/baseballtdd/ 에 있습니다.


    테스트 주도 개발 기반 야구 게임 만들기

    4단계. 사용자 입력 숫자열 생성기


    1. 기능 정의와 테스트 시작


    이번에 개발해볼 기능은 사용자 입력기입니다. 만들어보기전에 한 번 생각해봅시다. 사용자 입력을 왜 받는 것일까요? 바로 랜덤 숫자열 생성기로부터 생성된 숫자열들과 비교할 숫자열을 만들기 위해서입니다. 다시 말하면 이번 포스팅에서 저희가 만들 것은 "사용자 입력 숫자열 생성기"입니다! 이제부터 "사용자 입력기"를 "사용자 입력 숫자열 생성기"로 재 정의하도록 하겠습니다. 이 생성기의 기능은 크게 두가지입니다.


    1. 시스템에 기반한 사용자 입력을 받는다.(지금은 콘솔 기반이겠죠?)
    2. 사용자 입력에 대해서 유효성 검사를 한다.

     

    이제 재정의를 해보았으니 클래스를 만들어보도록 하죠. TDD에서는 항상 소스 코드를 만들기 전에 무엇을 해야 한다고 했었죠? 맞습니다. 실패하는 테스트 코드가 필요합니다. 테스트 코드를 작성해볼까요? 다음과 같이 테스트 디렉토리에 UserInputBallsGenerator를 작성해주세요. (이번 시간부터 JUnit 테스트는 생략하겠습니다.)


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Test;

    public class UserInputBallsGeneratorTest {
    @Test
    public void userInputBalssGeneratorCreate() {
    UserInputBallsGenerator generator = new UserInputBallsGenerator();
    }
    }

    이제 예상대로 테스트가 실패합니다. 다음은 성공을 위한 소스 코드 작성이 필요합니다. main 디렉토리에 UserInputBallsGenerator를 다음과 같이 작성해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    public class UserInputBallsGenerator {
    }

     

    자 이제 테스트를 해봅시다. 통과하는군요! 실패 후 최소한의 코드로 테스트를 통과시켰습니다. 이제 리팩토링을 할 차례네요. 다행히 리팩토링할 거리는 없습니다. 이 생성기 역시 게임에서 지정한 길이에 대한 정보가 필요합니다. 왜냐하면 유효성 검사 중 제한 길이에 대한 검사가 들어가거든요. 따라서 길이 정보를 가지고 생성시킬 수 있어야 합니다. 테스트 코드를 다음과 같이 작성해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Test;

    public class UserInputBallsGeneratorTest {
    //이전과 동일

    @Test
    public void userInputBallsGeneratorCreateByGameDefaultLength(){
    UserInputBallsGenerator generator =
    new UserInputBallsGenerator(Game.DEFAULT_LEN);
    }
    }

    자 이제 테스트가 실패합니다. 역시 길이에 대한 필드, 길이 정보를 가지고 클래스를 생성시키는 생성자가 없거든요. 이제 소스 코드를 다음과 같이 변경해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    public class UserInputBallsGenerator {
    private int maxLen;

    public UserInputBallsGenerator(){
    this(Game.DEFAULT_LEN);
    }

    public UserInputBallsGenerator(int maxLen){
    this.maxLen = maxLen;
    }
    }

    이제 테스트를 진행해봅시다. 통과합니다. 이제 리팩토링을 진행할 차례입니다. 테스트 코드에서 생성기를 생성하는 부분이 중복되는군요! 이제 중복된 코드를 제거하기 위해서 @Before + setUp 메소드로 빼두겠습니다. 다음처럼 말이죠.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    public class UserInputBallsGeneratorTest {

    private UserInputBallsGenerator generator;

    @Before
    public void setUp() throws Exception {
    generator = new UserInputBallsGenerator(Game.DEFAULT_LEN);
    }
    }

    자 리팩토링을 마쳤으니 Git에 올리도록 하죠. 코멘트는 "UserInputBallsGenerator 생성 테스트 및 리팩토링 완료"로 하죠. 아차! 그 전에 어차피 쓰지 않을것이니까 일단 UserInputGenerator의 디폴트 생성자는 private으로 막아주세요.


    2. 본격적인 테스트 주도 개발(약간의 편법)


    본격적인 테스트에 들어가기 전에 어떤 기능에 대한 테스트가 있는지 살펴보도록 하죠.


    1. 사용자 입력
    2. 입력에 대한 유효성 검사


    어떤 것이 테스트하기가 쉬울까요? 음 만약 입력에 대한 구현이 필요하다면 이전 포스팅에서 했던 것처럼 입력 포맷을 박아놓고 하면 유효성 검사가 더 쉽습니다. 하지만 자바는 이미 사용자 입력에 대한 구현을 자바 표준 라이브러리에 구현이 되어 있죠. 이들을 굳이 테스트할 필요가 있을까요? 제 생각에는 없습니다. 이미 셀 수도 없이 많은 개발자들이 잘 쓰고 있으니까요. 일단 그래서 TDD의 규칙을 무시하고 사용자 입력 부분만 작성해보려 합니다. 소스 코드를 다음과 같이 변경해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    public class UserInputBallsGenerator {
        //이전과 동일
    private BufferedReader br;

    //이전과 동일
    public UserInputBallsGenerator(int maxLen){
    this.maxLen = maxLen;
    br = new BufferedReader(new InputStreamReader(System.in));
    }

    public String read(){
    String input = null;

    try{
    input = br.readLine();
    }catch (IOException e){
    e.printStackTrace();
    }

    return input;
    }
    }


    보통 어떤 클래스 내에서 Exception을 던지는 기능을 쓸 때 해당 클래스가 그 처리를 맡아야 합니다. 따라서 read라는 메소드로 한번 더 감싸서 try처리를 해준 것이지요. 프로그래밍 제약 사항을 기억하시나요? 한 메소드 당 10줄 이내의 하나의 기능을 가져야 한다. 이것을 충족시키기 위한 필요조건이기도 하지요. 실제로 TDD로 개발하실 때 이렇게 하면 안됩니다. 오히려 자신이 작성하지 않은 코드는 더 세심하게 테스트를 해야 하지요. 다만, 제가 이렇게 한 이유는 저의 TDD의 얕은 지식 때문입니다. 어떻게 테스트할지 감이 잡히지 않았기 때문에 이런 편법을 쓴것이지요. 이 부분에 대해서는 지식이 채워지는대로 수정하겠습니다. 일단 Git에 "UserInputBallsGenerator 입력 기능 처리 테스트 미구현"이라고 올려주세요. 이제 진짜 본격적으로 테스트를 시작해보죠. 유효성 검사에 대한 테스트입니다. 근데 유효성 검사라고 하니 뭔가 익숙하지 않나요? 맞습니다. 이전 포스팅에서 만든 RandomBallsGenerator에서도 생성된 숫자열에 대해 유효성 검사를 진행했었죠. 사실 이번 검사는 랜덤 생성 테스트 이외에 나머지 부분이 똑같습니다. 따라서 이번에는 이 만들어진 테스트를 이용해보도록 하지요. RandomBallsGenerator에서 다음을 복사하고 주석을 처리해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.junit.Assert.assertEquals;

    public class UserInputBallsGeneratorTest {

    //이전과 동일
    /*
    //유효성 검사 1. 널이 아닌가?
    @Test
    public void isStringValidatedIsNotNullWhichIsGeneratedByRandomBallsGenerator(){
    String balls = generator.generateBalls();
    assertEquals(balls != null, true);
    }
    //유효성 검사 2. 게임에서 지정한 길이인가?
    @Test
    public void isStringValidatedIsLengthEqualsGamesMaxLenWhichIsGeneratedByRandomBallsGenerator(){
    String balls = generator.generateBalls();
    assertEquals(balls.length(), Game.DEFAULT_LEN);
    }
    //유효성 검사 3. 문자열이 1~9까지의 숫자들로 이루어져 있는가
    @Test
    public void isStringValidatedAllLettersComposedNumber1To9(){
    String balls = generator.generateBalls();
    for (int i=0; i<balls.length(); i++){
    int c = balls.charAt(i) - '0';
    assertEquals( (c >= 1 && c <= 9), true );
    }
    }
    //유효성 검사 4. 문자열이 서로 다른 숫자로 이루어져 있는가
    @Test
    public void isStringValidatedAreAllDifferents(){
    String balls = generator.generateBalls();
    long cnt = 0;

    for (int i=0; i<balls.length(); i++){
    char c = balls.charAt(i);
    cnt += Arrays.stream(balls.split("")).filter(s -> s.equals(c + "")).count();
    }
    assertEquals(cnt, balls.length());
    }
    */
    }

    이제 이 테스트들을 토대로 유효성 검사 기능인 validate 메소드를 만들겁니다. 테스트 코드에 다음을 추가해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.junit.Assert.assertEquals;

    public class UserInputBallsGeneratorTest {

    //이전과 동일

    @Test
    public void generatorCanValidate(){
    generator.validate("123");
    }
    //이전과 동일
    }


    이제 테스트를 진행해보면 실패하겠죠? 왜냐하면 이 메소드가 소스코드에 구현이 안되어 있으니까요. 이제 소스 코드를 구현해봅시다. validate 메소드는 이 문자열이 게임에서 유효한 문자열인지 판단하는 메소드니까 반환 타입은 boolean이 적당하겠습니다. 따라서 소스 코드를 다음과 같이 작성해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    public class UserInputBallsGenerator {
    //이전과 동일

    public boolean validate(String input) {
    return true;
    }
    }

    이제 테스트를 통과하는지 볼까요? 통과하네요. 이제 리팩토링 여부를 살펴봅시다. 주석이 켜져 있다면 중복이 되니까 리팩토링 건수지만 아직은 아니네요. 이제 Git에 "UserInputBallsGenerator - 유효성 검사 메소드 생성"이라고 코멘트를 달아 올리도록 하죠. 이제 본격적으로 유효성 검사를 진행해보죠. 먼저 널이 아닌지 여부에 대한 검사입니다. 테스트 코드의 generatorCanValidate 메소드를 validateNotNull로 바꾸고 코드를 다음과 같이 수정해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.junit.Assert.assertEquals;

    public class UserInputBallsGeneratorTest {

    //이전과 동일
    /*
    //유효성 검사 1. 널이 아닌가?
    @Test
    public void isStringValidatedIsNotNullWhichIsGeneratedByRandomBallsGenerator(){
    String balls = generator.generateBalls();
    assertEquals(balls != null, true);
    }
    */
    @Test
    public void validateNotNull(){
    assertEquals(generator.validate("123"), true);
    assertEquals(generator.validate(null), false);
    }
    //이전과 동일
    }

    테스트를 바로 진행해봅시다. 바로 실패가 뜹니다. 아직 널이 아닌 것인가에 대한 처리를 안했거든요. 자 위로 올린 주석을 봅시다. 왜냐하면 저 코드에는 유효성 검사하는 코드가 숨어있거든요. 바로 다음 코드입니다.


    assertEquals(balls != null, true);


    이 부분은 생성된 숫자열이 널이 아닌가를 체크하는 것이지요. 이것을 거의 그대로 옮기도록 하죠. 소스코드를 다음처럼 변경해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    public class UserInputBallsGenerator {
    //이전과 동일

    public boolean validate(String input) {
    return isInputNotNull(input);
    }
    private boolean isInputNotNull(String input){
    return input != null;
    }
    }

    테스트가 통과하는군요! 이제 리팩토링 부분이 있나 살펴봅시다. 일단 테스트 코드의 1번 유효성 검사에 대한 주석은 이제 필요 없으니 삭제해주세요. 더는 없는것 같습니다. 이제 Git에 올리도록 하죠. 코멘트는"UserInputBallsGenerator - 유효성 검사 1. NOT NULL" 로 하도록 하겠습니다. 이제 4번까지 똑같이 반복하시면 됩니다. RandomBallsGenerator 클래스의 테스트들을 참고하면서 말이죠. 여러분이 한 번 해보세요! (절대 귀찮아서가 아닙니다^^) 저와 여지껏 함께 진행해왔다면 2~4번을 진행하면 다음과 같이 코드가 쉽게 변경될 겁니다. 


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.junit.Assert.assertEquals;

    public class UserInputBallsGeneratorTest {

    //이전과 동일
    /*
    //유효성 검사 2. 게임에서 지정한 길이인가?
    @Test
    public void isStringValidatedIsLengthEqualsGamesMaxLenWhichIsGeneratedByRandomBallsGenerator(){
    String balls = generator.generateBalls();
    assertEquals(balls.length(), Game.DEFAULT_LEN);
    }
    */
    @Test
    public void validateIsMaxLen(){
    assertEquals(generator.validate("123"), true);
    assertEquals(generator.validate("12"), false);
    assertEquals(generator.validate("1234"), false);
    }
    /*
    //유효성 검사 3. 문자열이 1~9까지의 숫자들로 이루어져 있는가
    @Test
    public void isStringValidatedAllLettersComposedNumber1To9(){
    String balls = generator.generateBalls();
    for (int i=0; i<balls.length(); i++){
    int c = balls.charAt(i) - '0';
    assertEquals( (c >= 1 && c <= 9), true );
    }
    }
    */
    @Test
    public void validateIsOnlyComposed1to9(){
    assertEquals(generator.validate("123"), true);
    assertEquals(generator.validate("12a"), false);
    assertEquals(generator.validate("ㄱ12"), false);
    assertEquals(generator.validate("!12"), false);
    }
    /*
    //유효성 검사 4. 문자열이 서로 다른 숫자로 이루어져 있는가
    @Test
    public void isStringValidatedAreAllDifferents(){
    String balls = generator.generateBalls();
    long cnt = 0;

    for (int i=0; i<balls.length(); i++){
    char c = balls.charAt(i);
    cnt += Arrays.stream(balls.split("")).filter(s -> s.equals(c + "")).count();
    }
    assertEquals(cnt, balls.length());
    }
    */
    @Test
    public void validateAllDifferentNumber(){
    assertEquals(generator.validate("123"), true);
    assertEquals(generator.validate("456"), true);
    assertEquals(generator.validate("111"), false);
    assertEquals(generator.validate("112"), false);
    assertEquals(generator.validate("121"), false);
    assertEquals(generator.validate("211"), false);
    assertEquals(generator.validate("337"), false);
    assertEquals(generator.validate("272"), false);
    assertEquals(generator.validate("499"), false);
    }
    }


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Arrays;

    public class UserInputBallsGenerator {
    //이전과 동일

    public boolean validate(String input) {
    return isInputNotNull(input) && isInputLengthEqualsGamesLength(input)
    && isInputComposedOnlyOneToNine(input) && isInputComposedAllDifferentNumbers(input)
    ;
    }
    private boolean isInputNotNull(String input){
    return input != null;
    }
    private boolean isInputLengthEqualsGamesLength(String input){
    return input.length() == maxLen;
    }
    private boolean isInputComposedOnlyOneToNine(String input){

    for (int i=0; i<input.length(); i++){
    int c = input.charAt(i) - '0';

    if( !(c >= 1 && c <= 9) )
    return false;
    }

    return true;
    }
    private boolean isInputComposedAllDifferentNumbers(String input){
    long cnt = 0;

    for (int i=0; i<input.length(); i++)
    cnt += countCharacterAtIndexOnInput(input
    , i);

    return cnt == maxLen;
    }
    private long countCharacterAtIndexOnInput(String input, int idx){
    final char c = input.charAt(idx);
    long cnt = Arrays.stream(input.split("")) //문자열을 쪼개서
    .map(s -> s.charAt(0)) //쪼갠 하나의 문자열을 Char형으로
    .filter( s -> s == c) //현재 문자와 같은 요소들 필터링
    .count(); //요소 카운팅
    return cnt;
    }
    }


    이 테스트들이 완료되면 주석들을 삭제하고 Git에 "유효성 검사 기능 완료" 라는 코멘트를 달아 올리시면 됩니다. 주석을 삭제할 때 한번 더 이전 테스팅 로직과 소스 코드에 옮겨진 validate 부분을 비교해보세요. 이제 이 만들어진 기능들을 가지고 사용자 입력 숫자열을 만들어보죠. 먼저 테스팅 코드에 다음을 추가해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.junit.Assert.assertEquals;

    public class UserInputBallsGeneratorTest {

    //이전과 동일

    @Test
    public void generatorCanBeGenerateBalls(){
    generator.generateBalls();
    }
    }

    이제 테스트가 실패했습니다. 바로 소스코드를 만들도록 하죠. 일단 게임에서 정의한 숫자열은 문자열 타입이기 때문에 문자열을 반환해야 합니다. 따라서 일단은 다음과 같이 작성해주세요.


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Arrays;

    public class UserInputBallsGenerator {
    //이전과 동일

    public String generateBalls() {
    return "";
    }
    }


    이제 숫자열을 생성하는 로직을 체크해볼까요?


    1. 문자열을 사용자에게 입력받는다.
    2. 입력받은 문자열이 게임에서 유효한 정보인지 검사한다.
    3. 검사를 통과하면 반환하고 실패하면 1~2를 반복한다.


    이것을 바로 코드로 옮겨보겠습니다.


    [src/main/java/UserInputBallsGenerator.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Arrays;

    public class UserInputBallsGenerator {
    //이전과 동일

    public String generateBalls() {

    while (true){
    System.
    out.println("숫자를 입력해주세요 ex)123 :");
    String balls = read();

    if (validate(balls))
    return balls;
    System.out.println("숫자열이 유효하지 않습니다. 다시 입력해주세요");
    }
    }
    }

    이제 테스트를 하면 갑자기 테스트가 먹통이 되는 것을 볼 수 있습니다. 아무래도 테스트 코드에 무한루프 혹은 시스템 입력을 사용하는 코드를 쓰면 내부적으로 문제가 생기는 듯 한데 이유는 잘 모르겠습니다. 좀 불안하신 분들은 main 메소드를 만들어서 한 번 생성기를 생성하고 generateBalls() 메소드를 호출해서 직접 테스트를 하는 것도 좋은 방법입니다. 이제 Git 에 "UserInputBallsGenerator 완성" 이라고 코멘트를 달아서 올리도록 하지요.


    3. SRP (Single Responsibility Principle) 하나의 클래스는 하나의 기능만!


    프로그래밍 제약 사항에 메소드 하나는 한 가지 일만을 수행해야 한다를 기억하시나요? 이것과 비슷한 의미로 클래스 역시 하나의 기능만을 담당해야 한다는 규칙이 있습니다. 이른바 SOLID 규칙, 그 중 S에 해당하는 SRP, 단일 책임의 원칙을 따라야 한다는 말이죠. 그렇다면 UserInputBallsGenerator는 이 규칙을 잘 준수하고 있을까요? 음 보는 시각에 따라 다르겠지만 저는 입력 기능, 검증 기능 역시 클래스로 뺴야 한다고 보았습니다. 생성기는 숫자열 생성만을 담당하는 것이고 내부의 입력기와 검증기의 도움을 받는 형태가 조금 더 객체 지향에 가깝다고 생각하기 때문이지요. 자 이제 SRP에 따라 클래스를 바꿔봅시다. 먼저 기능들을 다시 클래스로 분류해야 합니다. 일단 입력 부분은 BufferedReader와 read를 갖는 ConsoleUserInputReader로 만들도록 하죠. 그렇다면 코드는 다음과 같이 바뀔겁니다.


    [src/main/java/UserInputBallsGenerator.java]

    import java.util.Arrays;

    public class UserInputBallsGenerator {
        //이전과 동일 필드
    private ConsoleUserInputReader reader;

    //이전과 동일 디폴트 생성자

    public UserInputBallsGenerator(int maxLen){
    this.maxLen = maxLen;
    this.reader = new ConsoleUserInputReader(maxLen);
    }

    //이전과 동일 validate
    public String generateBalls() {

    while (true){
    System.
    out.println("숫자를 입력해주세요 ex)123 :");
    String balls = reader.read();

    if (validate(balls))
    return balls;
    System.out.println("숫자열이 유효하지 않습니다. 다시 입력해주세요");
    }
    }

     

    [src/main/java/ConsoleUserInputReader.java]

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    public class ConsoleUserInputReader {
    private int maxLen;
    private BufferedReader br;

    public ConsoleUserInputReader(int maxLen){
    this.maxLen = maxLen;
    br = new BufferedReader(new InputStreamReader(System.in));
    }

    public String read(){
    String input = null;

    try{
    input = br.readLine();
    }catch (IOException e){
    e.printStackTrace();
    }

    return input;
    }
    }

    딱 읽는 기능만 클래스로 뽑아온 것이죠. 검증기도 마찬가지로 검증 부분만 따로 빼서 만들 수 있습니다. 검증기를 빼면 다음처럼 코드가 변경될겁니다.


    [src/main/java/UserInputBallsGenerator.java]

    import java.util.Arrays;

    public class UserInputBallsGenerator {
    //이전과 동일 필드
    private UserInputValidator validator;

    //이전과 동일 디폴트 ㅅ

    public UserInputBallsGenerator(int maxLen){
    this.maxLen = maxLen;
    this.reader = new ConsoleUserInputReader(maxLen);
    this.validator = new UserInputValidator(maxLen);
    }

    public String generateBalls() {

    while (true){
    System.
    out.println("숫자를 입력해주세요 ex)123 :");
    String balls = reader.read();

    if (validator.validate(balls))
    return balls;
    System.out.println("숫자열이 유효하지 않습니다. 다시 입력해주세요");
    }
    }

    }

    [src/main/java/UserInputValidator.java]

    import java.util.Arrays;

    public class UserInputValidator {
    private int maxLen;

    public UserInputValidator(int maxLen){
    this.maxLen = maxLen;
    }

    public boolean validate(String input) {
    return isInputNotNull(input) && isInputLengthEqualsGamesLength(input)
    && isInputComposedOnlyOneToNine(input) && isInputComposedAllDifferentNumbers(input);
    }
    private boolean isInputNotNull(String input){
    return input != null;
    }
    private boolean isInputLengthEqualsGamesLength(String input){
    return input.length() == maxLen;
    }
    private boolean isInputComposedOnlyOneToNine(String input){

    for (int i=0; i<input.length(); i++){
    int c = input.charAt(i) - '0';

    if( !(c >= 1 && c <= 9) )
    return false;
    }

    return true;
    }
    private boolean isInputComposedAllDifferentNumbers(String input){
    long cnt = 0;

    for (int i=0; i<input.length(); i++)
    cnt += countCharacterAtIndexOnInput(input, i);

    return cnt == maxLen;
    }
    private long countCharacterAtIndexOnInput(String input, int idx){
    final char c = input.charAt(idx);
    long cnt = Arrays.stream(input.split("")) //문자열을 쪼개서
    .map(s -> s.charAt(0)) //쪼갠 하나의 문자열을 Char형으로
    .filter( s -> s == c) //현재 문자와 같은 요소들 필터링
    .count(); //요소 카운팅
    return cnt;
    }
    }

    이렇게 코드를 분리하면 기존 테스트 코드 역시 망가집니다. 따라서 테스트 코드도 다음과 같이 변경해주세요.


    [src/test/java/UserInputBallsGeneratorTest.java]

    import org.junit.Before;
    import org.junit.Ignore;
    import org.junit.Test;

    public class UserInputBallsGeneratorTest {

    private UserInputBallsGenerator generator;

    @Before
    public void setUp() throws Exception {
    generator = new UserInputBallsGenerator(Game.DEFAULT_LEN);
    }

    //테스트 코드에서는 무한루프를 돌리면 안되는 것 같다....
    @Ignore
    @Test
    public void generatorCanBeGenerateBalls(){
    generator.generateBalls();
    }
    }


    [src/test/java/UserInputValidatorTest.java]

    import org.junit.Before;
    import org.junit.Test;

    import static org.junit.Assert.assertEquals;

    public class UserInputValidatorTest {
    private UserInputValidator validator;
    @Before
    public void setUp(){
    validator = new UserInputValidator(Game.DEFAULT_LEN);
    }

    @Test
    public void validateNotNull(){
    assertEquals(validator.validate("123"), true);
    assertEquals(validator.validate(null), false);
    }

    @Test
    public void validateIsMaxLen(){
    assertEquals(validator.validate("123"), true);
    assertEquals(validator.validate("12"), false);
    assertEquals(validator.validate("1234"), false);
    }

    @Test
    public void validateIsOnlyComposed1to9(){
    assertEquals(validator.validate("123"), true);
    assertEquals(validator.validate("12a"), false);
    assertEquals(validator.validate("ㄱ12"), false);
    assertEquals(validator.validate("!12"), false);
    }

    @Test
    public void validateAllDifferentNumber(){
    assertEquals(validator.validate("123"), true);
    assertEquals(validator.validate("456"), true);
    assertEquals(validator.validate("111"), false);
    assertEquals(validator.validate("112"), false);
    assertEquals(validator.validate("121"), false);
    assertEquals(validator.validate("211"), false);
    assertEquals(validator.validate("337"), false);
    assertEquals(validator.validate("272"), false);
    assertEquals(validator.validate("499"), false);
    }
    }

    자 SRP 규칙에 따라 코드도 분리하였으니 Git에 "UserInputBallsGenerator SRP 적용"이라고 올리도록 하죠. 


    4. 마치며....


    이번 포스팅에서는 사용자 입력 숫자열 생성기에 대해서 TDD로 개발하는 것을 해보았습니다. 그리고 SRP 원칙에 따라서 클래스를 생성기 안에 입력기와 검증기를 클래스로 만들어 분리하였습니다. 다음 포스팅에서는 게임 결과 출력기에 대해서 개발해보도록 하겠습니다.

    728x90
Designed by Tistory.