System.out.println ()에 대한 JUnit 테스트


370

제대로 설계되지 않은 표준 응용 프로그램에 많은 오류 메시지를 작성하는 이전 응용 프로그램에 대해 JUnit 테스트를 작성해야합니다. 때 getResponse(String request)방법이 제대로 작동 그것은 XML 응답을 반환합니다 :

@BeforeClass
public static void setUpClass() throws Exception {
    Properties queries = loadPropertiesFile("requests.properties");
    Properties responses = loadPropertiesFile("responses.properties");
    instance = new ResponseGenerator(queries, responses);
}

@Test
public void testGetResponse() {
    String request = "<some>request</some>";
    String expResult = "<some>response</some>";
    String result = instance.getResponse(request);
    assertEquals(expResult, result);
}

그러나 XML 형식이 잘못되었거나 요청을 이해하지 못하면 null일부 내용을 반환 하여 표준 출력에 씁니다.

JUnit에서 콘솔 출력을 주장하는 방법이 있습니까? 다음과 같은 경우를 잡으려면

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());

답변:


581

사용하여 있는 ByteArrayOutputStream을 하고 System.setXXX은 간단하다 :

private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;

@Before
public void setUpStreams() {
    System.setOut(new PrintStream(outContent));
    System.setErr(new PrintStream(errContent));
}

@After
public void restoreStreams() {
    System.setOut(originalOut);
    System.setErr(originalErr);
}

샘플 테스트 사례 :

@Test
public void out() {
    System.out.print("hello");
    assertEquals("hello", outContent.toString());
}

@Test
public void err() {
    System.err.print("hello again");
    assertEquals("hello again", errContent.toString());
}

이 코드를 사용하여 명령 행 옵션을 테스트했습니다 (-version이 버전 문자열 등을 출력하는지 확인).

편집 : 이 답변의 이전 버전은 System.setOut(null)테스트 후에 호출 되었습니다. 주석가가 참조하는 NullPointerExceptions의 원인입니다.


또한 JUnitMatchers를 사용하여 응답을 테스트했습니다. assertThat (result, containsString ( "<request : GetEmployeeByKeyResponse")); 고마워요
Mike Minicki

3
나는 그것이 VM이 시작되었을 때 무엇에 스트림 다시 복원 System.setOut (널)를 사용하는 것을 선호
tddmonkey

5
javadocs는 System.setOut 또는 System.setErr에 null을 전달할 수 있다는 것에 대해 아무 말도하지 않습니다. 이것이 모든 JRE에서 작동합니까?
finnw

55
NullPointerException위에서 제안한대로 ( java.io.writer(Object)XML 유효성 검사기에서 내부적으로 호출 된) null 오류 스트림을 설정 한 후 다른 테스트에서 발생했습니다 . 대신 원본을 필드에 저장 oldStdErr = System.err하고 @After방법으로 복원 하는 것이 좋습니다 .
Luke Usherwood

6
훌륭한 솔루션. 그것을 사용하는 모든 사람을위한 메모, outContent에서 공백 / 줄 바꿈을 트림 ()해야 할 수도 있습니다.
Allison

102

나는 이것이 오래된 스레드라는 것을 알고 있지만 이것을 수행하는 멋진 라이브러리가 있습니다.

시스템 규칙

문서의 예 :

public void MyTest {
    @Rule
    public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();

    @Test
    public void overrideProperty() {
        System.out.print("hello world");
        assertEquals("hello world", systemOutRule.getLog());
    }
}

또한 System.exit(-1)명령 행 도구를 테스트해야하는 트랩 및 기타 사항 을 포착 할 수 있습니다.


1
표준 출력 스트림은 프로그램의 모든 부분에서 사용하는 공유 리소스이기 때문에이 방법에는 문제가 있습니다. 표준 출력 스트림의 직접 사용을 제거하기 위해 의존성 삽입을 사용하는 것이 좋습니다 : stackoverflow.com/a/21216342/545127
Raedwald

30

리디렉션 대신에 공동 작업자로 전달 한 다음 프로덕션 에서 사용하고 테스트에서 테스트 스파이 를 사용하여 System.out사용하는 클래스를 리팩터링합니다 . 즉, 표준 출력 스트림을 직접 사용하지 않으려면 Dependency Injection을 사용하십시오.System.out.println()PrintStreamSystem.out

생산 중

ConsoleWriter writer = new ConsoleWriter(System.out));

시험에서

ByteArrayOutputStream outSpy = new ByteArrayOutputStream();
ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy));
writer.printSomething();
assertThat(outSpy.toString(), is("expected output"));

토론

이런 식으로 테스트중인 클래스는 표준 출력을 간접적으로 리디렉션하거나 시스템 규칙을 방해하지 않고도 간단한 리팩토링을 통해 테스트 할 수 있습니다.


1
JDK의 어느 곳에서도이 ConsoleWriter를 찾을 수 없습니다. 어디에 있습니까?
Jean-Philippe Caruana

3
아마도 대답에 언급되어야하지만 클래스는 user1909402에 의해 생성되었다고 생각합니다.
Sebastian

