Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

웹 백엔드 미션 유틸리티 라이브러리를 제작한다. - 7월 28일 #87

Closed
woowahan-pjs opened this issue Jul 19, 2021 · 4 comments

Comments

@woowahan-pjs
Copy link
Contributor

No description provided.

@unluckyjung
Copy link

테스트케이스가 여러개인경우, Scanner가 닫히는 상황을 해결한다.

Sol 1

Scanner 인스턴스화를 앱을 구동하는 main에서 수행하도록 한다.

public class WoowaScanner {
    private static Scanner scanner;

    public static void run() {
        // 다른 테스트 때마다 App이 다시도니, App 최초 실행시 run 메소드를 호출하여 System.in을 다시 받으면 해결
        scanner = new Scanner(System.in);
    }

    public static String nextLine() {
        return scanner.nextLine();
    }

    public static Integer nextInt() {
        return scanner.nextInt();
    }
}
package utils;

import java.io.IOException;

public class App {

    public static void main(String[] args) throws IOException {
        // 지우지 말라는 코멘트
        WoowaScanner.run();

        // =======아래부터 코드를 작성하도록 유도========= //
        App app = new App();
        app.run();
    }
}
@DisplayName("입력과 숫자를 입력받고, 출력한다.")
@ParameterizedTest
@CsvSource({"시드,-1", "포츈,1"})
void 이름과_숫자를_입력(final String name, final String number) {

    assertSimpleTest(() -> {
        subject(
                name, number,
                // 종료 트리거
                STOP
        );
        assertThat(captor.toString().trim()).contains(name, number);
    });
}
  • 한 테스트에서 여러개의 입력, 하나의 테스트가 끝난뒤 다른 테스트에 대한 수행 전부 통과하는것을 확인함.


Wrong Case

public class WoowaScanner {
    // 필드에서 생성, 관리할시 여러 입력에 대한 처리는 되나, 다른 테스트에서 문제발생.
    // @CsvSource({"시드", "포츈"})
    private static Scanner scanner = new Scanner(System.in);

    public static String nextLine() {
        // @CsvSource({"시드,포츈"})
        // 내부에서 생성시 다른테스트에 대한 처리는 되나, 여러 입력에 대한 처리가 안됨.
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}

그외 시도해본방법

  • 스캐너를 이용하지 않고, Stream에 직접적으로 접근하여 Scanner에서 제공하는 기능을 라이브러리화 시킴
    • 다른 테스트의 경우 결국 System.in 부분이 새로 필요하여 실패

@hsik0225
Copy link

Wrong Case

InputView에서 Scanner를 스태틱으로 생성하고, 생성한 스캐너를 이용해서 사용자 입력을 받는 메소드를 만들었다. 그 후 테스트 코드에서 getInput() 메소드를 모킹하여 원하는 결과를 출력하도록 하려고 했다.

public class InputView {
    private static final Scanner SCANNER = new Scanner(System.in);
    
    public static String getInput() {
        return SCANNER.nextLine();
    }
}

@ParameterizedTest
@ValueSource(strings = {"시드", "포츈"})
void 잘못된_상품_구매_이름_테스트(final String productName) {
    try (MockedStatic<InputView> inputViewMock = Mockito.mockStatic(InputView.class)) {
        inputViewMock.when(InputView::getInput).thenReturn("2", productName, MAIN_EXIT_NUMBER);
        assertSimpleTest(() -> {
            ...
}

하지만 모킹한 메소드가 아닌, 실제 메소드인 SCANNER.nextLine()가 실행되었고, 프로그램은 사용자의 입력을 기다렸다가 일정 시간동안 입력이 들어오지 않아 실패했다.


MockedStatic은 현재 스레드를 키로, 설정한 목 메소드를 밸류로 갖는다.
즉, 테스트를 실행한 [main, 5, main] 스레드를 키로, 2를 반환하는 메소드를 밸류로 갖는다.

assertSimpleTest는 [junit-timeout-thread-1, 5, main]라는 새로운 스레드를 생성 후, 그 스레드에서 테스트를 실행한다.

그래서 기존 [main, 5, main]과 매핑되어 있던 목 메소드가 아닌, [junit-timeout-thread-1, 5, main]과 매핑된 메소드를 실행한다.

매핑된 메소드가 null 이므로, 기존 메소드를 실행하게 되어 테스트가 실패하게 된다.


새로운 스레드를 생성하는 것이 아닌 기존 스레드에서 타임아웃을 체크해야 한다. 코드가 복잡해져서 이 방법은 포기하기로 했다.

@Joyykim
Copy link

Joyykim commented Jul 26, 2021

Sol 2

리플렉션을 사용해 Scanner가 닫혔는지 확인하고 닫혔다면 새로 생성하도록 했습니다.
프로덕션 코드에서는 새로 Scanner를 생성하는 등의 작업이 필요하지 않습니다.
단점은 입력을 받는 매번 리플렉션을 사용하기 때문에 속도가 느릴 수 있다는 점입니다.

public class ScannerUtils {

    private static Scanner scanner = getScanner();

    public static String nextLine() {
        makeNewScannerIfScannerIsClosed();
        return scanner.nextLine();
    }

    public static int nextInt() {
        makeNewScannerIfScannerIsClosed();
        return scanner.nextInt();
    }

    // 스캐너가 닫혀있다면 새로운 스캐너 생성
    private static void makeNewScannerIfScannerIsClosed() {
        if (scanner == null || scannerIsClosed()) {
            scanner = getScanner();
        }
    }

    // Scanner의 private 필드 sourceClosed를 리턴
    private static boolean scannerIsClosed() {
        try {
            Field sourceClosedField = Scanner.class.getDeclaredField("sourceClosed");
            sourceClosedField.setAccessible(true);
            return sourceClosedField.getBoolean(scanner);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.out.println("리플렉션 중 에러 발생");
        }
        return true;
    }

    private static Scanner getScanner() {
        return new Scanner(System.in);
    }
}

@woowahan-pjs woowahan-pjs changed the title 웹 백엔드 미션 유틸리티 라이브러리를 제작한다. - 7월 26일 웹 백엔드 미션 유틸리티 라이브러리를 제작한다. - 7월 28일 Jul 26, 2021
@unluckyjung
Copy link

unluckyjung commented Aug 1, 2021

피드백 반영

중앙 저장소 다운로드.

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

dependencies {
	  implementation 'com.github.Joyykim:wooteco-utils:0.0.3'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants