Java 测试涉及ScheduledExecutorService#scheduleAtFixedRate的代码时单元测试失败
关于可复制的例子,我有以下的类Java 测试涉及ScheduledExecutorService#scheduleAtFixedRate的代码时单元测试失败,java,unit-testing,junit,mockito,scheduledexecutorservice,Java,Unit Testing,Junit,Mockito,Scheduledexecutorservice,关于可复制的例子,我有以下的类 public class SampleCaching { ScheduledExecutorService executorService; @com.google.inject.Inject InterestCache interestCache; @Inject MultimediaCache multimediaCache; @Inject public SampleCaching(InterestCache in
public class SampleCaching {
ScheduledExecutorService executorService;
@com.google.inject.Inject InterestCache interestCache;
@Inject MultimediaCache multimediaCache;
@Inject
public SampleCaching(InterestCache interestCache, MultimediaCache multimediaCache) {
this.executorService = Executors.newScheduledThreadPool(3);
this.interestCache = interestCache;
this.multimediaCache = multimediaCache;
}
protected void calculate() {
interestCache.populateOne();
interestCache.populateTwo();
multimediaCache.populateMultimedia();
log.info("Cache population completed!");
}
public void start() {
executorService.scheduleAtFixedRate(this::calculate,
0, 20, TimeUnit.MINUTES); // notice initial delay
}
}
事实上,我为这段代码编写了一个半错误的单元测试,内容如下:
@org.junit.runner.RunWith(PowerMockRunner.class)
@org.powermock.core.classloader.annotations.PowerMockIgnore("javax.management.*")
public class SampleCachingTest {
@org.mockito.Mock InterestCache interestCache;
@Mock MultimediaCache multimediaCache;
@org.mockito.InjectMocks SampleCaching sampleCaching;
@Test
public void testInvokingStart() throws Exception {
sampleCaching.start();
verify(multimediaCache, times(0)).populateMultimedia();
verify(interestCache, times(0)).populateOne();
verify(interestCache, times(0)).populateTwo();
}
}
- 我看了一遍,但我不知道出于什么原因,我需要在这里模拟
的返回类型,不管怎样,模拟对我来说工作正常scheduleAtFixedRate
- 找到了与
测试关系不大的最上等答案(至少我现在是这么想的)ScheduledExecutorService
1分钟,这个测试就通过了
真正让我问这个问题的是如果我把测试改为
@Test
public void testInvokingStart() throws Exception {
sampleCaching.start();
verify(interestCache, times(1)).populateOne();
verify(interestCache, times(1)).populateTwo();
}
它总是成功执行,但添加验证
用于多媒体时,测试总是失败,另一方面:
verify(multimediaCache, times(1)).populateMultimedia(); // or even to `times(0)`
这种行为(确定性或确定性)背后有什么原因吗?修复此测试的正确方法是什么?因此,您正在触发方法SampleCaching#启动自己,这反过来会通知ScheduledExecutorService调用calculate方法,初始延迟为0秒。这将在一个单独的线程中发生。同时,测试代码将继续运行,接下来要做的是验证在多媒体缓存上没有调用populateMultimedia方法。然后对populateOne和populateTwo也一样。
此操作的成功与否取决于启动的另一个线程中计算方法的进度。如果它已经调用了populateMultimedia方法,那么您的第一次验证将失败,其他验证也将失败。另一方面,如果它没有进展到那么远,测试将成功,但可能在populateOne或populateTwo上失败
您或者需要构建一个同步机制(例如java.util.concurrent.CountDownLatch),这个计算方法在最后进行倒计时,测试代码在验证之前进行等待,或者在调用start方法和验证调用之间设置一个合理的延迟。
第一种是侵入性的,因为它会更改您正在测试的组件。你可以考虑创建一个SimuleCurp的子类,它覆盖了计算方法,但是如果你的计算方法是私有的,那么它又是侵入性的。谢谢你回答这个问题,关于“比你的第一个验证失败,其他人也会失败……”,有一个陷阱,尽管我在实际代码中更改了方法调用的顺序或在测试中进行了验证,但测试始终无法验证populateMultimedia
方法。(我使用IntelliJ来执行这些测试,但在测试验证期间如何执行这些方法会有影响。)…除此之外,这是观察到的一个奇怪的点,并促使我将此作为一个问题提出。更改实际代码中的顺序或更改验证顺序不会改变此测试的基本问题。测试无法忽略两个并发线程正在执行的事实。一个执行逻辑,一个验证逻辑是否正在执行,这一切都取决于线程上执行的时间。使用CountDownLatch将对您有所帮助。请注意,时间(0)可以替换为never(),而时间(1)可以始终忽略。因此,verify(multimediaCache).populateMultimedia()等于verify(multimediaCache,乘以(1)),populateMultimedia()