Зависящие от времени юнит-тесты
Мне нужно протестировать функцию, результат которой будет зависеть от текущего времени (используя isBeforeNow()
).
public boolean isAvailable() {
return (this.someDate.isBeforeNow());
}
Можно ли заглушить / смоделировать системное время с помощью Mockito, чтобы я мог надежно протестировать функцию?
5 ответов
Joda Time поддерживает установку "фальшивого" текущего времени через setCurrentMillisFixed
а также setCurrentMillisOffset
методы DateTimeUtils
учебный класс.
См. http://joda-time.sourceforge.net/api-release/org/joda/time/DateTimeUtils.html
Лучший способ (IMO) сделать ваш код тестируемым - это извлечь зависимость "что такое текущее время" в его собственный интерфейс с реализацией, которая использует текущее системное время (обычно используется), и реализацией, которая позволяет вам устанавливать время продвигай как хочешь и тд
Я использовал этот подход в различных ситуациях, и он работал хорошо. Это легко настроить - просто создайте интерфейс (например, Clock
) который имеет единственный метод, чтобы дать вам текущий момент в любом формате, который вы хотите (например, используя Joda Time, или, возможно, Date
).
Java 8 представила абстрактный класс java.time.Clock
что позволяет вам иметь альтернативную реализацию для тестирования. Это именно то, что Джон предложил в своем ответе тогда.
Чтобы добавить ответ Джона Скита, Joda Time уже содержит интерфейс текущего времени: DateTimeUtils.MillisProvider
Например:
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils.MillisProvider;
public class Check {
private final MillisProvider millisProvider;
private final DateTime someDate;
public Check(MillisProvider millisProvider, DateTime someDate) {
this.millisProvider = millisProvider;
this.someDate = someDate;
}
public boolean isAvailable() {
long now = millisProvider.getMillis();
return (someDate.isBefore(now));
}
}
Смоделируйте время в модульном тесте (используя Mockito, но вы можете реализовать свой собственный класс MillisProviderMock):
DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10);
MillisProvider mockMillisProvider = mock(MillisProvider.class);
when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis());
Check check = new Check(mockMillisProvider, someDate);
Использовать текущее время в производстве ( DateTimeUtils.SYSTEM_MILLIS_PROVIDER был добавлен в Joda Time в 2.9.3):
Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);
Я использую подход, аналогичный подходу Джона, но вместо того, чтобы создавать специализированный интерфейс только для текущего времени (скажем, Clock
Я обычно создаю специальный интерфейс тестирования (скажем, MockupFactory
). Я положил туда все методы, которые мне нужны для тестирования кода. Например, в одном из моих проектов у меня есть четыре метода:
- тот, который возвращает макет клиента базы данных;
- тот, который создает объект уведомлений макета, который уведомляет код об изменениях в базе данных;
- тот, который создает макет java.util.Timer, который запускает задачи, когда я этого хочу;
- тот, который возвращает текущее время.
У тестируемого класса есть конструктор, который принимает этот интерфейс среди других аргументов. Тот без этого аргумента просто создает экземпляр этого интерфейса по умолчанию, который работает "в реальной жизни". И интерфейс, и конструктор являются частными пакетами, поэтому API тестирования не выходит за пределы пакета.
Если мне нужно больше имитируемых объектов, я просто добавляю метод к этому интерфейсу и реализую его как в тестировании, так и в реальных реализациях.
Таким образом, я создаю код, пригодный для тестирования в первую очередь, не налагая слишком много на сам код. Фактически, код становится еще чище, так как большая часть заводского кода собирается в одном месте. Например, если мне нужно переключиться на другую реализацию клиента базы данных в реальном коде, мне нужно изменить только одну строку вместо поиска ссылок на конструктор.
Конечно, так же, как и в случае с подходом Джона, он не будет работать с сторонним кодом, который вы не можете или не можете изменять.