Проверьте, что ScheduledExecutorService вызывает выполняемый объект несколько раз, используя Java mockito

#java #unit-testing #mockito #guice #scheduledexecutorservice

Вопрос:

У меня есть служба, вызываемая раз в минуту. Я пытаюсь написать модульный тест, чтобы проверить, продолжает ли планировщик вызывать выполняемый объект, даже если из вызовов возникает исключение. Уже есть тестовый случай, который подтверждает, что исключение, пойманное в выполняемом, не всплывает.

 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();
 

сбой с сообщением о том, что он был вызван только один раз.

Оба просто вызывают выполняемый метод один раз, а затем завершают модульный тест. Я не понимаю, почему планировщик не работает во время тестирования.

Пожалуйста, предложите, как написать тестовый пример для этого варианта использования.

Комментарии:

1. Есть ли у вашего теста время на сон, чтобы подождать 2 минуты, прежде чем вы подтвердите?

2. Добавление сна на 2 минуты сработало. Удалось зафиксировать несколько вызовов service.run(). Спасибо за предложение. Не могли бы вы объяснить, пожалуйста, почему здесь требуется сон?

3. В идеальном модульном тесте вам не нужно было бы проверять, сколько раз service.run вызывался модуль scheduler , потому что в этот момент вы пытаетесь проверить scheduler , сработал ли он или нет. Вместо этого вам следует протестировать ServiceRunner.run , когда вы вызываете соответствующий метод с соответствующими аргументами в планировщике. Вы можете сделать это, введя макет/шпионский экземпляр планировщика. Модульное тестирование фреймворка/библиотечного кода — это когда вы заканчиваете длительными бессмысленными тестами

Ответ №1:

Ваш тестовый пример полностью испорчен, потому что вы недостаточно абстрагировались.

Давайте представим, что у вас есть 2000 модульных тестов, и все равно у вас неправильная абстракция. 2000 тестов-это не так уж много, но они могут выполняться примерно за 1 минуту. Теперь представьте, что 1% ваших тестов зависит от вашего ServiceRunner класса, это 20 тестов. Вполне разумно думать, что 20 классов используют класс с именем ServiceRunner . Что ж, теперь ваши тесты вместо того, чтобы выполняться около 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(); //