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

Переопределение системного времени для тестирования на Java

Изучите различные способы переопределения системного времени для тестирования.

Автор оригинала: baeldung.

1. Обзор

В этом кратком руководстве мы сосредоточимся на различных способах переопределения системного времени для тестирования .

Иногда в нашем коде есть логика вокруг текущей даты. Возможно , некоторые вызовы функций, такие как new Date() или Calendar.getInstance () , которые в конечном итоге вызовут System.currentTimeMillis .

Для ознакомления с использованием Java Clock , пожалуйста, обратитесь к этой статье здесь . Или, к использованию AspectJ, здесь .

2. Использование часов в java.time

Пакет java.time в Java 8 включает абстрактный класс java.time.Часы с целью включения альтернативных часов по мере необходимости. С помощью этого мы можем подключить вашу собственную реализацию или найти ту, которая уже создана для удовлетворения наших потребностей.

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

Первый из них исправлен . Из него мы можем получить Часы , которые всегда возвращают одно и то же Instant , гарантирует, что тесты не зависят от текущих часов.

Чтобы использовать его, нам нужны Мгновенный и Смещение зоны :

Instant.now(Clock.fixed( 
  Instant.parse("2018-08-22T10:00:00Z"),
  ZoneOffset.UTC))

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

Другими словами, можно имитировать работу в будущем, в прошлом или в любой произвольный момент времени :

Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault());

// go to the future:
Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10));
        
// rewind back with a negative value:
clock = Clock.offset(constantClock, Duration.ofSeconds(-5));
 
// the 0 duration returns to the same clock:
clock = Clock.offset(constClock, Duration.ZERO);

С помощью класса Duration можно манипулировать от наносекунд до дней. Кроме того, мы можем отрицать длительность, что означает получение копии этой длительности с отрицаемой длиной.

3. Использование Аспектно-Ориентированного Программирования

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

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

Одним из способов реализации этого является использование AspectJ:

public aspect ChangeCallsToCurrentTimeInMillisMethod {
    long around(): 
      call(public static native long java.lang.System.currentTimeMillis()) 
        && within(user.code.base.pckg.*) {
          return 0;
      }
}

В приведенном выше примере мы перехватываем каждый вызов System.currentTimeMillis () внутри указанного пакета , который в данном случае является user.code.base.pckg.* , и возвращаем ноль каждый раз, когда происходит это событие .

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

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

По этой причине нам не нужно было бы перекомпилировать его.

4. Издевательство над методом Instant.now()

Мы можем использовать класс Instant для представления мгновенной точки на временной шкале. Обычно мы можем использовать его для записи временных меток событий в нашем приложении. Метод known() этого класса позволяет нам получить текущий момент времени из системных часов в часовом поясе UTC.

Давайте рассмотрим некоторые альтернативы для изменения его поведения при тестировании.

4.1. Перегрузка сейчас() С часами

Мы можем перегрузить метод now() фиксированным экземпляром Clock . Многие классы в пакете java.time имеют метод new () , который принимает параметр Clock |, что делает этот подход предпочтительным:

@Test
public void givenFixedClock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-22T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));

    Instant instant = Instant.now(clock);

    assertThat(instant.toString()).isEqualTo(instantExpected);
}

4.2. Использование PowerMock

Кроме того, если нам нужно изменить поведение метода now() без отправки параметров, мы можем использовать PowerMock :

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Instant.class })
public class InstantUnitTest {
    @Test
    public void givenInstantMock_whenNow_thenGetFixedInstant() {
        String instantExpected = "2014-12-22T10:15:30Z";
        Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
        Instant instant = Instant.now(clock);
        mockStatic(Instant.class);
        when(Instant.now()).thenReturn(instant);

        Instant now = Instant.now();

        assertThat(now.toString()).isEqualTo(instantExpected);
    }
}

4.3. Использование JMockit

В качестве альтернативы мы можем использовать библиотеку JMockit.

JMockit предлагает нам два способа издеваться над статическим методом . Один из них использует класс MockUp :

@Test
public void givenInstantWithJMock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-21T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
    new MockUp() {
        @Mock
        public Instant now() {
            return Instant.now(clock);
        }
    };

    Instant now = Instant.now();

    assertThat(now.toString()).isEqualTo(instantExpected);
}

А другой использует класс Ожиданий:

@Test
public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC"));
    Instant instantExpected = Instant.now(clock);
    new Expectations(Instant.class) {
        {
            Instant.now();
            result = instantExpected;
        }
    };

    Instant now = Instant.now();

    assertThat(now).isEqualTo(instantExpected);
}

5. Издевательство над методом LocalDateTime.now()

Другим полезным классом в пакете java.time является класс LocalDateTime . Этот класс представляет дату-время без часового пояса в календарной системе ISO-8601. Метод now() этого класса позволяет нам получить текущую дату-время из системных часов в часовом поясе по умолчанию.

Мы можем использовать те же альтернативы, чтобы издеваться над ним, как мы видели раньше. Например, перегрузка now() с фиксированными часами :

@Test
public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
    String dateTimeExpected = "2014-12-22T10:15:30";

    LocalDateTime dateTime = LocalDateTime.now(clock);

    assertThat(dateTime).isEqualTo(dateTimeExpected);
}

6. Заключение

В этой статье мы рассмотрели различные способы переопределения системного времени для тестирования. Сначала мы рассмотрели собственный пакет java.time и его класс Clock . Далее мы рассмотрели, как применить аспект для создания класса System . Наконец, мы увидели различные альтернативы издевательству над методом now() в классах Instant и LocalDateTime .