Перенаправить консольный вывод в строку в 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();
}
}