Переопределить Java System.currentTimeMillis для тестирования чувствительного ко времени кода

129

Есть ли способ в коде или с аргументами JVM переопределить текущее время, представленное через System.currentTimeMillis, кроме ручного изменения системных часов на хост-машине?

Немного предыстории:

У нас есть система, которая выполняет ряд бухгалтерских заданий, большая часть своей логики которых вращается вокруг текущей даты (т.е. 1-го числа месяца, 1-го числа года и т. Д.)

К сожалению, многие из унаследованных кодов вызывают функции, такие как new Date()или Calendar.getInstance(), обе из которых в конечном итоге вызывают System.currentTimeMillis.

В целях тестирования прямо сейчас мы застряли в ручном обновлении системных часов, чтобы управлять тем, в какое время и дату код считает, что тест выполняется.

Итак, мой вопрос:

Есть ли способ переопределить то, что возвращается System.currentTimeMillis? Например, чтобы указать JVM автоматически добавлять или вычитать некоторое смещение перед возвратом из этого метода?

Заранее спасибо!

Майк Кларк
источник
1
Я не знаю, актуально ли это сейчас, но есть другой способ добиться этого с помощью AspectJ, см. Мой ответ по адресу: stackoverflow.com/questions/18239859/…
Nándor Előd Fekete 05
@ NándorElődFekete решение в ссылке интересно, однако требует перекомпиляции кода. Интересно, есть ли у оригинального плаката возможность перекомпилировать, учитывая тот факт, что он утверждает, что имеет дело с устаревшим кодом.
cleberz
1
@cleberz Одно из хороших свойств AspectJ - это то, что он работает напрямую с байт-кодом, поэтому для работы ему не нужен исходный исходный код.
Nándor Előd Fekete
@ NándorElődFekete большое спасибо за подсказку. Мне не было известно об инструментарии уровня байт-кода аспектаJ (особенно инструментарии классов JDK). Это заняло у меня некоторое время, но я смог понять, что мне нужно как переплетение во время компиляции rt.jar, так и переплетение во время загрузки классов, отличных от jdk, чтобы удовлетворить мои потребности (переопределить System. currentTimeMillis () и System.nanoTime ()).
cleberz
@cleberz У меня есть еще один ответ по поводу работы с классами JRE , вы тоже можете это проверить.
Nándor Előd Fekete

Ответы:

115

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

Это можно почти автоматизировать с помощью поиска и замены одноэлементной версии:

  • Заменить Calendar.getInstance()на Clock.getInstance().getCalendarInstance().
  • Заменить new Date()наClock.getInstance().newDate()
  • Заменить System.currentTimeMillis()наClock.getInstance().currentTimeMillis()

(и т. д. по мере необходимости)

Сделав этот первый шаг, вы можете постепенно заменять синглтон на DI.

Джон Скит
источник
50
Загромождать код путем абстрагирования или упаковки всех потенциальных API-интерфейсов для повышения тестируемости - ИМХО, не очень хорошая идея. Даже если вы сможете выполнить рефакторинг один раз с помощью простого поиска и замены, код станет намного сложнее читать, понимать и поддерживать. Несколько макетов фреймворков должны иметь возможность изменять поведение System.currentTimeMillis, в противном случае лучше использовать АОП или самодельную инструментализацию.
jarnbjo 05
21
@jarnbjo: Ну, конечно, вы можете высказать свое мнение, но это довольно хорошо зарекомендовавший себя метод, IMO, и я определенно использовал его с большим эффектом. Это не только улучшает тестируемость, но и делает явной вашу зависимость от системного времени, что может быть полезно при обзоре кода.
Джон Скит
4
@ virgo47: Думаю, нам придется согласиться, чтобы не соглашаться. Я не считаю, что «явное указание ваших зависимостей» загрязняет код - я считаю это естественным способом сделать код более понятным. Я также не считаю, что скрытие этих зависимостей с помощью статических вызовов без надобности «совершенно приемлемо».
Джон Скит
26
ОБНОВЛЕНИЕ . Новый пакет java.time, встроенный в Java 8, включает java.time.Clockкласс, «позволяющий подключать альтернативные часы по мере необходимости».
Basil Bourque
8
Этот ответ подразумевает, что с DI все в порядке. Лично я еще не видел приложения в реальном мире, которое бы эффективно его использовало. Вместо этого мы видим изобилие бессмысленных отдельных интерфейсов, «объектов» без состояния, «объектов» только для данных и классов с низкой связностью. ИМО, простота и объектно-ориентированный дизайн (с истинными объектами с полным состоянием) - лучший выбор. Что касается staticметодов, убедитесь, что они не объектно-ориентированные, но внедрение зависимости без состояния, чей экземпляр не работает ни с одним состоянием экземпляра, на самом деле не лучше; в любом случае это просто причудливый способ замаскировать то, что фактически является «статическим» поведением.
Rogério
78

