使用 Java mockito 验证 scheduledExecutorService 多次调用 runnable

Verify scheduledExecutorService calls the runnable multiple times using Java mockito

我每分钟调用一次服务。我正在尝试编写一个单元测试来检查调度程序是否继续调用可运行对象,即使调用抛出异常也是如此。 已经有一个测试用例可以确认在 runnable 中捕获的异常没有冒泡。

class ServiceRunner {

@NonNull
private final Service service;

@NonNull
private final ScheduledExecutorService scheduler;

@Inject
public ServiceRunner(final Service service) {
    this(service, Executors.newSingleThreadScheduledExecutor());
}

public void run() {
    scheduler.scheduleAtFixedRate(this::runService, 0, 1, TimeUnit.MINUTES);
}

@VisibleForTesting
void runService() {
    try {
        service.run();
    } catch {
        log.info("Exception");
    }
} } 

到目前为止,我尝试创建一个模拟调度程序和一个调度程序对象。 两种方法都适用于

verify(service).run();

但是

verify(service, times(2)).run();

失败并显示仅调用一次的消息。

两者都只调用一次可运行方法,然后在单元测试中终止。 我不明白为什么调度程序在测试时不工作。

请建议如何为此用例编写测试用例。

你的测试用例完全有缺陷,因为你没有足够的抽象。

假设您有 2000 个单元测试,但仍然有一个错误的抽象。 2000 次测试并不多,但它可以在大约 1 分钟内 运行。现在假设 1% 的测试依赖于 ServiceRunner class,即 20 个测试。认为 20 classes 使用一个名为 ServiceRunner 的 class 并不是没有道理的。那么,现在你的测试,而不是 运行ning 大约 1 分钟,他们 运行 1 分钟 + 20 * 1 分钟,所以 21 分钟。

所以帮自己一个忙,向您的构造函数添加一个参数,说明您的服务必须达到的固定速率 运行。当您测试它时,请确保它 运行 是每个固定速率。

这样,您可以让您的测试等待 2 * 1 秒(如果您写了 1 秒),或者更好的 2 * 100 毫秒。

你的测试会变成这样:

var unit = MILLISECONDS;
var duration = 100;

var service = mock(Service.class);
var serviceRunner = new ServiceRunner(service, duration, unit);
serviceRunner.run();
unit.sleep(2 * duration);
verify(service, times(2)).run();

此外,不要忘记关闭您的 scheduler,通常通过包装调用它:

class ServiceRunner {
...
public void shutdown() {
  scheduler.shutdown();
}
...
}

然后调整你的测试:

var unit = MILLISECONDS;
var duration = 100;

var service = mock(Service.class);
var serviceRunner = new ServiceRunner(service, duration, unit);
serviceRunner.run();
unit.sleep(2 * duration);
serviceRunner.shutdown();
unit.sleep(duration); // Give it more time to make sure it's called twice and not three times.
verify(service, times(2)).run(); //