Überschreiben der Systemzeit zum Testen in Java

1. Übersicht

In diesem kurzen Tutorial konzentrieren wir uns auf verschiedene Möglichkeiten, um die Systemzeit zum Testen zu überschreiben .

Manchmal gibt es eine Logik um das aktuelle Datum in unserem Code. Möglicherweise einige Funktionsaufrufe wie new Date () oder Calendar.getInstance () , die schließlich System.CurrentTimeMillis aufrufen werden .

Eine Einführung in die Verwendung von Java Clock finden Sie in diesem Artikel hier. Oder zur Verwendung von AspectJ hier.

2. Verwenden von Clock in java.time

Das Paket java.time in Java 8 enthält eine abstrakte Klasse java.time.Clock, mit der alternative Uhren nach Bedarf angeschlossen werden können. Damit können wir unsere eigene Implementierung anschließen oder eine finden, die bereits für unsere Anforderungen entwickelt wurde.

Um unsere Ziele zu erreichen, enthält die obige Bibliothek statische Methoden, um spezielle Implementierungen zu erhalten . Wir werden zwei davon verwenden, die eine unveränderliche, threadsichere und serialisierbare Implementierung zurückgeben.

Der erste ist behoben . Daraus können wir eine Uhr erhalten , die immer den gleichen Zeitpunkt zurückgibt , um sicherzustellen, dass die Tests nicht von der aktuellen Uhr abhängig sind.

Um es zu benutzen, benötigen wir ein Instant und ein ZoneOffset :

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

Die zweite statische Methode ist der Offset . In diesem Fall umschließt eine Uhr eine andere Uhr, die sie zum zurückgegebenen Objekt macht, das Momente abrufen kann, die später oder früher um die angegebene Dauer liegen.

Mit anderen Worten, es ist möglich, das Laufen in der Zukunft, in der Vergangenheit oder zu einem beliebigen Zeitpunkt zu simulieren :

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 Duration- Klasse ist 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. Mit diesem Ansatz sind wir in der Lage das zu weben System - Klasse einen vordefinierten Wert zurückzukehren , die wir in unserem Testfall einstellen .

Es ist auch möglich, die Anwendungsklassen zu verweben , um den Aufruf von System.currentTimeMillis () oder new 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 Beispiel erfassen wir jeden Aufruf von System.currentTimeMillis () in einem angegebenen Paket , in diesem Fall user.code.base.pckg. * , Und geben jedes Mal, wenn dieses Ereignis eintritt , Null zurück .

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, um zu funktionieren.

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

4. verspottende die Instant.now () Methode

Wir können die Instant- Klasse verwenden, um einen Momentanpunkt auf der Zeitachse darzustellen. Normalerweise können wir damit Ereigniszeitstempel in unserer Anwendung aufzeichnen. Mit der now () -Methode dieser Klasse können wir den aktuellen Zeitpunkt von der Systemuhr in der UTC-Zeitzone abrufen.

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

4.1. Jetzt überladen () Mit einer Uhr

Wir können die now () -Methode mit einer festen Clock- Instanz überladen . Viele der Klassen im Paket java.time verfügen über eine now () -Methode, die einen Clock- Parameter verwendet . Dies macht dies zu unserem bevorzugten Ansatz:

@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 von PowerMock

Wenn wir das Verhalten der now () -Methode ändern müssen, ohne Parameter zu senden, können wir außerdem PowerMock 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 JMockit- Bibliothek verwenden.

JMockit bietet uns zwei Möglichkeiten, eine statische Methode zu verspotten. Einer verwendet die MockUp- Klasse:

@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 Expectations- Klasse:

@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. verspottende die LocalDateTime.now () Methode

Eine weitere nützliche Klasse im Paket java.time ist die LocalDateTime- Klasse. Diese Klasse repräsentiert eine Datums- und Uhrzeit ohne Zeitzone im ISO-8601-Kalendersystem. Mit der now () -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. Zum Beispiel jetzt () mit einer festen Uhr überladen :

@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 zum Testen zu überschreiben. Zuerst haben wir uns das native Paket java.time und seine Clock- Klasse angesehen. Als nächstes sehen wir , wie ein Aspekt anzuwenden , um das zu weben System - Klasse. Schließlich sahen wir verschiedene Alternativen zu der spöttischen now () Methode auf Sofort und Local Klassen.

Codebeispiele finden Sie wie immer auf GitHub.