모든 코드는 여기를 클릭 하시면 확인 하실 수 있습니다.

안녕하세요. 벌써 4탄까지나 쓰게 되었네요...! 이번 포스팅에서는 아래와 같은 기능들을 테스트하고 구현하여 리팩토링하여 출력을 제외한 나머지 부분을 마무리 지을까 합니다. 완성된 예제코드는 모든 포스팅을 마무리 하고 추가로 올리겠습니다.

  • 0~9사이의 난수를 생성하는 유틸 클래스를 테스트 및 구현한다.
  • 자동차는 생성된 난수를 입력 받아 4이상의 값이면 이동하고 3이하의 값이면 이동하지 않는다.

0~9사이의 난수를 생성하는 유틸 클래스를 테스트 및 구현한다.

  • 먼저 0 ~ 9 사이의 난수를 생성한다는 테스트를 구현해보겠습니다.
    package kail.study.java.racing.util;
    
    import static org.assertj.core.api.Assertions.*;
    
    import org.junit.jupiter.api.Test;
    
    public class RandomUtil {
    
    	public static final int ENOUGH_BIG_NUMBER = 10000;
    
    	@Test
    	void create() {
    		for(int i = 0; i< ENOUGH_BIG_NUMBER; i++) {
    			assertThat(RandomGenerator.create()).isBetween(0,9);
    		}
    	}
    }
  • 0 ~ 9 사이의 난수가 정확하게 생성된다는 부분은 테스트를 어떻게 작성 해야 하는 지 잘 모르겠습니다. 좋은 방법을 아시는 분은 댓글로 알려주시면 감사하겠습니다. 저는 충분히 큰 수만큼 테스트를 돌렸을 때도 정상적으로 작동한다는 정도로 테스트를 짜보았습니다. 당연히 위 코드는 담당 클래스와 메소드가 없기 때문에 작동하지 않겠죠? 그럼 이제 Production code 를 작성하겠습니다.
    package kail.study.java.racing.util;
    
    import java.util.Random;
    
    public class RandomGenerator {
    	private static final int BOUND = 10;
    
    	public static int create() {
    		return new Random().nextInt(BOUND);
    	}
    }
  • 유틸성 클래스라고 판단해 RandomGenerator라는 유틸클래스를 생성하였고 기존의 StringUtil 뿐 아니라 각 유틸클래스가 기능이 많지 않아 하나의 유틸 클래스로 통합해도 된다고 생각하지만 보는 사람이 구분이 쉽도록 하기 위해 따로 분리하였습니다. 따로 리팩토링 할 부분은 없는 것 같아요.
  • 추가적으로 조금 얘기하자면 원래 TDD의 방법론으로 하자면 단순히 create() 매소드가 3이라는 임의의 수를 반환하게 하는 것이 테스트가 통과하는 가장 쉬운 로직이 되겠죠? 이후에 리팩토링을 통해 범용적으로 적용될 수 있는 로직을 작성하고 클래스 이름 및 분류 등 설계 부분을 다시 리팩토링하는게 맞는 방법입니다. 하지만 사람마다 본인이 머리속에 바로 떠오르는 부분은 굳이 앞 단계를 할 필요는 없다고 생각해서 바로 new Random().nextInt()를 통해 로직을 작성하였습니다. 글을 읽으시는 분들도 본인의 수준에 맞게 어려운 부분은 단순히 테스트만 통과하는 로직을 작성하고 그 이후에 로직작성 및 리팩토링을 해보는게 좋을 거라 생각됩니다. 저 또한 그렇게 하려고 합니다.
  • 다시 돌아와서 완벽한 로직은 아니지만 ( 0 ~ 9 사이의 난수가 10000번 돌렸을 때는 나왔으나 아닌 경우도 있을 수 있기에 ) 랜덤값을 어느정도 검증하는 테스트 및 로직을 완성하였습니다. 그렇다면 이제 저렇게 생성된 랜덤값에 의해 자동차의 전진 및 스탑을 결정하는 코드를 작성해보겠습니다.

자동차는 생성된 난수를 입력 받아 전진 혹은 정지 한다.

  • 자동차는 생성된 난수를 입력 받아 특정 로직 ( 4 이상이면 전진 3이하면 정지 ) 를 수행한다 기능에 대해 테스트부터 작성 해 보겠습니다.
        @ParameterizedTest
    	@ValueSource(ints = {4,7,9})
    	@DisplayName("4이상의 수가 입력되어 전진하는 경우")
    	void 전진하는_경우(int randomValue) {
    		Car car = new Car("pobi");
    		car.move(randomValue);
    		assertThat(car.getPosition()).isEqualTo(1);
    	}
  • 위의 코드는 자동차에 move라는 매소드가 존재하지 않고 getPosition() 또한 존재하지 않아 컴파일 오류가 발생합니다. 이를 통과시키는 코드를 작성 해 보겠습니다. 아래의 코드를 통해 이동하는 경우와 정지하는 경우 모든 경우가 하나의 Production 로직으로 해결이 되기 때문에 테스트와 프로덕션 코드 모두를 한 번에 추가하겠습니다.
    public void move(int randomValue) {
    		if(randomValue >= MOVE_STANDARD){
    			this.position++;
    		}
    	}
    public int getPosition() {
    		return position;
    	}

        @ParameterizedTest
    	@ValueSource(ints = {1,0,3})
    	@DisplayName("3이하의 수가 입력되어 정지하는 경우")
    	void 정지하는_경우(int randomValue) {
    		Car car = new Car("pobi");
    		car.move(randomValue);
    		assertThat(car.getPosition()).isEqualTo(0);
    	}
  • 이렇게 Production Code 를 수정하면 테스트는 정상적으로 작동이 됩니다. 이 포스팅에서 지금까지 한 부분을 요약하자면 랜덤한 값에 의해 자동차의 전진유무가 결정되기 때문에 랜덤한 값이 정상적으로 반환이 되는지, 그리고 자동차는 그 랜덤한 값이 파라미터로 넘어왔을 때 그 값을 판단해 이동을 하는지에 대해 테스트 하였습니다.
  • 추가적으로 이러한 방식에는 문제점을 가지고 있습니다. 랜덤한 값을 테스트 하는 것에 대해 많은 횟수를 통해 검증한 부분이 항상 동작한다고 말할 수 없으며 자동차의 위치는 다양한 값에 의해 변경될 수 있지만 현재는 RandomGenrator에 의존하고 있습니다. 인터페이스를 통해 그 인터페이스를 상속하고 있는 다양한 방법을 클래스로 구현하는 방식을 채택하면 이러한 의존성을 줄일 수 있습니다. 그 부분은 포스팅을 마무리하고 후편에서 다뤄보도록 하겠습니다.
다음 포스팅에서는 프로그램 실행을 위해 도메인 코드를 연결하고 던졌던 예외를 잡는 부분까지 추가하여 TDD로 개발하기 부분을 마무리 하겠습니다. README.md 에 있던 부분 중 체크되지 않은 부분은 포스팅을 마무리하고 번외(?) 편에서 리팩토링을 할 예정이니 그 부분은 다다음 포스팅에서 확인해주세요~

0개의 댓글