Перенаправить консольный вывод в строку в Java

У меня есть одна функция, тип возвращаемого значения VOID, и она печатается прямо на консоли.

Однако мне нужен этот вывод в строке, чтобы я мог работать над ним.

Поскольку я не могу вносить какие-либо изменения с функцией с типом возвращаемого значения VOID, поэтому я должен перенаправить этот вывод в строку.

Как я могу перенаправить его в JAVA?

Есть много вопросов относительно перенаправления stdout в строку, но они перенаправляют только ввод, полученный от пользователя, а не вывод какой-либо функции...

5 ответов

Решение

Если функция печатает в System.outВы можете захватить этот вывод, используя System.setOut способ изменить System.out пойти в PrintStream предоставлено вами. Если вы создаете PrintStream подключен к ByteArrayOutputStream, то вы можете захватить вывод как String,

Пример:

    // Create a stream to hold the output
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(baos);
    // IMPORTANT: Save the old System.out!
    PrintStream old = System.out;
    // Tell Java to use your special stream
    System.setOut(ps);
    // Print some output: goes to your special stream
    System.out.println("Foofoofoo!");
    // Put things back
    System.out.flush();
    System.setOut(old);
    // Show what happened
    System.out.println("Here: " + baos.toString());

Эта программа печатает только одну строку:

    Here: Foofoofoo!

Вот служебный класс с именем ConsoleOutputCapturer. Это позволяет выводу перейти на существующую консоль, однако за сценой продолжает захватывать выводимый текст. Вы можете контролировать, что захватывать с помощью методов запуска / остановки. Другими словами, вызовите start, чтобы начать захват вывода консоли, и как только вы закончите захват, вы можете вызвать метод stop, который возвращает значение String, содержащее вывод консоли, для временного окна между вызовами start-stop. Этот класс не является потокобезопасным, хотя.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;

public class ConsoleOutputCapturer {
    private ByteArrayOutputStream baos;
    private PrintStream previous;
    private boolean capturing;

    public void start() {
        if (capturing) {
            return;
        }

        capturing = true;
        previous = System.out;      
        baos = new ByteArrayOutputStream();

        OutputStream outputStreamCombiner = 
                new OutputStreamCombiner(Arrays.asList(previous, baos)); 
        PrintStream custom = new PrintStream(outputStreamCombiner);

        System.setOut(custom);
    }

    public String stop() {
        if (!capturing) {
            return "";
        }

        System.setOut(previous);

        String capturedValue = baos.toString();             

        baos = null;
        previous = null;
        capturing = false;

        return capturedValue;
    }

    private static class OutputStreamCombiner extends OutputStream {
        private List<OutputStream> outputStreams;

        public OutputStreamCombiner(List<OutputStream> outputStreams) {
            this.outputStreams = outputStreams;
        }

        public void write(int b) throws IOException {
            for (OutputStream os : outputStreams) {
                os.write(b);
            }
        }

        public void flush() throws IOException {
            for (OutputStream os : outputStreams) {
                os.flush();
            }
        }

        public void close() throws IOException {
            for (OutputStream os : outputStreams) {
                os.close();
            }
        }
    }
}

Если вы используете Spring Framework, есть действительно простой способ сделать это с помощью OutputCaptureExtension :

       @ExtendWith(OutputCaptureExtension.class)
 class MyTest {

     @Test
     void test(CapturedOutput output) {
         System.out.println("ok");
         assertThat(output).contains("ok");
         System.err.println("error");
     }

     @AfterEach
     void after(CapturedOutput output) {
         assertThat(output.getOut()).contains("ok");
         assertThat(output.getErr()).contains("error");
     }

 }

Вот мое полное решение, основанное на том же API, что и user659804 , упомянутый @Ernest Friedman-Hill .

Все можно сделать одной строкой:

      String capturedOut = ISystemOutAcquire.acquire(MyClass::myStaticFunOrRunnable);
  • Решение ниже позволяет передавать любыеRunnableи приобрести в течениеRunnable.run()вызов метода. Конечно, это не гарантирует , что другой поток не будет одновременно записывать дополнительные данные.
  • ОригиналSystem.outвосстанавливается, когдаISystemOutAcquire.close()называется - RAII-подобное поведение.

