Kiểm tra JUnit cho System.out.println ()


370

Tôi cần phải viết các bài kiểm tra JUnit cho một ứng dụng cũ được thiết kế kém và đang viết rất nhiều thông báo lỗi cho đầu ra tiêu chuẩn. Khi getResponse(String request)phương thức hoạt động chính xác, nó trả về phản hồi 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);
}

Nhưng khi nó bị lỗi XML hoặc không hiểu yêu cầu, nó sẽ trả về nullvà ghi một số nội dung vào đầu ra tiêu chuẩn.

Có cách nào để khẳng định đầu ra giao diện điều khiển trong JUnit không? Để bắt các trường hợp như:

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

Liên quan đến, nhưng không phải là bản sao của stackoverflow.com/questions/3381801/ từ
Raedwald

Câu trả lời:


581

sử dụng ByteArrayOutputStream và System.setXXX rất đơn giản:

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

trường hợp thử nghiệm mẫu:

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

Tôi đã sử dụng mã này để kiểm tra tùy chọn dòng lệnh (khẳng định rằng -version xuất ra chuỗi phiên bản, v.v.)

Chỉnh sửa: Các phiên bản trước của câu trả lời này được gọi System.setOut(null)sau các bài kiểm tra; Đây là nguyên nhân của các bình luận viên NullPulumExceptions đề cập đến.


Furthemore, tôi đã sử dụng JUnitMatchers để kiểm tra các câu trả lời: assertThat (kết quả, chứaString ("<request: GetEmployeeByKeyResponse")); Cảm ơn, dfa.
Mike Minicki

3
Tôi thích sử dụng System.setOut (null) để khôi phục luồng trở lại như ban đầu khi VM được khởi chạy
tddmonkey

5
Các javadocs không nói bất cứ điều gì về việc có thể chuyển null sang System.setOut hoặc System.setErr. Bạn có chắc chắn điều này sẽ hoạt động trên tất cả các JRE?
vây

55
Tôi đã gặp một NullPointerExceptionthử nghiệm khác sau khi thiết lập luồng lỗi null như được đề xuất ở trên (trong java.io.writer(Object), được gọi là nội bộ bởi trình xác thực XML). Tôi sẽ đề nghị thay vì lưu bản gốc trong một trường: oldStdErr = System.errvà khôi phục điều này trong @Afterphương thức.
Luke Usherwood

6
Giải pháp tuyệt vời. Chỉ cần một lưu ý cho bất cứ ai sử dụng nó, bạn có thể cần phải cắt () khoảng trắng / dòng mới từ outContent.
Allison

102

Tôi biết đây là một chủ đề cũ, nhưng có một thư viện tốt để làm điều này:

Quy tắc hệ thống

Ví dụ từ các tài liệu:

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

Nó cũng sẽ cho phép bạn bẫy System.exit(-1)và những thứ khác mà một công cụ dòng lệnh sẽ cần phải được kiểm tra.


1
Cách tiếp cận này có nhiều vấn đề vì luồng đầu ra tiêu chuẩn là tài nguyên được chia sẻ được sử dụng bởi tất cả các phần trong chương trình của bạn. Tốt hơn là sử dụng Dependency Injection để loại bỏ việc sử dụng trực tiếp luồng đầu ra tiêu chuẩn: stackoverflow.com/a/21216342/545127
Raedwald

30

Thay vì chuyển hướng System.out, tôi sẽ cấu trúc lại lớp sử dụng System.out.println()bằng cách chuyển qua PrintStreamlàm cộng tác viên và sau đó sử dụng System.outtrong sản xuất và Test Spy trong thử nghiệm. Đó là, sử dụng Dependency Injection để loại bỏ việc sử dụng trực tiếp luồng đầu ra tiêu chuẩn.

Trong sản xuất

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

Trong bài kiểm tra

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

Thảo luận

