测试涉及ScheduledExecutiorService#scheduleAtFixedRate的代码时单元测试失败



对于可复制的示例,我有以下类

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();
}
}
  • 我浏览了Mocking ScheduledExecutiorService。scheduleWithFixedDelay(…(返回null,但我不知道出于什么原因,我应该在这里嘲笑scheduleAtFixedRate的返回类型,不管怎样,mock对我来说都很好
  • 学习了如何对执行器服务内运行的代码片段进行单元测试,而不是在Thread.sleep(time(上等待,以找到与ScheduledExecutorService测试无关的投票最多的答案(至少我现在是这么想的(

我说,半不正确,因为如果我增加实际代码中的初始延迟,比如说1 MINUTE,这个测试就会通过

真正让我问这个问题的是,如果我把测试改为

@Test
public void testInvokingStart() throws Exception {
sampleCaching.start();
verify(interestCache, times(1)).populateOne();
verify(interestCache, times(1)).populateTwo();
}

它总是成功执行,但为多媒体添加verify总是失败另一方面:

verify(multimediaCache, times(1)).populateMultimedia(); // or even to `times(0)`

这种行为背后有原因吗(确定性还是确定性(?修复此测试的正确方法是什么

因此,您正在触发方法SampleCaching#启动您自己,这反过来又告诉ScheduledExecutorService调用计算方法,初始延迟为0秒。这将在一个单独的线程中发生。同时,您的测试代码将继续运行,接下来要做的就是验证是否没有在您的multimediaCache上调用populateMultedia方法。populateOne和populateTwo也是如此。此操作的成功与否将取决于启动的另一个线程中的计算方法所取得的进展。如果它已经调用了populateMultedia方法,那么您的第一次验证将失败,其他验证也将失败。另一方面,如果它还没有取得那么大的进展,那么测试将成功,但可能会在populateOne或populateTwo上失败。

您要么需要构建一个同步机制(例如java.util.concurrent.CountDownLatch(,即您的计算方法在结束时进行countDown,测试代码在验证前进行等待,要么在调用启动方法和验证调用之间设置合理的延迟。第一个是侵入性的,因为它改变了您正在测试的组件。您可以考虑创建SimpleCaching的子类来覆盖计算方法,但如果您的计算方法是私有的,那么这也是侵入性的。

最新更新