如何对含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(_) } }
测试逻辑说明
- 通过mock
ScheduledExecutorService,控制每次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
相关产品推荐
相关产品推荐