Bằng cách này, lớp được kiểm tra trở nên có thể kiểm tra được bằng cách tái cấu trúc đơn giản, mà không cần phải chuyển hướng gián tiếp đầu ra tiêu chuẩn hoặc đánh chặn tối nghĩa với quy tắc hệ thống.


1
Tôi không thể tìm thấy ConsoleWriter này ở bất cứ đâu trong JDK: nó ở đâu?
Jean-Philippe Caruana

3
Có lẽ nó nên được đề cập trong câu trả lời, nhưng tôi tin rằng lớp được tạo bởi user1909402.
Sebastian

6
Tôi nghĩ ConsoleWriterlà đối tượng thử nghiệm,
Niel de Wet

22

Bạn có thể đặt luồng in System.out thông qua setOut () (và cho inerr). Bạn có thể chuyển hướng này đến một luồng in ghi vào một chuỗi và sau đó kiểm tra nó không? Đó sẽ là cơ chế đơn giản nhất.

(Tôi sẽ ủng hộ, ở một số giai đoạn, chuyển đổi ứng dụng sang một số khung đăng nhập - nhưng tôi nghi ngờ bạn đã nhận thức được điều này!)


1
Đó là điều gì đó xuất hiện trong đầu tôi nhưng tôi không thể tin rằng không có cách JUnit tiêu chuẩn để làm điều đó. Cảm ơn, Não. Nhưng các khoản tín dụng đã đến dfa cho những nỗ lực thực tế.
Mike Minicki

Cách tiếp cận này có nhiều vấn đề vì luồng đầu ra tiêu chuẩn là tài nguyên được chia sẻ được sử dụng bởi tất cả các phần trong chương trình của bạn. Tốt hơn là sử dụng Dependency Injection để loại bỏ việc sử dụng trực tiếp luồng đầu ra tiêu chuẩn: stackoverflow.com/a/21216342/545127
Raedwald

Đúng. Tôi sẽ thứ hai và thậm chí có thể đặt câu hỏi về việc xác nhận đăng nhập (tốt hơn là xác nhận cuộc gọi đến một thành phần đăng nhập hoặc tương tự)
Brian Agnew

13

Hơi lạc đề, nhưng trong trường hợp một số người (như tôi, khi tôi lần đầu tiên tìm thấy chủ đề này) có thể quan tâm đến việc nắm bắt đầu ra nhật ký thông qua SLF4J, JUnit của commons- tests @Rulecó thể giúp:

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

Tuyên bố từ chối trách nhiệm :

  • Tôi đã phát triển thư viện này vì tôi không thể tìm thấy bất kỳ giải pháp phù hợp cho nhu cầu của riêng mình.
  • Chỉ ràng buộc cho log4j, log4j2logbackcó sẵn tại thời điểm này, nhưng tôi rất vui để thêm nhiều hơn nữa.

Cảm ơn bạn rất nhiều vì đã tạo ra thư viện này! Tôi đã tìm kiếm một cái gì đó như thế này trong một thời gian dài! Nó rất, rất hữu ích vì đôi khi bạn chỉ đơn giản là không thể đơn giản hóa mã của mình đủ để có thể dễ dàng kiểm tra, nhưng với một thông điệp tường trình bạn có thể làm nên điều kỳ diệu!
carlspring 18/03/2015

Điều này có vẻ rất hứa hẹn ... nhưng ngay cả khi tôi chỉ sao chép chương trình ATMTest của bạn và chạy nó dưới dạng thử nghiệm dưới Gradle, tôi vẫn gặp một ngoại lệ ... Tôi đã nêu ra một vấn đề trên trang Github của bạn ...
loài gặm nhấm

9

Câu trả lời @dfa là tuyệt vời, vì vậy tôi đã thực hiện một bước xa hơn để có thể kiểm tra các khối ouput.

Đầu tiên tôi tạo TestHelperbằng một phương thức captureOutputchấp nhận lớp khó chịu CaptureTest. Phương thức captOutput thực hiện công việc thiết lập và xé các luồng đầu ra. Khi thực hiện CaptureOutput's testphương pháp được gọi, nó có quyền truy cập vào sản lượng tạo ra cho các khối thi.