Модульный тест:

      package pl.mjaron.etudes;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class ISystemOutAcquireTest {

    static void printingCode() {
        System.out.print("Sample output.");
    }

    @Test
    void acquireCall() {
        final String contentFromCall = ISystemOutAcquire.acquire(ISystemOutAcquireTest::printingCode);
        assertEquals("Sample output.", contentFromCall);
    }

    @Test
    void acquireScope() {

        final String contentFromScope;
        try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
            printingCode();
            contentFromScope = acquire.toString();
        }
        assertEquals("Sample output.", contentFromScope);
    }
}

Реализация:

      package pl.mjaron.etudes;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public interface ISystemOutAcquire extends AutoCloseable {

    PrintStream getOriginalOut();

    PrintStream getNewOut();

    /**
     * Restores the original stream.
     */
    @Override
    void close();

    /**
     * Replaces the original {@link PrintStream} with another one.
     */
    class ToPrintStream implements ISystemOutAcquire {
        private final PrintStream originalOut;
        private final PrintStream newOut;

        public ToPrintStream(PrintStream newOut) {
            this.originalOut = System.out;
            this.newOut = newOut;
            originalOut.flush();
            newOut.flush();
            System.setOut(newOut);
        }

        @Override
        public void close() {
            originalOut.flush();
            newOut.flush();
            System.setOut(originalOut);
        }

        @Override
        public PrintStream getOriginalOut() {
            return originalOut;
        }

        @Override
        public PrintStream getNewOut() {
            return newOut;
        }
    }

    public static ToPrintStream acquire(final PrintStream newOut) {
        return new ToPrintStream(newOut);
    }

    /**
     * Captures the {@link System#out} to {@link ByteArrayOutputStream}.
     * <p>
     * Overrides the {@link #toString()} method, so all captured text is accessible.
     */
    class ToByteArray extends ToPrintStream {

        private final ByteArrayOutputStream byteArrayOutputStream;

        public ToByteArray(final ByteArrayOutputStream byteArrayOutputStream) {
            super(new PrintStream(byteArrayOutputStream));
            this.byteArrayOutputStream = byteArrayOutputStream;
        }

        @Override
        public String toString() {
            return byteArrayOutputStream.toString();
        }

        public ByteArrayOutputStream getByteArrayOutputStream() {
            return byteArrayOutputStream;
        }
    }

    static ToByteArray acquire(final ByteArrayOutputStream byteArrayOutputStream) {
        return new ToByteArray(byteArrayOutputStream);
    }

    static ToByteArray acquire() {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        return acquire(byteArrayOutputStream);
    }

    static String acquire(final Runnable runnable) {
        try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
            runnable.run();
            return acquire.toString();
        }
    }
}

Хотя этот вопрос очень старый и на него уже есть очень хорошие ответы, я хочу предложить альтернативу. Я создал библиотеку специально для этого варианта использования. Он называется Console Captor, и вы можете добавить его с помощью следующего фрагмента:

      <dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>consolecaptor</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>

Пример класса

      public class FooService {

    public void sayHello() {
        System.out.println("Keyboard not responding. Press any key to continue...");
        System.err.println("Congratulations, you are pregnant!");
    }

}

Модульный тест

      import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.console.ConsoleCaptor;
import org.junit.jupiter.api.Test;

public class FooServiceTest {

    @Test
    public void captureStandardAndErrorOutput() {
        ConsoleCaptor consoleCaptor = new ConsoleCaptor();

        FooService fooService = new FooService();
        fooService.sayHello();

        assertThat(consoleCaptor.getStandardOutput()).contains("Keyboard not responding. Press any key to continue...");
        assertThat(consoleCaptor.getErrorOutput()).contains("Congratulations, you are pregnant!");
        
        consoleCaptor.close();
    }
}
Другие вопросы по тегам