ТЛ; др

Есть ли способ в коде или с аргументами JVM переопределить текущее время, представленное через System.currentTimeMillis, кроме ручного изменения системных часов на хост-машине?

Да.

Instant.now( 
    Clock.fixed( 
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)

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.

LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );

Используйте эти специальные фиксированные часы, чтобы всегда возвращать один и тот же момент. Мы получаем первый момент Рождества в Киритимати , когда UTC показывает время на настенных часах на четырнадцать часов раньше, 10 часов утра предшествующей даты 24 декабря.

Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );

Instant.toString (): 2016-12-24T10: 00: 00Z

zdt.toString (): 2016-12-25T00: 00 + 14: 00 [Тихоокеанский регион / Киритимати]

Смотрите живой код на IdeOne.com .

Истинное время, другой часовой пояс

Вы можете контролировать, какой часовой пояс назначается Clockреализацией. Это может быть полезно при тестировании. Но я не рекомендую это в производственном коде, где вы всегда должны явно указывать необязательные аргументы ZoneIdили ZoneOffsetаргументы.

Вы можете указать, что UTC будет зоной по умолчанию.

ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );

Вы можете указать любой часовой пояс. Укажите правильное время имя зоны в формате continent/region, например America/Montreal, Africa/Casablancaили Pacific/Auckland. Никогда не используйте аббревиатуру из 3–4 букв, например ESTили, ISTпоскольку они не являются истинными часовыми поясами, не стандартизированы и даже не уникальны (!).

ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );

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

ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );

Запустите этот код для сравнения. Обратите внимание, что все они сообщают об одном и том же моменте, об одной и той же точке на временной шкале. Они различаются только временем настенных часов ; Другими словами, три способа сказать одно и то же, три способа показать один и тот же момент.

System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );

America/Los_Angeles была текущей зоной JVM по умолчанию на компьютере, на котором выполнялся этот код.

zdtClockSystemUTC.toString (): 2016-12-31T20: 52: 39.688Z

zdtClockSystem.toString (): 2016-12-31T15: 52: 39.750-05: 00 [Америка / Монреаль]

zdtClockSystemDefaultZone.toString (): 2016-12-31T12: 52: 39.762-08: 00 [Америка / Лос-Анджелес]

По определению, Instantкласс всегда находится в формате UTC. Таким образом, эти три Clockиспользования, связанные с зонами, имеют точно такой же эффект.

Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );

InstantClockSystemUTC.toString (): 2016-12-31T20: 52: 39.763Z

InstantClockSystem.toString (): 2016-12-31T20: 52: 39.763Z

InstantClockSystemDefaultZone.toString (): 2016-12-31T20: 52: 39.763Z

Часы по умолчанию

Реализация, используемая по умолчанию, Instant.now- это та реализация, которую возвращает Clock.systemUTC(). Эта реализация используется, когда вы не указываете Clock. Убедитесь сами в исходном коде предварительной версии Java 9 дляInstant.now .

public static Instant now() {
    return Clock.systemUTC().instant();
}

По умолчанию Clockдля OffsetDateTime.nowи ZonedDateTime.nowустановлено Clock.systemDefaultZone(). Смотрите исходный код .

public static ZonedDateTime now() {
    return 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?

Василий Бурк
источник
это правильный ответ
bharal
42

Как сказал Джон Скит :

«Использовать Joda Time» почти всегда является лучшим ответом на любой вопрос, касающийся «как мне достичь X с помощью java.util.Date/Calendar?»

Итак, начнем (предполагая, что вы только что заменили все свои new Date()на new DateTime().toDate())

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

Если вы хотите импортировать библиотеку с интерфейсом (см. Комментарий Джона ниже), вы можете просто использовать Prevayler's Clock , который предоставит реализации, а также стандартный интерфейс. Полная банка составляет всего 96 КБ, поэтому она не должна разбивать банк ...

Стивен
источник
13
Нет, я бы не рекомендовал это - это не продвигает вас к действительно заменяемым часам; это заставляет вас использовать статику повсюду. Использование Joda Time, конечно, хорошая идея, но я бы все равно хотел использовать интерфейс Clock или аналогичный.
Джон Скит,
@Jon: Верно - для тестирования все нормально, но лучше интерфейс иметь.
Стивен
5
@Jon: Это плавный и IMHO идеальный и простой способ, если вы хотите протестировать зависящий от времени код, который уже использует JodaTime. Пытаться изобрести велосипед, добавив еще один слой / фреймворк абстракции, - не лучший вариант, если все уже было решено за вас с помощью JodaTime.
Stefan Haberl
2
@JonSkeet: Майк изначально не об этом просил. Ему нужен инструмент для тестирования кода, зависящего от времени, а не полноценные заменяемые часы в производственном коде. Я предпочитаю, чтобы моя архитектура была настолько сложной, насколько это необходимо, но максимально простой. Вводить дополнительный уровень абстракции (т.е. интерфейс) здесь просто не нужно
Стефан Хаберль
2
@StefanHaberl: Есть много вещей, которые не являются строго «необходимыми», но на самом деле я предлагаю сделать уже существующую зависимость явной и более простой для тестирования изолированно. Подделка системного времени с помощью статики имеет все обычные проблемы статики - это глобальное состояние без уважительной причины . Майк просил написать тестируемый код, который использовал бы текущую дату и время. Я по-прежнему считаю, что мое предложение намного чище (и делает зависимости более понятными), чем эффективная подделка статики.
Джон Скит
15

Хотя использование некоторого шаблона DateFactory кажется приятным, он не распространяется на библиотеки, которые вы не можете контролировать - представьте себе аннотацию Validation @Past с реализацией, основанной на System.currentTimeMillis (такая есть).

Вот почему мы используем jmockit, чтобы напрямую имитировать системное время:

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);

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

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}

