우테코

[우테코 2주차] 디버깅으로 입출력 예외 분석하기

wldud8297 2025. 10. 25. 19:52

 

우테코 공통 피드백으로 나온 디버깅을 사용해서 입출력 문제를 해결해보았다.

 

0. 문제 상황

입력을 2번 받기 때문에, 테스트 케이스도 2개를 작성하였다.

@Test
void doRacingGame_자동차_빈문자열_예외_테스트() {
    assertSimpleTest(() ->
            assertThatThrownBy(() -> runException("", "1"))
                    .isInstanceOf(IllegalArgumentException.class)
    );
}

@Test
void doRacingGame_시도횟수_빈문자열_예외_테스트() {
    assertSimpleTest(() ->
            assertThatThrownBy(() -> runException("pobi", ""))
                    .isInstanceOf(IllegalArgumentException.class)
    );
}

 

실패하는 테스트 인지 확인한다.
("Expecting code to raise a thowable")

에러를 던져야하는데, 던지지 않았다는 에러문이기 때문에 적절한 테스트이다.

 

 

검증기 코드를 작성한다.

// 빈 문자열 검사
public static void validateNotEmpty(String inputMessage){
    if (inputMessage==null || inputMessage.isEmpty()){
        throw new IllegalArgumentException(ErrorMessage.INPUT_NULL_ERROR.getMessage());
    }
}

 

 

검증기 코드를 controller에서 호출한다.

 

 

 

위에는 통과했는데, 같은 검증 코드로 아래의 테스트는 실패했다.

 

 

같은 검증기 코드를 적용했는데 왜 하나는 실패할까? 🤷🏻‍♀️

 

이것의 해답을 디버깅을 통해 알아보겠다.

 


 

1. 디버깅 사용법

1. Breakpoint(빨간콩) 찍기
2. 디버깅할 파일 선택하기
3. 디버깅 수행

 

1) Breakpoint 정하기

 

입력을 받는 순간에 빨간콩(breakpoint)를 찍어보자.

 

 

 

2) 디버깅 대상 선택

 

이제 해당 테스트를 디버깅 모드로 실행시킨다.

 

 

3) 디버깅 수행

 

디버그가 실행되면 빨간콩(breakpoint)을 찍은 곳에서 멈춘다.

 

 

디버깅 버튼을 소개하자면 아래와 같다.

해당 버튼을 이용하여, 원하는 곳으로 디버깅 포인트를 이동해 문제점을 파악하면 된다.

 

 


 

2. 디버깅으로 문제점 찾기

이제 디버깅으로 왜 두번째 테스트에서 실패하는지 파악해보겠다.

 

1) 두 테스트에서 할당되는 변수값 비교

성공한 "자동차_빈문자열_예외_테스트"의 경우,

빈문자열이 들어오면 inputCarName변수에 ""이 할당되어, 검증기 코드에서 에러를 던진다.

 

 

 

그에 반해, 실패한 "시도횟수_빈문자열_예외_테스트"의 경우,

아예 inputTryNumber 변수에 값이 할당이 안되고, 바로 Console.close()가 호출된다.

 

 

정리하자면, 두 개의 값을 입력해야할 때, 
(,값)으로 입력될 때는 ""이 할당되어 검증기가 예외를 던진다.
(값,) 으로 입력될때는 변수의 할당 없이 바로 Console.close()가 호출된다.

2) 디버깅으로 문제점 찾기

 

두 번째 테스트에서 step into 버튼을 타고 들어가면, Scanner.nextLine() 메소드에 도달한다.

 

 

nextline() 메소드는 현재 라인을 skip하고, 그 라인을 출력하는 함수이다.
즉, line separator를 기준으로 이전 문장을 출력한다는 말이다.

 

예를 들어, "사자\n호랑이" 로 입력이 들어오면, \n 기준으로 현재 라인을 skip하고 사자를 출력한다는 말이다.

 

 

첫번째 테스트코드를 보면, 입력이 "\n1"이다.

따라서, 첫번째 호출에서 "", 두번째 호출에서 1을 반환한다.

assertThatThrownBy(() -> runException("", "1"))

 

