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

如何对含ScheduledExecutorService与ScheduledFuture的代码做单元测试

解决方案

1. 改造原类提升可测试性

原类中ScheduledExecutorService为私有硬编码创建,无法在测试中替换为mock对象。通过构造注入方式暴露依赖,同时保留默认构造兼容原有使用:

import java.util.concurrent.*;

public class Refresher {

    private final ScheduledExecutorService executor;
    private ScheduledFuture<?> scheduledFuture = null;

    // 保持原有行为的默认构造
    public Refresher() {
        this(Executors.newSingleThreadScheduledExecutor());
    }

    // 用于测试/依赖注入的构造方法
    public Refresher(ScheduledExecutorService executor) {
        this.executor = executor;
    }

    public void refresh(Supplier<String> supplier) {
        synchronized (this) {
            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
            }

            scheduledFuture = executor.schedule(supplier::get, 500, TimeUnit.MILLISECONDS);
        }
    }

    public void shutdown() {
        executor.shutdown();
    }
}

2. Spock单元测试实现

利用Spock的mock能力,精确验证cancel方法的调用情况,确保除最后一次外的所有前置任务都被取消:

import spock.lang.Specification
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

class RefresherSpec extends Specification {

    def "两次调用refresh时,第一次任务被取消"() {
        given:
        def executor = Mock(ScheduledExecutorService)
        def firstFuture = Mock(ScheduledFuture)
        def secondFuture = Mock(ScheduledFuture)
        def refresher = new Refresher(executor)

        1 * executor.schedule(_, 500, TimeUnit.MILLISECONDS) >> firstFuture
        1 * executor.schedule(_, 500, TimeUnit.MILLISECONDS) >> secondFuture

        when:
        refresher.refresh({ "first" } as Supplier)
        refresher.refresh({ "second" } as Supplier)

        then:
        1 * firstFuture.cancel(true)
        0 * secondFuture.cancel(_)
    }

    def "三次调用refresh时,前两次任务被取消"() {
        given:
        def executor = Mock(ScheduledExecutorService)
        def firstFuture = Mock(ScheduledFuture)
        def secondFuture = Mock(ScheduledFuture)
        def thirdFuture = Mock(ScheduledFuture)
        def refresher = new Refresher(executor)

        1 * executor.schedule(_, 500, TimeUnit.MILLISECONDS) >> firstFuture
        1 * executor.schedule(_, 500, TimeUnit.MILLISECONDS) >> secondFuture
        1 * executor.schedule(_, 500, TimeUnit.MILLISECONDS) >> thirdFuture

        when:
        refresher.refresh({ "first" } as Supplier)
        refresher.refresh({ "second" } as Supplier)
        refresher.refresh({ "third" } as Supplier)

        then:
        1 * firstFuture.cancel(true)
        1 * secondFuture.cancel(true)
        0 * thirdFuture.cancel(_)
    }
}

测试逻辑说明

  • 通过mockScheduledExecutorService,控制每次schedule调用返回的ScheduledFuture实例。
  • 利用Spock的交互验证语法,精确检查cancel方法的调用次数和参数,确保前序任务被正确取消,最后一次任务不会触发取消操作。

3. 补充:验证Supplier执行情况(可选)

若需验证仅最后一次Supplier会被执行,可结合真实线程池与同步工具测试:

import spock.lang.Specification
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicBoolean

class RefresherExecutionSpec extends Specification {

    def "只有最后一次refresh的Supplier会被执行"() {
        given:
        def executor = Executors.newSingleThreadScheduledExecutor()
        def refresher = new Refresher(executor)
        def firstExecuted = new AtomicBoolean(false)
        def latch = new CountDownLatch(1)

        when:
        refresher.refresh({ firstExecuted.set(true); "first" } as Supplier)
        refresher.refresh({ latch.countDown(); "second" } as Supplier)
        def waitSuccess = latch.await(1, TimeUnit.SECONDS)

        then:
        waitSuccess
        !firstExecuted.get()

        cleanup:
        executor.shutdownNow()
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:48:17