web-dev-qa-db-ja.com

System.out.println()のJUnitテスト

設計が良くなく、標準出力にたくさんのエラーメッセージを書いている古いアプリケーション用の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());
323
Mike Minicki

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の原因です。

516
dfa

これは古いスレッドですが、これを実行するためのNiceライブラリがあります。

システムルール

ドキュメントからの例:

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)やコマンドラインツールがテストされる必要があるであろう他のものをトラップすることを可能にするでしょう。

95
Will

System.outをリ​​ダイレクトする代わりに、共同作業者としてPrintStreamを渡し、テストでSystem.outを使用し、テストでTest Spyを使用して、System.out.println()を使用するクラスをリファクタリングします。つまり、標準出力ストリームを直接使用しないようにするには、Dependency Injectionを使用します。

製造中

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

ディスカッション

このように、テスト中のクラスは、標準出力の間接的なリダイレクトやシステムルールによる不明瞭な傍受を必要とせずに、単純なリファクタリングによってテスト可能になります。

22
user1909402

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

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

22
Brian Agnew

少し話題を外れていますが、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のバインディングのみが使用可能ですが、もっと追加できれば幸いです。
12
Marc Carré

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

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

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() );
            }
        });
}
9
mguymon

もしあなたが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");
}
6
Disper

@ dfa's answer および 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. @Beforeアノテーションが原因でsetUpOutput()
  2. provideInput(String data)から呼び出されるtestCase1()
  3. getOutput()から呼び出されるtestCase1()
  4. @Afterアノテーションが原因でrestoreSystemInputOutput()

System.errは必要ないのでテストしませんでしたが、System.outのテストと同様に、実装は簡単なはずです。

System.outをテストするためのJUnit 5の完全な例(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");
    }
}
1
Jens Piegsa

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

1
Sam Jacobs

JUnitを使用中にsystem.out.printlnを使用したり、logger apiを使用して直接印刷することはできません。しかし、もしあなたが何か値をチェックしたいのなら、あなたは単に使うことができます

Assert.assertEquals("value", str);

それは以下のアサーションエラーを投げます:

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

あなたの値は21.92であるべきです、今あなたがあなたのテストケースの下のようにこの値を使ってテストするならば合格します。

 Assert.assertEquals(21.92, str);
0
Virtual

外へ

@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);
}
0
Shimon Doodkin