You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ByteArrayOutputStream的JUnit批量测试失败但单测通过的问题排查

Test set: org.oscargs.MenuTest

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.452 s <<< FAILURE! -- in org.oscargs.MenuTest
org.oscargs.MenuTest.badCommandLenghtOneTest -- Time elapsed: 0.008 s <<< FAILURE!
org.opentest4j.AssertionFailedError:
expected: <Input command: Bad parameters! Only available parameters are 'start ' and 'exit'

but was: <>
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1141)
at org.oscargs.MenuTest.badCommandLenghtOneTest(MenuTest.java:64)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

## 使用的依赖
```xml
<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>

解决方案

核心原因

  1. Log4j输出未被正确重置:被测类使用Log4j的logger.info()输出内容,但测试仅替换了System.out,Log4j的全局配置在批量测试时会被复用,导致后续测试的日志未输出到当前测试创建的ByteArrayOutputStream。
  2. System.out恢复错误:@AfterEach中执行System.setOut(null)会破坏标准输出流的状态,导致后续测试无法正常输出。

具体修复步骤

1. 保存并恢复原始System.out

在测试类中新增变量保存原始标准输出流,在@AfterEach中恢复,而非设置为null:

package org.oscargs;
import org.junit.jupiter.api.*;

import java.io.*;

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

class MenuTest {
    private Menu menu;
    private OutputStream outputStream;
    private PrintStream originalOut; // 新增:保存原始输出流

    @BeforeEach
    void setUp() {
        try {
            originalOut = System.out; // 保存原始输出
            this.outputStream = new ByteArrayOutputStream();
            System.setOut(new PrintStream(outputStream));
            this.menu = new Menu();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @AfterEach
    void tearDown() {
        try {
            System.setOut(originalOut); // 恢复原始输出流
            this.outputStream.close();
            this.menu.teardown();
            this.menu = null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 测试方法保持不变
    @Test
    void badCommandLenghtOneTest() {
        // ... 原代码
    }

    @Test
    void exitCommandTest() {
        // ... 原代码
    }
}

2. 动态配置Log4j,让日志输出到当前测试的OutputStream

因为被测类依赖Log4j,需要在每个测试前重新配置Log4j,使其输出到当前测试的ByteArrayOutputStream:

第一步:添加Log4j核心依赖(如果未添加)

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
    <scope>test</scope>
</dependency>

第二步:在@BeforeEach中配置Log4j

import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.layout.PatternLayout;

// ... 其他代码

@BeforeEach
void setUp() {
    try {
        originalOut = System.out;
        this.outputStream = new ByteArrayOutputStream();
        PrintStream testOut = new PrintStream(outputStream);
        System.setOut(testOut);

        // 配置Log4j输出到当前测试的PrintStream
        LoggerContext context = LoggerContext.getContext(false);
        Configuration config = context.getConfiguration();

        // 创建仅输出消息内容的布局(避免多余日志格式干扰测试)
        PatternLayout layout = PatternLayout.newBuilder()
                .withPattern("%msg") // 只输出日志消息,不添加额外换行或格式
                .build();

        // 创建输出到System.out的Appender(此时System.out已指向测试的outputStream)
        ConsoleAppender appender = ConsoleAppender.newBuilder()
                .setName("TestConsoleAppender")
                .setTarget(ConsoleAppender.Target.SYSTEM_OUT)
                .setLayout(layout)
                .build();
        appender.start();
        config.addAppender(appender);

        // 将Appender绑定到根Logger
        config.getRootLogger().addAppender(appender, null, null);

        // 更新Log4j配置
        context.updateLoggers(config);

        this.menu = new Menu();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3. 调整预期输出匹配Log4j格式

因为Log4j的logger.info()不会自动添加换行(除非布局配置了),所以需要调整测试中的预期输出,确保和实际输出完全一致:
比如在badCommandLenghtOneTest中,预期输出应为:

String expectedOutput = "Input command: " + 
                        "Bad parameters! Only available parameters are 'start <player1> <player2>' and 'exit'\n";

额外优化

  • 移除测试类中未使用的@ExtendWith(MockitoExtension.class)注解,减少不必要的依赖。
  • 去掉teardown()中的outputStream.flush(),ByteArrayOutputStream在关闭时会自动刷新内容。

内容的提问来源于stack exchange,提问作者Oscar Garcia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 16:35:00