System.out.println () için JUnit testi


370

Kötü tasarlanmış ve standart çıktıya bir sürü hata iletisi yazıyor eski bir uygulama için JUnit testleri yazmak gerekir. Ne zaman getResponse(String request)yöntemi doğru davranır bir XML yanıt verir:

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

Ancak hatalı biçimlendirilmiş XML aldığında veya isteği anlamadığında, nullstandart çıktıya bazı şeyler döndürür ve yazar.

JUnit'te konsol çıktısını onaylamanın bir yolu var mı? Gibi davaları yakalamak için:

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

Yanıtlar:


581

kullanarak ByteArrayOutputStream ve System.setXXX basittir:

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

örnek test kılıfları:

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

Komut satırı seçeneğini test etmek için bu kodu kullandım (-version'un sürüm dizesi vb.

Düzenleme: Bu cevabın önceki sürümleri System.setOut(null)testlerden sonra çağrılır ; NullPointerExceptions yorumcularının başvurabilmelerinin nedeni budur.


Dahası, yanıtları test etmek için JUnitMatchers kullandım: assertThat (sonuç, includeString ("<request: GetEmployeeByKeyResponse")); Teşekkürler, dfa.
Mike Minicki

3
Akışı VM başlatıldığı zamana geri yüklemek için System.setOut (null) kullanmayı tercih ediyorum
tddmonkey

5
Javadocs null System.setOut veya System.setErr geçirme hakkında bir şey söylemiyor. Bunun tüm JRE'lerde çalışacağından emin misiniz?
finnw

55
NullPointerExceptionYukarıda önerilen bir null hata akışı ayarladıktan sonra diğer testlerde karşılaştım (içinde java.io.writer(Object), bir XML doğrulayıcı tarafından dahili olarak adlandırılır). Bunun yerine orijinali bir alana kaydetmenizi oldStdErr = System.errve @Afteryöntemde geri yüklemenizi öneririm .
Luke Usherwood

6
Harika bir çözüm. Bunu kullanan herkes için sadece bir not, outContent'ten boşluk / satırsonu kırpmanız () gerekebilir.
Allison

102

Bu eski bir iş parçacığı olduğunu biliyorum, ama bunu yapmak için güzel bir kütüphane var:

Sistem Kuralları

Dokümanlardan örnek:

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

Ayrıca, System.exit(-1)bir komut satırı aracının test edilmesi gereken tuzak ve diğer şeyleri de yapmanızı sağlar.


1
Standart çıktı akışı, programınızın tüm bölümleri tarafından kullanılan paylaşılan bir kaynak olduğundan, bu yaklaşım sorunlarla doludur. Standart çıkış akışının doğrudan kullanımını ortadan kaldırmak için Bağımlılık Enjeksiyonu kullanmak daha iyidir: stackoverflow.com/a/21216342/545127
Raedwald

30

Yönlendirme yerine, bir ortak çalışan olarak geçip daha sonra üretimde ve testte bir Test Casusu kullanarak kullanılan System.outsınıfı yeniden düzenlerdim . Yani, standart çıkış akımının doğrudan kullanımını ortadan kaldırmak için Bağımlılık Enjeksiyonu kullanın.System.out.println()PrintStreamSystem.out

Üretimde

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

Testte

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

Tartışma

Bu şekilde test edilen sınıf, standart çıktının dolaylı olarak yeniden yönlendirilmesine veya bir sistem kuralıyla belirsiz müdahaleye gerek kalmadan basit bir yeniden düzenleme ile test edilebilir hale gelir.


1
Bu ConsoleWriter JDK hiçbir yerde bulamadım: nerede?
Jean-Philippe Caruana

3
Muhtemelen cevapta belirtilmelidir, ancak sınıfın user1909402 tarafından oluşturulduğuna inanıyorum.
Sebastian

6
Sanırım ConsoleWritertest konusu,
Niel de Wet

22

System.out yazdırma akışını setOut () (ve için inve err) aracılığıyla ayarlayabilirsiniz . Bunu bir dizeye kayıt yapan bir yazdırma akışına yönlendirebilir ve ardından inceleyebilir misiniz? Bu en basit mekanizma gibi görünüyor.

(Bir aşamada, uygulamayı bir günlük çerçevesine dönüştürürüm - ancak bunun farkında olduğunuzu sanıyorum!)


1
Bu aklıma gelen bir şeydi ama bunu yapmanın standart bir JUnit yolu olmadığına inanamadım. Teşekkürler, Brain. Ancak krediler fiili çaba için dfa'ya ulaştı.
Mike Minicki

Standart çıktı akışı, programınızın tüm bölümleri tarafından kullanılan paylaşılan bir kaynak olduğundan, bu yaklaşım sorunlarla doludur. Standart çıkış akışının doğrudan kullanımını ortadan kaldırmak için Bağımlılık Enjeksiyonu kullanmak daha iyidir: stackoverflow.com/a/21216342/545127
Raedwald

Evet. Ben ikinci ve hatta belki bir günlükleme iddia (bir günlük bileşenine veya benzeri bir çağrı iddia daha iyi) sorguya
Brian Agnew

13

(Ben ilk Konuyu bulunca benim gibi) Hafif konu dışı, ama durumda bazı insanlar SLF4J aracılığıyla günlük çıkışını yakalayan ilgilenen olabilir, commons-test 'ın JUnit @Rulekudreti yardım:

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

Feragatname :

  • Kendi ihtiyacım için uygun bir çözüm bulamadığım için bu kütüphaneyi geliştirdim.
  • Yalnızca bağlamaları log4j, log4j2ve logbackşu anda mevcuttur, ancak ben daha eklemek için mutluyum.

Bu kütüphaneyi oluşturduğunuz için çok teşekkür ederim! Uzun zamandır böyle bir şey arıyordum! Çok, çok yararlıdır, çünkü bazen kodunuzu kolayca test edilebilecek kadar basitleştiremezsiniz, ancak bir günlük mesajı ile harikalar yapabilirsiniz!
carlspring

Bu gerçekten umut verici görünüyor ... ama ATMTest programınızı kopyalayıp Gradle altında bir test olarak çalıştırdığımda bile, bir istisna alıyorum ... Github sayfanızda bir sorunla karşılaştım ...
mike rodent

9

@ dfa cevabı harika, bu yüzden çıkış bloklarını test etmeyi mümkün kılmak için bir adım daha attım.

İlk olarak , rahatsız edici sınıfı kabul eden TestHelperbir yöntemle captureOutputyarattım CaptureTest. CaptureOutput yöntemi, çıktı akışlarını ayarlama ve yırtma işini yapar. Uygulanması zaman CaptureOutputbireyin testyöntemi olarak adlandırılır, bu çıkış erişim testi bloğu oluşturmak yer alır.

TestHelper için kaynak:

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 ve CaptureTest öğelerinin aynı dosyada tanımlandığını unutmayın.

Ardından, testinizde statik captureOutput dosyasını içe aktarabilirsiniz. İşte JUnit kullanan bir örnek:

// 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 kullanıyorsanız (eski bir uygulama ile çalıştığınızdan bahsetmiştiniz, muhtemelen kullanmıyorsunuz, ancak başkaları için yararlı olabilir), o zaman org.springframework.boot.test.rule.OutputCapture öğesini kullanabilirsiniz. aşağıdaki şekilde:

@Rule
public OutputCapture outputCapture = new OutputCapture();

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

1
Cevabınızı oyladım çünkü Spring boot kullanıyorum ve beni doğru yola soktu. Teşekkürler! Ancak, outputCapture'ın başlatılması gerekir. (public OutputCapture outputCapture = yeni OutputCapture ();) Bkz. docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg

Kesinlikle haklısın. Yorum için teşekkürler! Cevabımı güncelledim.
Disper

4

Dayanarak DFA cevap @ ve gösteriler nasıl System.in test etmek başka cevap , bir programa bir giriş vermek ve çıkışını test etmek için benim çözüm paylaşmak istiyorum.

Referans olarak, JUnit 4.12 kullanıyorum.

Diyelim ki girdiyi çıktıya kopyalayan bu programımız var:

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

Test etmek için aşağıdaki sınıfı kullanabiliriz:

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

Çok fazla açıklamayacağım, çünkü kodun okunabilir olduğuna ve kaynaklarıma atıfta bulunduğuna inanıyorum.

JUnit çalıştığında testCase1(), yardımcı yöntemleri göründükleri sırayla çağıracaktır:

  1. setUpOutput(), @Beforeek açıklama nedeniyle
  2. provideInput(String data), denilen testCase1()
  3. getOutput(), denilen testCase1()
  4. restoreSystemInputOutput(), @Afterek açıklama nedeniyle

Test etmedim System.errçünkü ihtiyacım yoktu, ama teste benzer şekilde uygulanması kolay olmalı System.out.


1

ENTIRE JVM için yeniden yönlendirmeler yaptığı için system.out akışını yeniden yönlendirmek istemezsiniz. JVM üzerinde çalışan her şey berbat olabilir. Girdi / çıktıyı test etmenin daha iyi yolları vardır. Saplamalara / alaylara bakın.


1

Test etmek için tam JUnit 5 örneği System.out(when parçasını değiştirin):

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

Doğrudan kullanarak baskı olamaz System.out.println veya kullanarak logger api kullanırken JUnit . Ancak, herhangi bir değeri kontrol etmek istiyorsanız,

Assert.assertEquals("value", str);

İddia hatasının altına atılacaktır:

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

Değeriniz 21.92 olmalıdır. Şimdi bu değeri kullanarak test edecek olursanız, test durumunuz geçecektir.

 Assert.assertEquals(21.92, str);

0

dışarıya

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

hata için

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

Bu tür kurulum ve yırtma mantığı @Ruleiçin testinizde satır içi yapmak yerine, bir kullanırım . Özellikle, sizin onaylama işlemi başarısız olursa ikinci System.setOut/Errçağrı olacak değil ulaşılabilir.
dimo414
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.