Существует задокументированная проблема: с 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). Определенно не может обновиться до последней версии, где интерфейс полностью отличается. Я думал о том, чтобы встраивать только необходимое, но поскольку нам это не нужно в наших новых проектах, я вообще не занимаюсь этим.

virgo47
источник
1
Тот факт, что вы изменяете время для каждого класса, идет в обоих направлениях ... например, если вы имитируете nanoTime вместо currentTimeMillis, вам нужно быть очень осторожным, чтобы не нарушить java.util.concurrent. Как правило, если стороннюю библиотеку сложно использовать в тестах, вы не должны имитировать входные данные библиотеки: вы должны имитировать саму библиотеку.
Дэн Бериндей
1
Мы используем эту технику для тестов, где больше ничего не имитируем. Это интеграционный тест, и мы проверяем весь стек, делает ли он то, что должен, когда какая-то активность происходит в первый раз в году и т. Д. Хороший момент по поводу nanoTime, к счастью, нам не нужно высмеивать его, поскольку у него нет настенных часов смысл вообще. Я бы хотел увидеть Java с функциями сдвига времени, поскольку они мне уже были нужны более одного раза, и возиться с системным временем из-за этого очень не повезло.
virgo47
7

Powermock отлично работает. Просто использовал это, чтобы издеваться System.currentTimeMillis().

RENES
источник
Я попытался использовать Powermock, и если бы я правильно понял, он бы не стал издеваться над вызываемой стороной. Вместо этого он находит всех вызывающих и применяет имитацию. Это может занять очень много времени, так как вы должны позволить ему проверять КАЖДЫЙ класс в вашем пути к классам, если вы действительно хотите быть действительно уверены, что ваше приложение работает в сдвинутом времени. Поправьте меня, если я ошибаюсь насчет способа издевательства Powermock.
virgo47 02
1
@ virgo47 Нет, ты неправильно понял. PowerMock никогда не будет «проверять каждый класс в вашем пути к классам»; он будет проверять только те классы, которые вы специально указали ему для проверки (через @PrepareForTestаннотацию к тестовому классу).
Rogério
1
@ Rogério - это именно то, что я понимаю - я сказал: "Ты должен позволить этому ...". То есть, если вы ожидаете вызова в System.currentTimeMillisлюбом месте вашего пути к классам (любой библиотеке), вы должны проверить каждый класс. Я имел в виду именно это, не более того. Дело в том, что вы высмеиваете такое поведение на стороне вызывающего абонента («вы говорите конкретно»). Это нормально для простого теста, но не для тестов, где вы не можете быть уверены, что и откуда вызывает этот метод (например, более сложные тесты компонентов с задействованными библиотеками). Это вовсе не означает, что Powermock ошибочен, это просто означает, что вы не можете использовать его для этого типа теста.
virgo47 01
1
@ virgo47 Да, я понимаю, о чем вы. Я думал, вы имели в виду, что PowerMock придется автоматически проверять каждый класс в пути к классам, что, очевидно, было бы абсурдным. На практике, однако, для модульных тестов мы обычно знаем, какой класс (а) считывает время, поэтому указать его не проблема; действительно, для интеграционных тестов требование PowerMock о наличии всех указанных классов может быть проблемой.
Rogério
6

Используйте аспектно-ориентированное программирование (АОП, например AspectJ), чтобы сплести класс System так, чтобы он возвращал предопределенное значение, которое вы можете установить в своих тестовых примерах.

Или переплетите классы приложений, чтобы перенаправить вызов System.currentTimeMillis()или new Date()на другой собственный служебный класс.

