子线程断言失败,JUnit 为什么还显示通过?

子线程执行了断言,预期 1,实际 0。终端已经打印了这两个数字,JUnit 却给出 1 个测试通过。后来我保持这个失败输入,只让测试线程读取 Future,结果变成了 1 个测试失败。

先把失败传回测试线程

ExecutorService.submit 返回的 Future 会保存任务结果。任务抛出的异常也在里面。只调用 awaitTermination,可以确认线程池结束,但它不会把某个任务里的异常抛给 JUnit。

这次对照跑了三次。第一次让工作线程断言 0 等于 1,然后丢弃 Future。第二次还是 0,增加带 5 秒超时的 get。第三次保留同样的检查,把模拟写入的返回值改成 1。

下面是三份控制台输出的关键行。第二次的退出码 1 是这次故意保留的失败。

text 复制代码
IgnoredFailureTest (exit 0)
worker: expected=1 actual=0
[         1 tests successful      ]
[         0 tests failed          ]

ObservedFailureTest (exit 1)
worker: expected=1 actual=0
    => java.util.concurrent.ExecutionException: org.opentest4j.AssertionFailedError: expected: <1> but was: <0>
[         0 tests successful      ]
[         1 tests failed          ]

FixedWorkerTest (exit 0)
worker: expected=1 actual=1
[         1 tests successful      ]
[         0 tests failed          ]

Future.get 抛出的 ExecutionException 包着子线程的断言错误。如果测试主动捕获它,应该继续检查或重新抛出异常。只打一行日志然后返回,会再丢一次失败。

运行后把三份记录留住

环境为 JDK 21 和 JUnit Platform Console Standalone 1.12.2。保存下面的完整文件为 AsyncRegressionTest.java。三个测试类独立运行,便于记录旧版漏检、修正测试后失败、正确输入通过。命令会把 JUnit 的报告分目录写出,控制台输出与退出码也一并保留。

powershell 复制代码
$jar = '.\junit-platform-console-standalone-1.12.2.jar'
javac -encoding UTF-8 -cp $jar AsyncRegressionTest.java
if ($LASTEXITCODE -ne 0) { throw 'compile failed' }
$classes = @('IgnoredFailureTest','ObservedFailureTest','FixedWorkerTest')
$results = foreach ($class in $classes) {
    $argsForTest = @('execute','--class-path','.','--select-class',$class,
                    '--disable-ansi-colors','--reports-dir',("runs/" + $class))
    java -jar $jar @argsForTest *> ("run-" + $class + ".txt")
    "$class exit=$LASTEXITCODE"
}
$results | Set-Content -Encoding utf8 exit-codes.txt
java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AsyncRegressionTest {
    static Future<?> verifyWrite(ExecutorService pool, int actualRows) {
        return pool.submit(() -> {
            System.out.printf("worker: expected=1 actual=%d%n", actualRows);
            assertEquals(1, actualRows);
        });
    }
}

class IgnoredFailureTest {
    @Test
    void workerEndsButFailureIsIgnored() throws InterruptedException {
        try (var pool = Executors.newSingleThreadExecutor()) {
            // 模拟写入结果为 0,任务里的断言会失败。
            AsyncRegressionTest.verifyWrite(pool, 0);
            pool.shutdown();
            assertTrue(pool.awaitTermination(5, TimeUnit.SECONDS));
        }
    }
}

class ObservedFailureTest {
    @Test
    void getPropagatesWorkerFailure() throws Exception {
        try (var pool = Executors.newSingleThreadExecutor()) {
            Future<?> job = AsyncRegressionTest.verifyWrite(pool, 0);
            job.get(5, TimeUnit.SECONDS);
        }
    }
}

class FixedWorkerTest {
    @Test
    void workerResultIsVerified() throws Exception {
        try (var pool = Executors.newSingleThreadExecutor()) {
            // 只改变模拟返回值,继续用相同断言与等待方式。
            Future<?> job = AsyncRegressionTest.verifyWrite(pool, 1);
            job.get(5, TimeUnit.SECONDS);
        }
    }
}

测试文件中的写入行数是固定输入。这次只验证子线程的断言能否影响测试结果。接入真实数据库后,要在任务完成并提交后再查落库数据。多个 Future 对应多个需要验收的任务时,逐个收集结果,保留能定位失败任务的标识。

你们的异步测试会故意让一个任务失败跑一遍吗?最容易漏掉的是执行异常、等待超时,还是任务根本没开始?

有关 Future 的结果和异常,见 JDK 21 Future 文档。控制台命令参考 JUnit 5.12.2 用户指南。

相关推荐
stark张宇2 小时前
Vibe Coding 入门实战:用 Claude Code 跑通 AI 产品开发流程(上)
后端·claude·vibecoding
考虑考虑2 小时前
JDK27中的Set.ofLazy
java·后端·java ee
文丑颜不良啊3 小时前
LeetCode2841~2860题目解析
java·leetcode
小南家的青蛙3 小时前
Tomcat与Jetty深度对比
java·tomcat·jetty
乌暮4 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
YDS8294 小时前
Small Spring IOC篇:容器事件及其监听器
java·spring
金銀銅鐵4 小时前
带您看懂字节码指令(三):一行加法里有什么
java·jvm
文丑颜不良啊4 小时前
LeetCode2921~2940题目解析
java·leetcode
我不会起名字3225 小时前
未接入最终读上限的参数:`websocketMaxMsgLen` 与 12.5 倍差距
后端·websocket·go·即时通讯·im·故障排查