하지만 두 번째 테스트 코드를 보면, 입력이 "pobi\n"이다.

첫번째 호출에서 "pobi"를 반환한 후 값이 없기 때문에,

두 번째 호출에서 NoSuchElementException("No line found")을 던진다.

assertThatThrownBy(() -> runException("pobi", ""))

 


 

3. 실제 애플리케이션 Run과 테스트 Run의 차이

디버깅을 통해 할당되는 변수값이 다르다는 사실을 알아냈다.

그렇다면 왜 할당되는 변수의 값이 다른가요?

 

실제 애플리케이션을 실행을 시켜서 아무것도 입력안하고 엔터를 치면, 제대로 에러가 발생한다.

왜 테스트는 실패하고, 애플리케이션에서는 정상적으로 에러를 던지는 건가요?

 

이는 테스트 환경과 실제 애플리케이션 run 환경이 다르기 때문이다.

실제 애플리케이션은 사용자의 입력을 받기위해 엔터를 입력한다. (="/n"이 입력된다)

 

즉, 사용자의 입력이 무조건 "\n"로 끝난다.

(터미널 입력으로 사용자의 입력을 받는 일반적인 경우를 말함. 특수한 경우는 고려하지 않겠다.)

 

 그래서 실제 애플리케이션 Run처럼 테스트 코드에서 \n을 붙이면, "입력이 비어있습니다." 예외를 던지고 실패했던 테스트가 통과한다.


 

그러면 왜 테스트에서 Error를 던지지 않고, 정상종료(console.close())가 되나요?

 

두 번째 테스트 케이스를 보면, "Expecting code to raise a thowable" 에러라고 뜨는데

이는 에러를 던지지 않고 애플리케이션이 정상 종료되었다는 걸 의미한다. 

 

nesxtLine()에서 호출한 NoSuchElementException()에러가 떠야하는 거 아닌가요?

 

위에서 말했듯이, 실제 애플리케이션과 테스트 코드에는 환경 차이가 있다.

그래서 보통 테스트에서는 입력이 끝났거나(EOF) 스캐너가 입력을 더 이상 받을 수 없을 때 던져지는 예외(NoSuchElementException())를 무시한다.

 

실제 runException 메소드를 살펴보면, NoSuchElementException을 ignore(무시)하는 코드가 있다. 그래서 에러를 던지지 않고, console.close()로 넘어가서 실행이 종료되는 것이다.

 


4. 문제 해결

그렇다면, 입력과정에서 해야하는 예외처리는 두가지이다.

1. 입력 받았을때, 빈 문자열 ""
2. 입력 받았을때, NoSuchElementException 에러

 

그래서 이 두 가지를 해줄 getNotNullInputMessage()을 만들었다.

public String getInputMessage(){
    String inputMessage = Console.readLine().trim();
    return inputMessage;
}
public String getNotNullInputMessage(){
    try{
        String inputMessage=getInputMessage();
        Validator.validateNotEmpty(inputMessage);
        return inputMessage;
    } catch(NoSuchElementException e){
        throw new IllegalArgumentException(ErrorMessage.INPUT_NULL_ERROR.getMessage());
    }
}

 

이 값을 controller에서 호출해주면, 테스트가 통과한다.

 

 


 

이번 시간에는 디버깅을 통해서 입출력 오류에 대해 분석해보았다.

 

지난 주 과제를 풀면서 왜 입출력 에러가 제대로 잡히지 않을까?라는 의문이 있었는데, 

System.out.println()으로 로그를 찍어도 값이 나오지 않아 답답했었다.

 

때마침, 1주차 우테코 공통 피드백에서 디버깅에 대해 알려주셔서, 이를 적용해보았다.

 

이번 기회로 디버깅을 실습해보면서, 너무 편리한 기능이라는 걸 체감했다.

하나하나 로그를 찍지 않아도 되고, 코드가 진행되가는 과정을 살펴볼 수 있다는 것이 문제를 해결하는데 큰 도움이 되었다.

 

다음에는 조건문을 넣거나, 원하는 변수를 지정하여 추척하는 기능 등의 더 다양한 디버깅 기능을 사용해봐야겠다.