Überschreiben der Systemzeit zum Testen in Java

Überschreiben der Systemzeit zum Testen in Java

1. Überblick

In diesem kurzen Tutorial konzentrieren wir uns aufdifferent ways to override the system time for testing.

Manchmal gibt es eine Logik um das aktuelle Datum in unserem Code. Möglicherweise rufen einige Funktionen wienew Date() oderCalendar.getInstance() auf, die schließlichSystem.CurrentTimeMillis aufrufen werden.

Eine Einführung in die Verwendung vonJava Clock finden Sie unterto this article here. Oder zur Verwendung von AspectJhere.

2. Verwenden der Uhr injava.time

Das Paketjava.time inJava 8 includes an abstract class java.time.Clock mit dem Ziel, bei Bedarf alternative Uhren anzuschließen. Damit können wir unsere eigene Implementierung einbinden oder eine finden, die bereits auf unsere Bedürfnisse zugeschnitten ist.

Um unsere Ziele zu erreichen,the above library includes static methods to yield special implementations. Wir werden zwei davon verwenden, die eine unveränderliche, threadsichere und serialisierbare Implementierung zurückgeben.

Der erste istfixed. Daraus ergibt sich, dasswe can obtain a Clock that always returns the same*Instant*, erkennt, dass die Tests nicht von der aktuellen Uhr abhängen.

Um es zu benutzen, brauchen wir einInstant und einZoneOffset:

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

The second static method is offset. In diesem Beispiel umschließt eine Uhr eine andere Uhr, die sie zum zurückgegebenen Objekt macht und in der Lage ist, Zeitpunkte zu ermitteln, die um die angegebene Dauer später oder früher liegen.

Mit anderen Worten,it’s possible to simulate running in the future, in the past, or in any arbitrary point in time:

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);

Mit der KlasseDurationist es möglich, von Nanosekunden bis zu Tagen zu manipulieren. Wir können auch eine Dauer negieren, was bedeutet, dass wir eine Kopie dieser Dauer mit der negierten Länge erhalten.

3. Verwenden der aspektorientierten Programmierung

Eine andere Möglichkeit, die Systemzeit zu überschreiben, ist AOP. Bei diesem Ansatz werdenwe’re able to weave the System class to return a predefined value which we can set within our test cases.

Es ist auch möglich, die Anwendungsklassen zu verweben, um den Aufruf anSystem.currentTimeMillis() oder annew Date() an eine andere eigene Dienstprogrammklasse umzuleiten.

Eine Möglichkeit, dies zu implementieren, ist die Verwendung von AspectJ:

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

Im obigen Beispielwe’re catching every call to System.currentTimeMillis() inside a specified package, in diesem Falluser.code.base.pckg., * und jedes Mal, wenn dieses Ereignis eintritt, wird Null zurückgegeben.

Hier können wir unsere eigene Implementierung deklarieren, um die gewünschte Zeit in Millisekunden zu erhalten.

Ein Vorteil der Verwendung von AspectJ besteht darin, dass es direkt auf Bytecode-Ebene ausgeführt wird, sodass der ursprüngliche Quellcode nicht benötigt wird.

Aus diesem Grund müssten wir es nicht neu kompilieren.

4. Verspotten derInstant.now()-Methode

Wir können die KlasseInstantverwenden, um einen Momentanpunkt auf der Zeitachse darzustellen. Normalerweise können wir damit Ereignis-Zeitstempel in unserer Anwendung aufzeichnen. Dienow()-Methode dieser Klasse ermöglicht es uns, den aktuellen Zeitpunkt von der Systemuhr in der UTC-Zeitzone abzurufen.

Sehen wir uns einige Alternativen an, um das Verhalten beim Testen zu ändern.

4.1. Überladen vonnow() mitClock

Wir können dienow()-Methode mit einer festenClock-Instanz überladen. Many of the classes in the java.time package have a now() method that takes a Clock parameter, was dies zu unserem bevorzugten Ansatz macht:

@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. Verwenden vonPowerMock

Wenn wir das Verhalten dernow()-Methode ändern müssen, ohne Parameter zu senden, können wir außerdemPowerMock verwenden:

@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. Verwenden von JMockit

Alternativ können wir die BibliothekJMockitverwenden.

JMockit bietet uns zwei Möglichkeiten vonmocking a static method. Einer verwendet die KlasseMockUp:

@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);
}

Und der andere verwendet die KlasseExpectations:

@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. Verspotten derLocalDateTime.now()-Methode

Eine weitere nützliche Klasse im Paketjava.timeist die KlasseLocalDateTime. Diese Klasse repräsentiert eine Datums- und Uhrzeitangabe ohne Zeitzone im ISO-8601-Kalendersystem. Mit dernow()-Methode dieser Klasse können wir die aktuelle Datums- und Uhrzeitangabe von der Systemuhr in der Standardzeitzone abrufen.

Wir können die gleichen Alternativen verwenden, um es zu verspotten, wie wir es zuvor gesehen haben. Beispiel: Überladen vonnow() mit einem festenClock:

@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. Fazit

In diesem Artikel haben wir verschiedene Möglichkeiten untersucht, um die Systemzeit für das Testen zu überschreiben. Zuerst haben wir uns das native Paketjava.time und dessenClock-Klasse angesehen. Als nächstes haben wir gesehen, wie ein Aspekt angewendet wird, um dieSystem-Klasse zu weben. Schließlich sahen wir verschiedene Alternativen zum Verspotten dernow()-Methode für die KlassenInstant undLocalDateTime.

Wie immer finden sich Codebeispiele inover on GitHub.