Uji JUnit untuk System.out.println ()

370

Saya perlu menulis tes JUnit untuk aplikasi lama yang dirancang dengan buruk dan sedang menulis banyak pesan kesalahan ke output standar. Ketika getResponse(String request)metode berperilaku dengan benar, ia mengembalikan respons 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);
}

Tetapi ketika mendapat XML cacat atau tidak mengerti permintaan itu kembali nulldan menulis beberapa hal ke output standar.

Apakah ada cara untuk menegaskan keluaran konsol di JUnit? Untuk menangkap kasus seperti:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
Mike Minicki
sumber
Terkait dengan, tetapi bukan duplikat dari stackoverflow.com/questions/3381801/…
Raedwald

Jawaban:

581

menggunakan ByteArrayOutputStream dan System.setXXX sederhana:

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

contoh uji kasus:

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

Saya menggunakan kode ini untuk menguji opsi baris perintah (menyatakan bahwa -versi keluaran string versi, dll.)

Sunting: Versi sebelumnya dari jawaban ini dipanggil System.setOut(null)setelah tes; Ini adalah penyebab yang dirujuk oleh komentator NullPointerExceptions.

dfa
sumber
Selain itu, saya telah menggunakan JUnitMatchers untuk menguji tanggapan: assertThat (hasil, berisi Strtring ("<permintaan: GetEmployeeByKeyResponse")); Terima kasih, dfa.
Mike Minicki
3
Saya lebih suka menggunakan System.setOut (null) untuk mengembalikan aliran kembali ke apa itu ketika VM diluncurkan
tddmonkey
5
Javadocs tidak mengatakan apa-apa tentang bisa meneruskan null ke System.setOut atau System.setErr. Apakah Anda yakin ini akan bekerja pada semua JRE?
finnw
55
Saya mengalami NullPointerExceptiondalam tes lain setelah menetapkan aliran kesalahan nol seperti yang disarankan di atas (dalam java.io.writer(Object), dipanggil secara internal oleh validator XML). Saya akan menyarankan sebaliknya menyimpan yang asli di bidang: oldStdErr = System.errdan mengembalikan ini dalam @Aftermetode.
Luke Usherwood
6
Solusi bagus Hanya sebuah catatan untuk siapa saja yang menggunakannya, Anda mungkin perlu memangkas () spasi putih / baris baru dari outContent.
Allison
102

Saya tahu ini adalah utas lama, tetapi ada perpustakaan yang bagus untuk melakukan ini:

Aturan Sistem

Contoh dari dokumen:

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

Ini juga akan memungkinkan Anda untuk menjebak System.exit(-1)dan hal-hal lain yang perlu diuji oleh alat baris perintah.

Akan
sumber
1
Pendekatan ini penuh dengan masalah karena aliran output standar adalah sumber daya bersama yang digunakan oleh semua bagian dari program Anda. Lebih baik menggunakan Dependency Injection untuk menghilangkan penggunaan langsung dari arus keluaran standar: stackoverflow.com/a/21216342/545127
Raedwald
30

Alih-alih mengarahkan System.out, saya akan refactor kelas yang menggunakan System.out.println()dengan melewati a PrintStreamsebagai kolaborator dan kemudian menggunakan System.outdalam produksi dan Mata-Mata Uji dalam tes. Yaitu, gunakan Dependency Injection untuk menghilangkan penggunaan langsung dari arus keluaran standar.

Dalam produksi

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

Dalam Tes

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

Diskusi

Dengan cara ini kelas yang diuji dapat diuji dengan refactoring sederhana, tanpa perlu pengalihan tidak langsung dari output standar atau penyadapan yang tidak jelas dengan aturan sistem.

pengguna1909402
sumber
1
Saya tidak dapat menemukan ConsoleWriter ini di JDK: di mana itu?
Jean-Philippe Caruana
3
Mungkin harus disebutkan dalam jawaban, tetapi saya percaya bahwa kelas dibuat oleh user1909402.
Sebastian
6
Saya pikir ConsoleWriteradalah subjek ujian,
Niel de Wet
22

Anda dapat mengatur aliran cetak System.out melalui setOut () (dan untuk indan err). Bisakah Anda mengarahkan ini ke aliran cetak yang merekam ke string, dan kemudian memeriksanya? Itu akan tampak sebagai mekanisme paling sederhana.

(Saya akan menganjurkan, pada tahap tertentu, mengonversi aplikasi ke beberapa kerangka logging - tapi saya curiga Anda sudah mengetahui hal ini!)

Brian Agnew
sumber
1
Itu adalah sesuatu yang muncul di pikiran saya, tetapi saya tidak percaya tidak ada cara standar JUnit untuk melakukan itu. Terima kasih, Brain. Tetapi kredit harus dfa untuk upaya yang sebenarnya.
Mike Minicki
Pendekatan ini penuh dengan masalah karena aliran output standar adalah sumber daya bersama yang digunakan oleh semua bagian dari program Anda. Lebih baik menggunakan Dependency Injection untuk menghilangkan penggunaan langsung dari arus keluaran standar: stackoverflow.com/a/21216342/545127
Raedwald
Iya. Saya ingin yang kedua dan mungkin bahkan mempertanyakan penebangan yang menegaskan (lebih baik untuk menegaskan panggilan ke komponen penebangan atau serupa)
Brian Agnew
13

