Рубрики
Без рубрики

Машина времени для Java

В этой статье описывается инструмент, разработанный для поддержки модульного тестирования в зависимости от времени… С тегами java, тестирование, devexpress, машина времени.

В этой статье описывается инструмент, разработанный для поддержки модульного тестирования зависящей от времени логики в приложениях Java. Инструмент помогает контролировать качество торговых платформ и других сложных и/или параллельных систем.

Не всегда просто писать модульные тесты для функций, зависящих от времени. В простых ситуациях будет работать простая замена метода, возвращающего текущее время, на пользовательскую реализацию [1] [2].

Вот простой метод подсчета количества дней до конца света:

fun daysBeforeDoom() {
    return doomTime - System.currentTimeMillis()) / millisInDay
}

Замена всех вызовов System.currentTimeMillis() любым существующим инструментом [1] [2] или написав системный преобразователь кода с использованием платформы ASM [3] или АспектJ [4] часто будет достаточно.

Есть и другие случаи, когда такого подхода недостаточно. Представьте, что мы хотим, чтобы наш код просыпался каждый день и отображал “N дней, оставшихся до конца света”, как в следующем фрагменте кода.

while (true) {
    Thread.sleep(ONE_DAY)
    println("${daysBeforeDoom()} days left till the doomsday")
}

Как мы можем протестировать этот код? Как мы можем утверждать, что он вызывается каждый день и что он отображает правильное сообщение? Используя простой подход, описанный выше, и заменив System.currentTimeMillis() позволяет нам только проверить правильность сообщения. Нам нужно было бы подождать целый день, чтобы проверить промежуток времени.

Протестировать такой код без дополнительных инструментов практически невозможно. Давайте попробуем его создать.

В настоящее время у нас есть два метода, которые возвращают текущее время: System.currentTimeMillis() и System.nanoTime() . У нас также есть несколько зависящих от времени методов, которые ожидают события или могут подождать: Thread.sleep() , Object.wait() и LockSupport.park() .

Мы хотим создать новый метод |/увеличить время() , которое может изменить текущее время и разбудить любые ожидающие потоки с таймаутом.

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

Тестовый пример:

increaseTime(ONE_DAY)
checkMessage()

Это создает потенциальное условие гонки между нашим сообщением о проверке и фактическим временем, необходимым для завершения операции печати. Конечно, один из подходов состоит в том, чтобы добавить паузу:

Отремонтированный тест:

increaseTime(ONE_DAY)
Thread.sleep(500 /*ms*/)
checkMessage()

В обычной практике этот тест почти всегда будет работать, но не гарантируется, что проверка сообщения() будет вызвана после печати сообщения. Это может произойти из-за сложности логики тестирования или просто из-за выполнения кода на перегруженном сервере. У вас может возникнуть соблазн увеличить время ожидания, но это делает тесты медленнее и по-прежнему не дает гарантии правильности.

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

Тест, который мы хотели бы написать:

increaseTime(ONE_DAY)
waitUntilThreadsAreFrozen(1_000/*ms, timeout*/)
checkMessage()

Неотъемлемая проблема заключается в том, что мы хотим поддерживать все зависящие от времени методы во время тестирования, но мы также хотим, чтобы waitUntilThreadsAreFrozen() метод, и поддерживать и то, и другое непросто.

Наше решение реализовано в специальном инструменте Devexperts для тестирования зависящей от времени логики под названием time-test [5].

Давайте посмотрим, как это работает.

Проверка времени реализована как агент Java. Чтобы использовать его, вы должны добавить javaagent:timetest.jar и включите его в путь к классу. Инструмент преобразует байт-код и заменяет все зависящие от времени вызовы методов нашими конкретными реализациями. Однако написать хороший java-агент иногда бывает непросто, поэтому мы разработали JAgent framework [6] для упрощения разработки java-агентов.

