Есть ли способ в коде или с аргументами JVM переопределить текущее время, представленное через System.currentTimeMillis
, кроме ручного изменения системных часов на хост-машине?
Немного предыстории:
У нас есть система, которая выполняет ряд бухгалтерских заданий, большая часть своей логики которых вращается вокруг текущей даты (т.е. 1-го числа месяца, 1-го числа года и т. Д.)
К сожалению, многие из унаследованных кодов вызывают функции, такие как new Date()
или Calendar.getInstance()
, обе из которых в конечном итоге вызывают System.currentTimeMillis
.
В целях тестирования прямо сейчас мы застряли в ручном обновлении системных часов, чтобы управлять тем, в какое время и дату код считает, что тест выполняется.
Итак, мой вопрос:
Есть ли способ переопределить то, что возвращается System.currentTimeMillis
? Например, чтобы указать JVM автоматически добавлять или вычитать некоторое смещение перед возвратом из этого метода?
Заранее спасибо!
источник
Ответы:
Я настоятельно рекомендую вместо того, чтобы возиться с системными часами, вы кусаете пулю и реорганизуете этот устаревший код, чтобы использовать сменные часы. В идеале это должно быть сделано с помощью внедрения зависимостей, но даже если вы используете заменяемый синглтон, вы получите возможность тестирования.
Это можно почти автоматизировать с помощью поиска и замены одноэлементной версии:
Calendar.getInstance()
наClock.getInstance().getCalendarInstance()
.new Date()
наClock.getInstance().newDate()
System.currentTimeMillis()
наClock.getInstance().currentTimeMillis()
(и т. д. по мере необходимости)
Сделав этот первый шаг, вы можете постепенно заменять синглтон на DI.
источник
java.time.Clock
класс, «позволяющий подключать альтернативные часы по мере необходимости».static
методов, убедитесь, что они не объектно-ориентированные, но внедрение зависимости без состояния, чей экземпляр не работает ни с одним состоянием экземпляра, на самом деле не лучше; в любом случае это просто причудливый способ замаскировать то, что фактически является «статическим» поведением.ТЛ; др
Да.
Clock
В java.timeУ нас есть новое решение проблемы замены сменных часов, чтобы облегчить тестирование с фиктивными значениями даты и времени. Пакет java.time в Java 8 включает абстрактный класс
java.time.Clock
с явной целью:Вы можете подключить свою собственную реализацию
Clock
, хотя, скорее всего, вы найдете такую, уже созданную для ваших нужд. Для вашего удобства java.time включает статические методы для получения специальных реализаций. Эти альтернативные реализации могут быть полезны во время тестирования.Измененная каденция
Различные
tick…
методы производят часы, которые увеличивают текущий момент с различной частотой.По умолчанию
Clock
время обновляется с такой частотой, как миллисекунды в Java 8 и в Java 9 с точностью до наносекунд (в зависимости от вашего оборудования). Вы можете запросить отчет об истинном текущем моменте с различной степенью детализации.tickSeconds
- Увеличение в целых секундахtickMinutes
- Увеличение в минутахtick
- Увеличивает переданныйDuration
аргумент.Ложные часы
Некоторые часы могут врать, производя результат, отличный от аппаратных часов ОС хоста.
fixed
- Сообщает один неизменный (не увеличивающийся) момент как текущий момент.offset
- Сообщает текущий момент, но смещенный переданнымDuration
аргументом.Например, зафиксируйте первый момент самого раннего Рождества в этом году. другими словами, когда Санта и его олени делают свою первую остановку . Самый ранний часовой пояс в настоящее время, кажется, находится
Pacific/Kiritimati
в+14:00
.Используйте эти специальные фиксированные часы, чтобы всегда возвращать один и тот же момент. Мы получаем первый момент Рождества в Киритимати , когда UTC показывает время на настенных часах на четырнадцать часов раньше, 10 часов утра предшествующей даты 24 декабря.
Смотрите живой код на IdeOne.com .
Истинное время, другой часовой пояс
Вы можете контролировать, какой часовой пояс назначается
Clock
реализацией. Это может быть полезно при тестировании. Но я не рекомендую это в производственном коде, где вы всегда должны явно указывать необязательные аргументыZoneId
илиZoneOffset
аргументы.Вы можете указать, что UTC будет зоной по умолчанию.
Вы можете указать любой часовой пояс. Укажите правильное время имя зоны в формате
continent/region
, напримерAmerica/Montreal
,Africa/Casablanca
илиPacific/Auckland
. Никогда не используйте аббревиатуру из 3–4 букв, напримерEST
или,IST
поскольку они не являются истинными часовыми поясами, не стандартизированы и даже не уникальны (!).Вы можете указать, что текущий часовой пояс JVM по умолчанию должен быть по умолчанию для конкретного
Clock
объекта.Запустите этот код для сравнения. Обратите внимание, что все они сообщают об одном и том же моменте, об одной и той же точке на временной шкале. Они различаются только временем настенных часов ; Другими словами, три способа сказать одно и то же, три способа показать один и тот же момент.
America/Los_Angeles
была текущей зоной JVM по умолчанию на компьютере, на котором выполнялся этот код.По определению,
Instant
класс всегда находится в формате UTC. Таким образом, эти триClock
использования, связанные с зонами, имеют точно такой же эффект.Часы по умолчанию
Реализация, используемая по умолчанию,
Instant.now
- это та реализация, которую возвращаетClock.systemUTC()
. Эта реализация используется, когда вы не указываетеClock
. Убедитесь сами в исходном коде предварительной версии Java 9 дляInstant.now
.По умолчанию
Clock
дляOffsetDateTime.now
иZonedDateTime.now
установленоClock.systemDefaultZone()
. Смотрите исходный код .Поведение реализаций по умолчанию изменилось между Java 8 и Java 9. В Java 8 текущий момент фиксируется с разрешением только в миллисекундах, несмотря на способность классов сохранять разрешение наносекунд . В Java 9 появилась новая реализация, способная фиксировать текущий момент с разрешением наносекунды - в зависимости, конечно, от возможностей аппаратных часов вашего компьютера.
О java.time
Java.time каркас встроен в Java 8 и более поздних версий. Эти классы вытеснять неприятные старые устаревшие классы даты и времени , такие как
java.util.Date
,Calendar
, иSimpleDateFormat
.Чтобы узнать больше, см. Oracle Tutorial . И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310 .
Проект Joda-Time , находящийся сейчас в режиме обслуживания , рекомендует перейти на классы java.time .
Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, совместимый с JDBC 4.2 или новее. Нет необходимости в строках, нет необходимости в
java.sql.*
занятиях. Hibernate 5 и JPA 2.2 поддерживают java.time .Где взять классы java.time?
источник
Как сказал Джон Скит :
Итак, начнем (предполагая, что вы только что заменили все свои
new Date()
наnew DateTime().toDate()
)Если вы хотите импортировать библиотеку с интерфейсом (см. Комментарий Джона ниже), вы можете просто использовать Prevayler's Clock , который предоставит реализации, а также стандартный интерфейс. Полная банка составляет всего 96 КБ, поэтому она не должна разбивать банк ...
источник
Хотя использование некоторого шаблона DateFactory кажется приятным, он не распространяется на библиотеки, которые вы не можете контролировать - представьте себе аннотацию Validation @Past с реализацией, основанной на System.currentTimeMillis (такая есть).
Вот почему мы используем jmockit, чтобы напрямую имитировать системное время:
Поскольку невозможно получить исходное разблокированное значение миллис, вместо этого мы используем нано-таймер - это не связано с настенными часами, но здесь достаточно относительного времени:
Существует задокументированная проблема: с HotSpot время возвращается в норму после ряда вызовов - вот отчет о проблеме: http://code.google.com/p/jmockit/issues/detail?id=43
Чтобы преодолеть это, мы должны включить одну конкретную оптимизацию HotSpot - запустить JVM с этим аргументом
-XX:-Inline
.Хотя это может быть не идеально для производственной среды, это просто отлично для тестов и абсолютно прозрачно для приложений, особенно когда DataFactory не имеет смысла для бизнеса и вводится только из-за тестов. Было бы неплохо иметь встроенную опцию JVM для работы в разное время, жаль, что это невозможно без подобных хаков.
Полная история находится в моем сообщении в блоге здесь: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
Полный удобный класс SystemTimeShifter представлен в сообщении. Класс можно использовать в ваших тестах, или его можно очень легко использовать в качестве первого основного класса перед вашим настоящим основным классом, чтобы запустить ваше приложение (или даже весь сервер приложений) в другое время. Конечно, это в основном предназначено для целей тестирования, а не для производственной среды.
РЕДАКТИРОВАТЬ Июль 2014: JMockit сильно изменился в последнее время, и вы обязательно будете использовать JMockit 1.0, чтобы использовать это правильно (IIRC). Определенно не может обновиться до последней версии, где интерфейс полностью отличается. Я думал о том, чтобы встраивать только необходимое, но поскольку нам это не нужно в наших новых проектах, я вообще не занимаюсь этим.
источник
Powermock отлично работает. Просто использовал это, чтобы издеваться
System.currentTimeMillis()
.источник
@PrepareForTest
аннотацию к тестовому классу).System.currentTimeMillis
любом месте вашего пути к классам (любой библиотеке), вы должны проверить каждый класс. Я имел в виду именно это, не более того. Дело в том, что вы высмеиваете такое поведение на стороне вызывающего абонента («вы говорите конкретно»). Это нормально для простого теста, но не для тестов, где вы не можете быть уверены, что и откуда вызывает этот метод (например, более сложные тесты компонентов с задействованными библиотеками). Это вовсе не означает, что Powermock ошибочен, это просто означает, что вы не можете использовать его для этого типа теста.Используйте аспектно-ориентированное программирование (АОП, например AspectJ), чтобы сплести класс System так, чтобы он возвращал предопределенное значение, которое вы можете установить в своих тестовых примерах.
Или переплетите классы приложений, чтобы перенаправить вызов
System.currentTimeMillis()
илиnew Date()
на другой собственный служебный класс.java.lang.*
Однако создание системных классов ( ) немного сложнее, и вам может потребоваться выполнить автономное переплетение для rt.jar и использовать отдельный JDK / rt.jar для ваших тестов.Это называется двоичным переплетением, и есть также специальные инструменты для переплетения системных классов и обхода некоторых проблем с ним (например, загрузка виртуальной машины может не работать)
источник
На самом деле нет способа сделать это непосредственно в виртуальной машине, но вы могли бы что-нибудь, чтобы программно установить системное время на тестовой машине. В большинстве (всех?) ОС для этого есть команды командной строки.
источник
date
иtime
команд. В Linuxdate
команда.Рабочий способ переопределения текущего системного времени для целей тестирования JUnit в веб-приложении Java 8 с EasyMock, без Joda Time и без PowerMock.
Вот что вам нужно сделать:
Что нужно сделать в тестируемом классе
Шаг 1
Добавьте новый
java.time.Clock
атрибут к тестируемому классуMyService
и убедитесь, что новый атрибут будет правильно инициализирован со значениями по умолчанию с помощью блока создания экземпляра или конструктора:Шаг 2
Вставьте новый атрибут
clock
в метод, который вызывает текущую дату и время. Например, в моем случае мне пришлось выполнить проверку того, произошла ли ранее дата, хранящаяся в базе данныхLocalDateTime.now()
, которую я заменилLocalDateTime.now(clock)
, например:Что нужно сделать в тестовом классе
Шаг 3
В тестовом классе создайте объект фиктивных часов и вставьте его в экземпляр тестируемого класса непосредственно перед вызовом тестируемого метода
doExecute()
, а затем сбросьте его обратно сразу после этого, например:Проверьте его в режиме отладки, и вы увидите, что дата 3 февраля 2017 года была правильно введена в
myService
экземпляр и использована в инструкции сравнения, а затем была правильно сброшена на текущую дату с помощьюinitDefaultClock()
.источник
На мой взгляд, может работать только неинвазивное решение. Особенно, если у вас есть внешние библиотеки и большая база устаревшего кода, нет надежного способа имитировать время.
JMockit ... работает только ограниченное количество раз
PowerMock & Co ... необходимо имитировать клиентов для System.currentTimeMillis (). Опять инвазивный вариант.
Из этого я вижу, что упомянутый подход javaagent или aop прозрачен для всей системы. Кто-нибудь сделал это и мог указать на такое решение?
@jarnbjo: не могли бы вы показать код javaagent, пожалуйста?
источник
Если вы используете Linux, вы можете использовать основную ветку libfaketime или во время тестирования совершить 4ce2835 .
Просто установите переменную окружения, указав время, которое вы хотите использовать для имитации своего Java-приложения, и запустите его с помощью ld-preloading:
Вторая переменная среды имеет первостепенное значение для приложений Java, которые в противном случае зависли бы. На момент написания ему требуется главная ветка libfaketime.
Если вы хотите изменить время управляемой службы systemd, просто добавьте следующее в свои переопределения файла модуля, например, для elasticsearch это будет
/etc/systemd/system/elasticsearch.service.d/override.conf
:Не забудьте перезагрузить systemd с помощью `systemctl daemon-reload
источник
Если вы хотите имитировать метод, имеющий
System.currentTimeMillis()
аргумент, вы можете передатьanyLong()
класс Matchers в качестве аргумента.PS Я могу успешно запустить свой тестовый пример, используя вышеуказанный трюк, и просто поделиться более подробной информацией о моем тесте, что я использую фреймворки PowerMock и Mockito.
источник