6
나는 ConsoleWriter시험 대상 이라고 생각 합니다.
Niel de Wet

22

setOut () 및 for in및을 통해 System.out 인쇄 스트림을 설정할 수 있습니다 err. 이것을 문자열로 기록하는 인쇄 스트림으로 리디렉션 한 다음 검사 할 수 있습니까? 가장 간단한 메커니즘 인 것 같습니다.

(어떤 단계에서 앱을 로깅 프레임 워크로 변환하는 것을 옹호하고 싶지만 이미 알고 있다고 생각합니다!)


1
그것은 내 마음에 온 것이었지만 표준 JUnit 방법이 없다고 믿을 수 없었습니다. 고마워 브레인 그러나 실제 노력에 대한 크레딧은 dfa가되었습니다.
Mike Minicki

표준 출력 스트림은 프로그램의 모든 부분에서 사용하는 공유 리소스이기 때문에이 방법에는 문제가 있습니다. 표준 출력 스트림의 직접 사용을 제거하기 위해 의존성 삽입을 사용하는 것이 좋습니다 : stackoverflow.com/a/21216342/545127
Raedwald

예. 나는 아마도 심지어 (로깅 구성 요소 또는 유사에 전화를 주장하는 것이 좋습니다) 로깅 어설 의문을 제기하는 것이 두 번째 것
브라이언 애그뉴

13

주제를 약간 벗어 났지만 일부 사람들 (나와 같이이 스레드를 처음 발견했을 때)이 SLF4J를 통해 로그 출력을 캡처하는 데 관심이있는 경우 commons-testing 의 JUnit @Rule이 도움이 될 수 있습니다.

public class FooTest {
    @Rule
    public final ExpectedLogs logs = new ExpectedLogs() {{
        captureFor(Foo.class, LogLevel.WARN);
    }};

    @Test
    public void barShouldLogWarning() {
        assertThat(logs.isEmpty(), is(true)); // Nothing captured yet.

        // Logic using the class you are capturing logs for:
        Foo foo = new Foo();
        assertThat(foo.bar(), is(not(nullValue())));

        // Assert content of the captured logs:
        assertThat(logs.isEmpty(), is(false));
        assertThat(logs.contains("Your warning message here"), is(true));
    }
}

면책 조항 :

  • 본인의 요구에 맞는 솔루션을 찾을 수 없어서이 라이브러리를 개발했습니다.
  • 만에 바인딩 log4j, log4j2그리고 logback순간에 사용할 수있는,하지만 난 더 추가 할 수 기쁘게 생각합니다.

이 라이브러리를 만들어 주셔서 대단히 감사합니다! 나는 오랫동안 이와 같은 것을 찾고있다! 때로는 쉽게 테스트 할 수있을 정도로 코드를 단순화 할 수 없지만 로그 메시지를 사용하면 놀라운 일을 할 수 있기 때문에 매우 유용합니다!
carlspring

이것은 정말 유망한 것처럼 보이지만 ATMTest 프로그램을 복사하여 Gradle에서 테스트로 실행할 때도 예외가 발생합니다 ... Github 페이지에서 문제가 발생했습니다 ...
mike rodent

9

@ dfa 답변은 훌륭하므로 출력 블록을 테스트 할 수 있도록 한 단계 더 나아갔습니다.

먼저 성가신 클래스를 받아들이는 TestHelper메소드로 만들었습니다 . captureOutput 메소드는 출력 스트림을 설정하고 해제하는 작업을 수행합니다. 의 메소드 구현 이 호출되면 테스트 블록에 대한 출력 생성에 액세스 할 수 있습니다.captureOutputCaptureTestCaptureOutputtest

TestHelper의 소스 :

public class TestHelper {

    public static void captureOutput( CaptureTest test ) throws Exception {
        ByteArrayOutputStream outContent = new ByteArrayOutputStream();
        ByteArrayOutputStream errContent = new ByteArrayOutputStream();

        System.setOut(new PrintStream(outContent));
        System.setErr(new PrintStream(errContent));

        test.test( outContent, errContent );

        System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
        System.setErr(new PrintStream(new FileOutputStream(FileDescriptor.out)));

    }
}

abstract class CaptureTest {
    public abstract void test( ByteArrayOutputStream outContent, ByteArrayOutputStream errContent ) throws Exception;
}

TestHelper와 CaptureTest는 동일한 파일에 정의되어 있습니다.

그런 다음 테스트에서 정적 captureOutput을 가져올 수 있습니다. 다음은 JUnit을 사용하는 예입니다.

// imports for junit
import static package.to.TestHelper.*;

public class SimpleTest {

    @Test
    public void testOutput() throws Exception {

        captureOutput( new CaptureTest() {
            @Override
            public void test(ByteArrayOutputStream outContent, ByteArrayOutputStream errContent) throws Exception {

                // code that writes to System.out

                assertEquals( "the expected output\n", outContent.toString() );
            }
        });
}

7

Spring Boot를 사용하고 있다면 (이전 응용 프로그램을 사용하고 있다고 언급 했으므로 아마도 그렇지는 않지만 다른 사람들에게 유용 할 수 있음) org.springframework.boot.test.rule.OutputCapture 다음과 같은 방식으로 :

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}