При создании тестов, зависящих от времени, вы должны включить поставщика времени тестирования. Он реализует все необходимые зависящие от времени методы ( System.currentTimeMillis() , Он реализует все необходимые зависящие от времени методы ( System.currentTimeMillis() , () , Объект.подождите() , LockSupport.park() , ...) и переопределяет их реализации по умолчанию. В большинстве тестов вам не нужно фактически управлять базовым временем, поэтому инструмент внутренне продолжает использовать зависящие от времени методы по умолчанию, заключенные в перегруженный метод. После запуска Поставщика времени тестирования вы можете использовать Поставщик времени тестирования.установить время() , Поставщик времени тестирования.увеличить время()

После запуска Поставщика времени тестирования вы можете использовать ||Поставщик времени тестирования.установить время()||, ||Поставщик времени тестирования.увеличить время()|| и || TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen()|| методы.

long timeMillis();
long nanoTime();
void sleep(long millis) throws InterruptedException;
void sleep(long millis, int nanos) throws InterruptedException;
void waitOn(Object monitor, long millis) throws InterruptedException;
void waitOn(Object monitor, long millis, int nanos) throws InterruptedException;
void notifyAll(Object monitor);
void notify(Object monitor);
void park(boolean isAbsolute, long time);
void unpark(Object thread);

После запуска Поставщика времени тестирования вы можете использовать Поставщик времени тестирования.установить время() , Поставщик времени тестирования.увеличить время() и

После запуска Поставщика времени тестирования вы можете использовать ||Поставщик времени тестирования.установить время()||, ||Поставщик времени тестирования.увеличить время()|| и || TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen()|| методы. Как описано ранее, основная задача реализации testtimeprovider заключается в поддержке как зависящего от времени, так и зависящего от времени, при каждом изменении все необходимые потоки помечаются как повторные в одно и то же время, ||waitUntilThreadsAreFrozen()||ожидает, пока все потоки не перейдут в состояние ожидания, и ни один из них не будет С этим подходом потоки проснутся, сбросят свою возобновленную метку, выполнят свою задачу, а затем вернутся в состояние ожидания перед тестированием с помощью testtimeprovider: e waitUntilThreadsAreFrozen распознает его как завершенный. помечено как возобновленное. умед и только потом просыпаются. метод nt вдоль дополнительного метода ||waitUntilThreadsAreFrozen()||.

@Before
public void setup() {
    // Use TestTimeProvider for this test
    TestTimeProvider.start(/* initial time could be passed here */);
}

@After
public void reset() {
    // Reset time provider to default after the test execution
    TestTimeProvider.reset();
}

@Test
public void test() {
    runMyConcurrentApplication();
    TestTimeProvider.increaseTime(60_000 /*ms*/);
    TestTimeProvider.waitUntilThreadsAreFrozen(1_000 /*ms*/);
    checkMyApplicationState();
}

После запуска Поставщика времени тестирования вы можете использовать ||Поставщик времени тестирования.установить время()||, ||Поставщик времени тестирования.увеличить время()|| и || TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen()|| методы. Как было описано ранее, основная задача реализации testtimeprovider заключается в поддержке как зависящего от времени, так и при каждом изменении все необходимые потоки помечаются как повторные в одно и то же время, ||waitUntilThreadsAreFrozen()||ожидает, пока все потоки не перейдут в состояние ожидания, и ни один из них При таком подходе потоки проснутся, сбросят свою метку возобновления, выполнят свою задачу, а затем вернутся в состояние ожидания, прежде чем мы надеемся, что инструмент проверки времени облегчит вашу жизнь. Тест с поставщиком времени тестирования: e подождите, пока потоки не будут заморожены, чтобы признать его завершенным. помечено как возобновленное. умед и только потом просыпаются. метод nt вдоль дополнительного метода ||waitUntilThreadsAreFrozen()||. После запуска Поставщика времени тестирования вы можете использовать ||Поставщик времени тестирования.установить время()||, ||Поставщик времени тестирования.увеличить время()|| и || TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen()|| методы. Как было описано ранее, основная задача реализации testtimeprovider заключается в поддержке как зависящего от времени, так и зависящего от времени, при каждом изменении все необходимые потоки помечаются как повторные в одно и то же время, ||waitUntilThreadsAreFrozen()||ожидает, пока все потоки не перейдут в состояние ожидания, и ни один из них При таком подходе потоки проснутся, сбросят свою отметку возобновления, выполнят свою задачу, а затем вернутся в состояние ожидания, прежде чем инструмент станет открытым и доступным на GitHub[5]. Мы надеемся, что инструмент проверки времени облегчит вашу жизнь. Тест с поставщиком времени тестирования: e подождите, пока потоки не будут заморожены, чтобы признать его завершенным. помечено как возобновленное. умед и только потом просыпаются. метод nt вдоль дополнительного метода ||waitUntilThreadsAreFrozen()||.

После запуска Поставщика времени тестирования вы можете использовать Поставщик времени тестирования.установить время() , Поставщик времени тестирования.увеличить время() и TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen() методы. Как было описано ранее, основная задача реализации testtimeprovider заключается в поддержке как зависящего от времени, так и зависящего от времени, при каждом изменении все необходимые потоки помечаются как повторные в одно и то же время, waitUntilThreadsAreFrozen() ожидает, пока все потоки не перейдут в состояние ожидания, и ни один из них При таком подходе потоки проснутся, сбросят свою отметку возобновления, выполнят свою задачу, а затем вернутся в состояние ожидания перед успешным тестированием! Инструмент с открытым исходным кодом и доступен на GitHub [5]. Мы надеемся, что инструмент проверки времени облегчит вашу жизнь. Тест с поставщиком времени тестирования: e подождите, пока потоки не будут заморожены, чтобы признать его завершенным. помечено как возобновленное. умед и только потом просыпаются. метод nt вдоль дополнительного метода waitUntilThreadsAreFrozen() . После запуска Поставщика времени тестирования вы можете использовать Поставщик времени тестирования.установить время() , Поставщик времени тестирования.увеличить время() и

После запуска TestTimeProvider вы можете использовать ||TestTimeProvider.setTime()||, ||TestTimeProvider.increasetime()|| и|| TestTimeProvider.waitUntilThreTimeProvider.java: adsAreFrozen()|| методы. Как описано ранее, основная задача реализации testtimeprovider заключается в поддержке как зависящего от времени, так и зависящего от времени при каждом изменении все необходимые потоки помечаются как повторные в одно и то же время, ||waitUntilThreadsAreFrozen()||ожидает, пока все потоки не перейдут в состояние ожидания, и ни один из них При таком подходе потоки не проснутся, сбросят свою возобновленную метку, выполните их задачу, а затем вернитесь в состояние ожидания перед Никитой Ковалем, инженером-исследователем по проблемам параллелизма и анализа кода в Devexperts https://github.com/Devexperts/jagent https://github.com/Devexperts/time-test https://www.eclipse.org/aspectj/|| http://asm.ow2.org/[2]|| https://stackoverflow.com/questions/2001671/override-java-system-currenttimemillis-for-testing-time-sensitive-code https://github.com/TOPdesk/time-transformer-agent/|||/Ссылки:|||| Счастливого тестирования! Инструмент с открытым исходным кодом и доступен на GitHub [5]. Мы надеемся, что инструмент проверки времени облегчит вашу жизнь. Тест с помощью testtimeprovider: e waitUntilThreadsAreFrozen распознает его как завершенный. помечено как возобновленное. умед и только потом просыпаются. метод nt вдоль дополнительного метода ||waitUntilThreadsAreFrozen()||.

Оригинал: “https://dev.to/devexperts/time-machine-for-java-249a”