프로그래밍 언어/Java

[JAVA] 단위테스트 공부3: 활용하기

fladi 2023. 11. 1. 02:39
728x90

 

이전 글

https://fladi.tistory.com/369

 

[JAVA] 단위테스트 공부1: Mock 알아보기

객체지향 프로그램에서는 여러가지 객체들이 서로 협력하면서 메시지를 주고 받는다. 그렇기 때문에 클래스의 독립적인 메서드를 테스트하기 위해서는 협력하는 상대를 가짜객체로 만들어서

fladi.tistory.com

https://fladi.tistory.com/370

 

[JAVA] 단위테스트 공부2: 테코톡 보고 정리

 

fladi.tistory.com

 

 

이번에는 이전에 공부한 내용을 바탕으로 활용한 내용을 설명하겠다

 

@ParameterizedTest

//Class Car
public void moveOrStop() {
    if (isMove()) {
        move++;
    }
}

private boolean isMove() {
    int randomNumber = Randoms.pickNumberInRange(0, 9);

    if (randomNumber >= 4) {
        return true;
    }

    return false;
}
@ParameterizedTest
@CsvSource(value = {"0, 0", "1, 0", "2, 0", "3, 0", "4, 1", "5, 1", "6, 1", "7, 1", "8, 1", "9, 1"})
@DisplayName("랜덤값에 따라 자동차 전진/멈추기 테스트")
void moveOrStop_동작_확인(int randomValue, int expected) {
    try (MockedStatic<Randoms> mock = Mockito.mockStatic(Randoms.class)) {
        mock.when(() -> Randoms.pickNumberInRange(anyInt(), anyInt())).thenReturn(randomValue);

        Car car = new Car("붕붕카");
        car.moveOrStop();

        assertThat(car.getPath()).isEqualTo(expected);
    }
}
  • Randoms.pickNumberInRange에서 randomValue가 나올 경우 결과값에 대해 테스트한다
  • @ParameterizedTest를 붙이고
  • @CsvSource 부분에서 테스트메서드 인자로 들어갈 값을 지정한다
    • "1, 0"의 경우 randomValue에 1이 들어가고, expected에 0이 들어간다
  • try resource문에서 Randoms에 대한 static mock객체를 생성한다
    • try문 내부에서는 Randoms의 pickNumberInRange(anyInt(), anyInt()) 메서드를 호출했을 때 randomValue를 return하도록 mock을 설정해준다.
    • Car를 새로 만들고, moveOrStop() 을 호출하면 내부에서 Randoms.pickNumberInRange 함수를 호출한다
  • 그리고 car.getPath()를 호출했을 때 나오는 값이 expected인지 확인한다

 

  • 다음과 같이 CsvSource에 넣은 값으로 반복하여 테스트가 진행된다
  • 묶여서 나오는 것도 하나의 장점이다

 

@Nested

class InputValidatorTest {
    private InputValidator inputValidator = new InputValidator();

    @DisplayName("자동차 이름 검증")
    @Nested
    class ValidateCarNames {
        @Test
        @DisplayName("정상동작")
        void good_case() {
            //when
            String goodCarNamesInput = "카,붕붕카,타요버스,시내버스,토마스버스,'";

            //then
            assertDoesNotThrow(() -> {
                inputValidator.validateCarNames(goodCarNamesInput);
            });
        }

        @ParameterizedTest
        @CsvSource(value = {"'붕붕카,타요버스,'", "'붕붕카,'", "',붕붕카'", "'붕붕카,,타요버스'"})
        @DisplayName("쉼표구분 예외 발생")
        void 쉼표구분_예외_발생(String input) {
            assertThatThrownBy(() -> inputValidator.validateCarNames(input))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("자동차 이름의 길이는 1이상 5이하여야 합니다");
        }
    }
  • @Nested로 관련있는 테스트를 한 곳에 묶었다
  • "붕붕카,타요버스" 라는 String 자체를 input에 넣기위해 " 안에 ' 를 넣었다
  • @Nested가 된 테스트들은 다음과 같이 묶여서 테스트된다

 

이렇게 자동차 이름 입력에 쉼표구분 테스트와 정상동작 테스트가 묶여서 나오는 걸 볼 수 있다

 

 

Console.readLine() 관련 테스트

class ConsoleInputTest {
    static InputValidator inputValidator;
    static ConsoleInput consoleInput;