1
스프링 부트를 사용하기 때문에 귀하의 답변을 투표했습니다. 감사! 그러나 outputCapture를 초기화해야합니다. (public OutputCapture outputCapture = new OutputCapture ();) docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg

당신은 절대적으로 맞습니다. 의견 주셔서 감사합니다! 내 답변을 업데이트했습니다.
Disper

4

@dfa의 답변을 바탕으로System.in 테스트 방법을 보여주는 또 다른 답변을 솔루션을 공유하여 프로그램에 입력하고 출력을 테스트하고 싶습니다.

참고로 JUnit 4.12를 사용합니다.

입력을 출력으로 간단히 복제하는이 프로그램이 있다고 가정 해 봅시다.

import java.util.Scanner;

public class SimpleProgram {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(scanner.next());
        scanner.close();
    }
}

이를 테스트하기 위해 다음 클래스를 사용할 수 있습니다.

import static org.junit.Assert.*;

import java.io.*;

import org.junit.*;

public class SimpleProgramTest {
    private final InputStream systemIn = System.in;
    private final PrintStream systemOut = System.out;

    private ByteArrayInputStream testIn;
    private ByteArrayOutputStream testOut;

    @Before
    public void setUpOutput() {
        testOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOut));
    }

    private void provideInput(String data) {
        testIn = new ByteArrayInputStream(data.getBytes());
        System.setIn(testIn);
    }

    private String getOutput() {
        return testOut.toString();
    }

    @After
    public void restoreSystemInputOutput() {
        System.setIn(systemIn);
        System.setOut(systemOut);
    }

    @Test
    public void testCase1() {
        final String testString = "Hello!";
        provideInput(testString);

        SimpleProgram.main(new String[0]);

        assertEquals(testString, getOutput());
    }
}

코드를 읽을 수 있다고 생각하고 소스를 인용했기 때문에 많이 설명하지 않습니다.

JUnit이 실행 testCase1()되면 다음과 같은 순서로 헬퍼 메소드를 호출합니다.

  1. setUpOutput()@Before주석으로 인해
  2. provideInput(String data)에서 호출 testCase1()
  3. getOutput()에서 호출 testCase1()
  4. restoreSystemInputOutput()@After주석으로 인해

필자가 System.err필요로하지 않았기 때문에 테스트 하지 않았지만 testing과 비슷하게 구현하기 쉬워야한다 System.out.


1

system.out 스트림을 리디렉션하지 않으려면 ENTIRE JVM에 대해 리디렉션해야합니다. JVM에서 실행중인 다른 것이 엉망이 될 수 있습니다. 입 / 출력을 테스트하는 더 좋은 방법이 있습니다. 스텁 / 모의를 살펴보십시오.


1

테스트 System.out할 전체 JUnit 5 예제 (언제 부분 교체) :

package learning;

import static org.assertj.core.api.BDDAssertions.then;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SystemOutLT {

    private PrintStream originalSystemOut;
    private ByteArrayOutputStream systemOutContent;

    @BeforeEach
    void redirectSystemOutStream() {

        originalSystemOut = System.out;

        // given
        systemOutContent = new ByteArrayOutputStream();
        System.setOut(new PrintStream(systemOutContent));
    }

    @AfterEach
    void restoreSystemOutStream() {
        System.setOut(originalSystemOut);
    }

    @Test
    void shouldPrintToSystemOut() {

        // when
        System.out.println("example");

        then(systemOutContent.toString()).containsIgnoringCase("example");
    }
}

0

JUnit 을 사용하는 동안 system.out.println 을 사용 하거나 로거 API 를 사용 하여 직접 인쇄 할 수 없습니다 . 그러나 값을 확인하려면 간단히 사용할 수 있습니다.

Assert.assertEquals("value", str);

어설 션 오류 아래에 발생합니다.

java.lang.AssertionError: expected [21.92] but found [value]

값은 21.92 여야합니다. 이제 다음과 같이이 값을 사용하여 테스트하면 테스트 사례가 통과합니다.

 Assert.assertEquals(21.92, str);

0

밖으로

@Test
void it_prints_out() {

    PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));

    System.out.println("Hello World!");
    assertEquals("Hello World!\r\n", out.toString());

    System.setOut(save_out);
}

실수

@Test
void it_prints_err() {

    PrintStream save_err=System.err;final ByteArrayOutputStream err= new ByteArrayOutputStream();System.setErr(new PrintStream(err));

    System.err.println("Hello World!");
    assertEquals("Hello World!\r\n", err.toString());

    System.setErr(save_err);
}

이러한 종류의 설정 및 분해 로직 @Rule의 경우 테스트에서 인라인을 수행하는 대신을 사용합니다 . 특히 어설 션이 실패하면 두 번째 System.setOut/Err통화에 도달 하지 않습니다 .
dimo414
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.