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

Java 17多线程场景下流式断言ResultCaptor设计方案咨询

解决方案:多线程下流式断言的ResultCaptor实现

核心问题分析

你当前的实现中,子线程抛出的断言异常会被线程自身的异常处理逻辑消化,无法直接传递到测试主线程。这是因为每个子线程拥有独立的执行上下文,主线程不会主动感知子线程的异常,必须通过主动收集或同步等待的方式来捕获。

修正基础ResultCaptor(修复空指针问题)

原ResultCaptor存在values未初始化的空指针隐患,先修正这个基础类:

public static class ResultCaptor<T> implements Answer<T> {
    protected final List<T> values = Collections.synchronizedList(new ArrayList<>());
    protected T lastValue;

    public List<T> getValues() {
        return Collections.unmodifiableList(values);
    }

    public T getLastValue() {
        return lastValue;
    }

    @Override
    public T answer(InvocationOnMock invocation) throws Throwable {
        lastValue = (T) invocation.callRealMethod();
        values.add(lastValue);
        return lastValue;
    }
}

支持多线程流式断言的MyResultCaptor实现

实现需要满足:线程安全存储异常、主线程同步等待、首次异常立即终止多余断言并传递到主线程:

public static class MyResultCaptor<T> extends ResultCaptor<T> {
    private final List<T> expectedResults;
    private final AtomicInteger invocationCount = new AtomicInteger(0);
    private final AtomicReference<AssertionError> firstError = new AtomicReference<>();
    private final CountDownLatch latch;

    public MyResultCaptor(List<T> expectedResults) {
        this.expectedResults = expectedResults;
        this.latch = new CountDownLatch(expectedResults.size());
    }

    @Override
    public T answer(InvocationOnMock invocation) throws Throwable {
        T actual = super.answer(invocation);
        int currentIndex = invocationCount.getAndIncrement();

        // 已有异常时跳过后续断言,避免无效执行
        if (firstError.get() != null) {
            latch.countDown();
            return actual;
        }

        try {
            if (currentIndex >= expectedResults.size()) {
                throw new AssertionError("Unexpected extra invocation at index " + currentIndex);
            }
            Assertions.assertEquals(expectedResults.get(currentIndex), actual);
        } catch (AssertionError e) {
            // 仅保留第一个异常
            firstError.compareAndSet(null, e);
        } finally {
            latch.countDown();
        }

        return actual;
    }

    /**
     * 等待所有预期调用完成,若有异常则抛出
     */
    public void awaitAndVerify() throws InterruptedException {
        latch.await();
        AssertionError error = firstError.get();
        if (error != null) {
            throw error;
        }
        // 额外验证调用次数是否匹配预期
        if (invocationCount.get() != expectedResults.size()) {
            throw new AssertionError("Expected " + expectedResults.size() + " invocations, but got " + invocationCount.get());
        }
    }
}

测试方法中的使用示例

@Test
void test() throws InterruptedException {
    List<String> expectedStrings = List.of("Hello", "world", "this", "is", "strangely", "difficult");

    MyResultCaptor<String> resultCaptor = new MyResultCaptor<>(expectedStrings);
    doAnswer(resultCaptor).when(myService).doesSomethingThatReturnsAString();

    // 启动多线程任务
    otherService.doTheThingThatStartsThreads();

    // 等待所有调用完成并验证结果
    resultCaptor.awaitAndVerify();
}

关键设计说明

  1. 线程安全保障:使用AtomicReference存储首个异常、AtomicInteger计数、synchronizedList存储结果,避免多线程竞争问题。
  2. 高效终止逻辑:一旦捕获到第一个异常,后续调用直接跳过断言,减少无效执行。
  3. 主线程同步:通过CountDownLatch让主线程等待所有预期的Mock调用完成,确保断言全部执行后再检查异常。
  4. 完整性验证:除了结果断言,还会验证调用次数是否与预期一致,避免漏调用或多调用的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:33:22