java.lang.*Однако создание системных классов ( ) немного сложнее, и вам может потребоваться выполнить автономное переплетение для rt.jar и использовать отдельный JDK / rt.jar для ваших тестов.

Это называется двоичным переплетением, и есть также специальные инструменты для переплетения системных классов и обхода некоторых проблем с ним (например, загрузка виртуальной машины может не работать)

mhaller
источник
Это, конечно, работает, но это намного проще сделать с помощью инструмента mocking, чем с помощью инструмента AOP; последний вид инструментов слишком универсален для рассматриваемой цели.
Rogério
3

На самом деле нет способа сделать это непосредственно в виртуальной машине, но вы могли бы что-нибудь, чтобы программно установить системное время на тестовой машине. В большинстве (всех?) ОС для этого есть команды командной строки.

Джереми Рэймонд
источник
Например, на окнах dateи timeкоманд. В Linux dateкоманда.
Джереми Рэймонд,
Почему не должно быть возможности сделать это с помощью АОП или инструментализации (было ли это сделано)?
jarnbjo 05
3

Рабочий способ переопределения текущего системного времени для целей тестирования JUnit в веб-приложении Java 8 с EasyMock, без Joda Time и без PowerMock.

Вот что вам нужно сделать:

Что нужно сделать в тестируемом классе

Шаг 1

Добавьте новый java.time.Clockатрибут к тестируемому классу MyServiceи убедитесь, что новый атрибут будет правильно инициализирован со значениями по умолчанию с помощью блока создания экземпляра или конструктора:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { 
    initDefaultClock(); // initialisation in an instantiation block, but 
                        // it can be done in a constructor just as well
  }
  // (...)
}

Шаг 2

Вставьте новый атрибут clockв метод, который вызывает текущую дату и время. Например, в моем случае мне пришлось выполнить проверку того, произошла ли ранее дата, хранящаяся в базе данных LocalDateTime.now(), которую я заменил LocalDateTime.now(clock), например:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

Что нужно сделать в тестовом классе

Шаг 3

В тестовом классе создайте объект фиктивных часов и вставьте его в экземпляр тестируемого класса непосредственно перед вызовом тестируемого метода doExecute(), а затем сбросьте его обратно сразу после этого, например:

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot

    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method

    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

Проверьте его в режиме отладки, и вы увидите, что дата 3 февраля 2017 года была правильно введена в myServiceэкземпляр и использована в инструкции сравнения, а затем была правильно сброшена на текущую дату с помощью initDefaultClock().

KiriSakow
источник
2

На мой взгляд, может работать только неинвазивное решение. Особенно, если у вас есть внешние библиотеки и большая база устаревшего кода, нет надежного способа имитировать время.

JMockit ... работает только ограниченное количество раз

PowerMock & Co ... необходимо имитировать клиентов для System.currentTimeMillis (). Опять инвазивный вариант.

Из этого я вижу, что упомянутый подход javaagent или aop прозрачен для всей системы. Кто-нибудь сделал это и мог указать на такое решение?

@jarnbjo: не могли бы вы показать код javaagent, пожалуйста?

user1477398
источник
3
Решение jmockit, работающее неограниченное количество раз с небольшим -XX: -Inline hack (на Sun HotSpot): virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Предоставляется полный удобный класс SystemTimeShifter в посте. Класс можно использовать в ваших тестах, или его можно очень легко использовать в качестве первого основного класса перед вашим настоящим основным классом, чтобы запустить ваше приложение (или даже весь сервер приложений) в другое время. Конечно, это предназначено в основном для целей тестирования, а не для производственной среды.
virgo47 02
2

Если вы используете Linux, вы можете использовать основную ветку libfaketime или во время тестирования совершить 4ce2835 .

Просто установите переменную окружения, указав время, которое вы хотите использовать для имитации своего Java-приложения, и запустите его с помощью ld-preloading:

# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar

Вторая переменная среды имеет первостепенное значение для приложений Java, которые в противном случае зависли бы. На момент написания ему требуется главная ветка libfaketime.

Если вы хотите изменить время управляемой службы systemd, просто добавьте следующее в свои переопределения файла модуля, например, для elasticsearch это будет /etc/systemd/system/elasticsearch.service.d/override.conf:

[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"

Не забудьте перезагрузить systemd с помощью `systemctl daemon-reload

Faxmodem
источник
-1

Если вы хотите имитировать метод, имеющий System.currentTimeMillis()аргумент, вы можете передать anyLong()класс Matchers в качестве аргумента.

PS Я могу успешно запустить свой тестовый пример, используя вышеуказанный трюк, и просто поделиться более подробной информацией о моем тесте, что я использую фреймворки PowerMock и Mockito.

Пареш Майани
источник