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