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.