Nguồn cho 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;
}

Lưu ý rằng TestHelper và CaptureTest được xác định trong cùng một tệp.

Sau đó, trong thử nghiệm của bạn, bạn có thể nhập hàm captOutput tĩnh. Dưới đây là một ví dụ sử dụng 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

Nếu bạn đang sử dụng Spring Boot (bạn đã đề cập rằng bạn đang làm việc với một ứng dụng cũ, vì vậy bạn có thể không sử dụng nhưng nó có thể được sử dụng cho những người khác), thì bạn có thể sử dụng org.springframework.boot.test.rule.OutputCapture theo cách sau đây:

@Rule
public OutputCapture outputCapture = new OutputCapture();

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

1
Tôi đã bình chọn câu trả lời của bạn vì tôi sử dụng Spring boot và nó đưa tôi đi đúng hướng. Cảm ơn! Tuy nhiên, outputCapture cần được khởi tạo. (công khai OutputCapture outputCapture = new OutputCapture ();) Xem docs.spring.io/spring-boot/docs/civerse/reference/html/iêu
EricGreg

Bạn hoàn toàn chính xác. Cảm ơn các bình luận! Tôi đã cập nhật câu trả lời của mình.
Giải tán

4

Dựa trên câu trả lời của @ dfamột câu trả lời khác cho thấy cách kiểm tra System.in , tôi muốn chia sẻ giải pháp của mình để cung cấp đầu vào cho chương trình và kiểm tra đầu ra của nó.

Để tham khảo, tôi sử dụng JUnit 4.12.

Giả sử chúng ta có chương trình này chỉ đơn giản là sao chép đầu vào thành đầu ra:

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

Để kiểm tra nó, chúng ta có thể sử dụng lớp sau:

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

Tôi sẽ không giải thích nhiều, vì tôi tin rằng mã có thể đọc được và tôi đã trích dẫn nguồn của mình.

Khi JUnit chạy testCase1(), nó sẽ gọi các phương thức của trình trợ giúp theo thứ tự chúng xuất hiện:

  1. setUpOutput(), vì @Beforechú thích
  2. provideInput(String data), gọi từ testCase1()
  3. getOutput(), gọi từ testCase1()
  4. restoreSystemInputOutput(), vì @Afterchú thích

Tôi đã không kiểm tra System.errvì tôi không cần nó, nhưng nó sẽ dễ thực hiện, tương tự như thử nghiệm System.out.


1

Bạn không muốn chuyển hướng luồng system.out vì điều đó chuyển hướng cho JVM ENTIRE. Bất cứ điều gì khác chạy trên JVM đều có thể bị rối tung. Có nhiều cách tốt hơn để kiểm tra đầu vào / đầu ra. Nhìn vào cuống / giả.


1

Ví dụ đầy đủ JUnit 5 để kiểm tra System.out(thay thế phần khi):

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

Bạn không thể in trực tiếp bằng cách sử dụng system.out.println hoặc sử dụng api logger trong khi sử dụng JUnit . Nhưng nếu bạn muốn kiểm tra bất kỳ giá trị nào thì bạn chỉ cần sử dụng

Assert.assertEquals("value", str);

Nó sẽ ném bên dưới lỗi xác nhận:

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

Giá trị của bạn phải là 21,92, Bây giờ nếu bạn sẽ kiểm tra bằng cách sử dụng giá trị này như bên dưới trường hợp kiểm tra của bạn sẽ vượt qua.

 Assert.assertEquals(21.92, str);

0

từ ngoài

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

cho lỗi

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

Đối với loại thiết lập và logic phân tích này, tôi sẽ sử dụng một @Rule, thay vì thực hiện nội tuyến trong thử nghiệm của bạn. Đáng chú ý, nếu xác nhận của bạn thất bại, System.setOut/Errcuộc gọi thứ hai sẽ không đạt được.
dimo414
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.