    @BeforeAll
    static void init() {
        inputValidator = mock(InputValidator.class);
        consoleInput = new ConsoleInput(inputValidator);
        when(inputValidator.validateCarNames(ArgumentMatchers.anyString()))
                .thenReturn(true);
        doNothing().when(inputValidator).validateMoveCount(ArgumentMatchers.anyString());
    }

    @AfterEach
    void clearSystemIn() {
        Console.close();
    }

    @DisplayName("자동차 이름 입력")
    @Test
    void goodCase() {
        //given
        byte[] buf = "붕붕카,타요버스,시내버스,토마스버스".getBytes();
        System.setIn(new ByteArrayInputStream(buf));

        //when
        List<String> carNames = consoleInput.getCarNames();

        //then
        assertThat(carNames).contains("붕붕카", "타요버스", "시내버스", "토마스버스");
        assertThat(carNames).containsExactly("붕붕카", "타요버스", "시내버스", "토마스버스");
    }
}
  • 테스트 메서드를 실행하기 전 ConsoleInput이 가지고 있는 InputValidator를 mock으로 설정하여 아무동작도 수행하지 않도록 한다
  • 그다음 System.setIn 을 이용하여 "붕붕카,타요버스,시내버스,토마스버스"를 시스템의 인풋으로 넣어준다(콘솔 입력값으로 넣어준다고 생각하면 된다)
  • 그리고 consoleInput.getCarNames()를 호출한다
    • System.in을 위에서 설정해줬기 때문에 해당 값을 받아와 List<String>을 만들어 반환한다
  • 해당 리스트에 순서에 맞게 "붕붕카", "타요버스", "시내버스", "토마스버스"가 포함되어있는지 검증한다

 

System.out.print 관련 테스트

class ConsoleOutputTest {
    private OutputStream captor;
    private Output output;

    @BeforeEach
    void init() {
        captor = new ByteArrayOutputStream();
        System.setOut(new PrintStream(captor));
        output = new ConsoleOutput();
    }

    @AfterEach
    void cleanBuffer() {
        System.out.flush(); //출력버퍼 비우기
    }

    @Test
    @DisplayName("자동차 이름 입력 문구 출력")
    void print_car_names_input_request() {
        output.printCarNamesInputRequest();
        assertThat(getOutput()).contains("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
    }
    
    String getOutput() {
        return captor.toString();
    }
}
  • 메서드 실행 전, System.out으로 나올 값의 출구를 capor에 저장하는 새로운 PrintStream으로 설정한다
  • 그리고 output을 실행하여 captor에 system.out으로 나오는 값을 받는다
  • 받은 값에 "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"가 포함되어 있는지 확인한다
  • 메서드를 끝내면 System.out에 들어있는 값을 싹 비워준다 (다른 테스트를 진행하기 위함)

 

assertThatThrownBy

assertThatThrownBy(() -> inputValidator.validateMoveCount(input))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("이동 횟수는 아홉 자릿수 이내여야 합니다.");
  • inputValidator객체의 validateMoveCount(input) 메서드를 실행했을 때 예외가 발생하는 지 확인한다
    • 예외가 발생해야 정상
  • 그리고 그 예외가 IllegalArgumentException인지 확인한다
  • 또한 예외에 "이동 횟수는 아홉 자릿수 이내여야 합니다"가 포함되어있는지 테스트한다

 

 

assertDoesNotThrow()

assertDoesNotThrow(() -> {
                inputValidator.validateMoveCount(goodMoveCountInput);
            });
  • 해당 코드를 수행했을 때 예외가 발생하지 않는지 테스트한다

 

728x90