Sedikit keluar dari topik, tetapi jika beberapa orang (seperti saya, ketika saya pertama kali menemukan utas ini) mungkin tertarik untuk menangkap keluaran log melalui SLF4J, pengujian umum JUnit @Rulemungkin membantu:

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

Penafian :

  • Saya mengembangkan perpustakaan ini karena saya tidak dapat menemukan solusi yang cocok untuk kebutuhan saya sendiri.
  • Hanya binding untuk log4j, log4j2dan logbacktersedia saat ini, tetapi saya senang menambahkan lebih banyak.
Marc Carré
sumber
Terima kasih banyak telah membuat perpustakaan ini! Saya telah mencari sesuatu seperti ini untuk waktu yang lama! Ini sangat, sangat berguna karena kadang-kadang Anda tidak bisa menyederhanakan kode Anda agar mudah diuji, tetapi dengan pesan log Anda dapat melakukan keajaiban!
carlspring
Ini terlihat sangat menjanjikan ... tetapi bahkan ketika saya hanya menyalin program ATMTest Anda dan menjalankannya sebagai ujian di bawah Gradle, saya mendapatkan pengecualian ... Saya telah mengangkat masalah pada halaman Github Anda ...
mike rodent
9

@ dfa jawabannya bagus, jadi saya mengambil langkah lebih jauh untuk memungkinkan untuk menguji blok ouput.

Pertama saya buat TestHelperdengan metode captureOutputyang menerima kelas yang menjengkelkan CaptureTest. Metode captureOutput melakukan pekerjaan pengaturan dan menghancurkan aliran output. Ketika pelaksanaan CaptureOutput's testmetode ini disebut, memiliki akses ke output menghasilkan untuk blok tes.

Sumber untuk 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;
}

Perhatikan bahwa TestHelper dan CaptureTest didefinisikan dalam file yang sama.

Kemudian dalam pengujian Anda, Anda dapat mengimpor tangkapan statis. Berikut ini contoh menggunakan 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() );
            }
        });
}
mguymon
sumber
7

Jika Anda menggunakan Spring Boot (Anda menyebutkan bahwa Anda sedang bekerja dengan aplikasi lama, jadi Anda mungkin tidak melakukannya tetapi mungkin berguna bagi orang lain), maka Anda dapat menggunakan org.springframework.boot.test.rule.OutputCapture dengan cara berikut:

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}
Disper
sumber
1
Saya memilih ulang jawaban Anda karena saya menggunakan boot Spring dan itu membuat saya di jalur yang benar. Terima kasih! Namun, outputCapture perlu diinisialisasi. (OutputCapture publik outputCapture = OutputCapture baru ();) Lihat docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg
Anda benar sekali. Terima kasih atas komentarnya! Saya telah memperbarui jawaban saya.
Disper
4

Berdasarkan jawaban @ dfa dan jawaban lain yang menunjukkan cara menguji System.in , saya ingin membagikan solusi saya untuk memberikan input ke suatu program dan menguji hasilnya.

Sebagai referensi, saya menggunakan JUnit 4.12.

Katakanlah kita memiliki program ini yang hanya mereplikasi input ke output:

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

Untuk mengujinya, kita dapat menggunakan kelas berikut:

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

Saya tidak akan menjelaskan banyak, karena saya percaya kode tersebut dapat dibaca dan saya mengutip sumber saya.

Ketika JUnit berjalan testCase1(), ia akan memanggil metode helper sesuai urutannya:

  1. setUpOutput(), karena @Beforepenjelasannya
  2. provideInput(String data), dipanggil dari testCase1()
  3. getOutput(), dipanggil dari testCase1()
  4. restoreSystemInputOutput(), karena @Afterpenjelasannya

Saya tidak menguji System.errkarena saya tidak membutuhkannya, tetapi harus mudah diimplementasikan, mirip dengan pengujian System.out.

Antonio Vinicius Menezes Medei
sumber
1

Anda tidak ingin mengarahkan aliran system.out karena itu mengalihkan untuk SELURUH JVM. Apa pun yang berjalan di JVM bisa kacau. Ada cara yang lebih baik untuk menguji input / output. Lihatlah ke bertopik / mengolok-olok.

Sam Jacobs
sumber
1

Contoh lengkap JUnit 5 untuk menguji System.out(ganti bagian ketika):

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");
    }
}
Jens Piegsa
sumber
0

Anda tidak dapat langsung mencetak dengan menggunakan system.out.println atau menggunakan logger api saat menggunakan JUnit . Tetapi jika Anda ingin memeriksa nilai apa pun maka Anda bisa menggunakannya

Assert.assertEquals("value", str);

Ini akan melempar kesalahan pernyataan di bawah ini:

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

Nilai Anda harus 21,92, Sekarang jika Anda akan menguji menggunakan nilai ini seperti di bawah ini test case Anda akan lulus.

 Assert.assertEquals(21.92, str);
Affy
sumber
0

untuk keluar

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

untuk err

@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);
}
Shimon Doodkin
sumber
Untuk jenis setup dan logika teardown ini saya akan menggunakan @Rule, daripada melakukannya inline dalam pengujian Anda. Khususnya, jika pernyataan Anda gagal, System.setOut/Errpanggilan kedua tidak akan tercapai.
dimo414