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の形式が正しくない場合や、要求を理解できない場合は、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")); ありがとう、dfa。
Mike Minicki 2009

3
私はSystem.setOut(null)を使用してストリームをVMが起動されたときの状態に戻すことを好む
tddmonkey

5
javadocsは、System.setOutまたはSystem.setErrにnullを渡すことができることについて何も述べていません。これはすべてのJREで機能しますか?
finnw 2009

55
NullPointerException上記で提案されているように(java.io.writer(Object)XMLバリデーターによって内部的に呼び出される)nullエラーストリームを設定した後、他のテストでが発生しました。代わりに、オリジナルをフィールドに保存oldStdErr = System.errし、@Afterメソッドでこれを復元することをお勧めします。
ルークアッシャーウッド

6
素晴らしいソリューション。それを使用している人への注意事項として、outContentから空白/改行をtrim()する必要があるかもしれません。
アリソン

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
レドワルド

30

リダイレクトする代わりにSystem.out、をコラボレーターとしてSystem.out.println()渡し、本番環境PrintStream使用System.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
このConsoleWriterはJDKのどこにも見つかりませんでした。どこにありますか?
Jean-Philippe Caruana、2015

3
おそらく回答で言及する必要がありますが、クラスはuser1909402によって作成されたと思います。
セバスチャン

6
ConsoleWriterはテストの対象だと思います
Niel de Wet

22

System.out印刷ストリームは、setOut()(およびfor inおよびerr)を介して設定できます。これを文字列に記録する印刷ストリームにリダイレクトし、それを検査できますか?これが最も単純なメカニズムのように見えます。

(私は、ある段階で、アプリをロギングフレームワークに変換することを提唱しますが、あなたはすでにこれを知っていると思います!)


1
それは私の頭に浮かんだことですが、それを行う標準のJUnitの方法がないとは信じられませんでした。ありがとう、脳。しかし、クレジットは実際の努力のためにdfaになりました。
マイクミニッキ2009

標準の出力ストリームはプログラムのすべての部分で使用される共有リソースであるため、このアプローチには問題があります。標準出力ストリームの直接の使用を排除するために依存性の注入を使用することをお勧めし: stackoverflow.com/a/21216342/545127
レドワルド

はい。私はそれを2番目にし、おそらくロギングアサート(ロギングコンポーネントまたは同様のものへの呼び出しをアサートする方が良い)に疑問を投げかけることもあります
Brian Agnew

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));
    }
}

免責事項

  • 自分のニーズに適したソリューションが見つからなかったため、このライブラリを開発しました。
  • 唯一のバインディングのためのlog4jlog4j2そしてlogback現時点で利用できますが、私はより多くを追加することが幸せです。

このライブラリを作成していただき、ありがとうございます。こんなものをずっとずっと探していました!簡単にテストできるようにコードを単純化できないこともあるので、非常に便利ですが、ログメッセージを使用すると不思議に思うことがあります。
carlspring 2015年

これは非常に有望に見えます...しかし、ATMTestプログラムをコピーしてGradleでテストとして実行しただけでも、例外が発生します... Githubページで問題が発生しました...
マイクげっ歯類

9

@dfaの回答は素晴らしいので、出力のブロックをテストできるようにするために、さらに一歩進んだ。

最初に、不快なクラスを受け入れるTestHelperメソッドcaptureOutputで作成しましたCaptureTest。captureOutputメソッドは、出力ストリームの設定と破棄の作業を行います。CaptureOutputtestメソッドの実装が呼び出されると、テストブロック用に生成された出力にアクセスできます。

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
私はSpring bootを使用していて、正しい方向に進んだので、あなたの回答に賛成票を投じました。ありがとう!ただし、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は必要なかったのでテストしませんでしたが、テストと同様に簡単に実装できますSystem.out


1

ENTIRE JVMにリダイレクトするため、system.outストリームをリダイレクトする必要はありません。JVM上で実行されている他のものはすべて混乱する可能性があります。入力/出力をテストするより良い方法があります。スタブ/モックを調べます。


1

テストする完全なJUnit 5の例System.out(when部分を置き換える):

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では、テストでインラインではなく、を使用します。特に、アサーションが失敗した場合、2番目のSystem.setOut/Err呼び出しに到達しません。
dimo414 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.