Gibt es eine Möglichkeit, entweder im Code oder mit JVM-Argumenten die aktuelle Zeit, wie sie über angezeigt wird, zu überschreiben, außer die System.currentTimeMillis
Systemuhr auf dem Host-Computer manuell zu ändern?
Ein kleiner Hintergrund:
Wir haben ein System, das eine Reihe von Buchhaltungsjobs ausführt, die einen Großteil ihrer Logik um das aktuelle Datum drehen (z. B. 1. des Monats, 1. des Jahres usw.).
Leider ruft ein Großteil des Legacy-Codes Funktionen wie new Date()
oder auf Calendar.getInstance()
, die beide schließlich aufrufen System.currentTimeMillis
.
Zu Testzwecken müssen wir derzeit die Systemuhr manuell aktualisieren, um zu ändern, zu welcher Uhrzeit und zu welchem Datum der Code glaubt, dass der Test ausgeführt wird.
Meine Frage lautet also:
Gibt es eine Möglichkeit, die Rückgabe zu überschreiben System.currentTimeMillis
? Um die JVM beispielsweise anzuweisen, einen Offset automatisch zu addieren oder zu subtrahieren, bevor Sie von dieser Methode zurückkehren?
Danke im Voraus!
quelle
Antworten:
Ich empfehle dringend , dass Sie, anstatt mit der Systemuhr herumzuspielen, in die Kugel beißen und den alten Code umgestalten, um eine austauschbare Uhr zu verwenden. Idealerweise sollte dies mit der Abhängigkeitsinjektion erfolgen, aber selbst wenn Sie einen austauschbaren Singleton verwenden, erhalten Sie Testbarkeit.
Dies könnte durch Suchen und Ersetzen für die Singleton-Version fast automatisiert werden:
Calendar.getInstance()
durchClock.getInstance().getCalendarInstance()
.new Date()
durchClock.getInstance().newDate()
System.currentTimeMillis()
durchClock.getInstance().currentTimeMillis()
(usw. nach Bedarf)
Sobald Sie diesen ersten Schritt getan haben, können Sie den Singleton nacheinander durch DI ersetzen.
quelle
java.time.Clock
Klasse, "mit der alternative Uhren nach Bedarf angeschlossen werden können".static
Methoden ist sicher, dass es sich nicht um OO handelt, aber das Einfügen einer zustandslosen Abhängigkeit, deren Instanz in keinem Instanzstatus ausgeführt wird, ist nicht wirklich besser. Es ist nur eine ausgefallene Art, das ohnehin "statische" Verhalten zu verschleiern.tl; dr
Ja.
Clock
In java.timeWir haben eine neue Lösung für das Problem des Austauschs einer steckbaren Uhr, um das Testen mit falschen Datums- / Uhrzeitwerten zu erleichtern . Das Paket java.time in Java 8 enthält eine abstrakte Klasse
java.time.Clock
mit einem expliziten Zweck:Sie könnten Ihre eigene Implementierung von anschließen
Clock
, obwohl Sie wahrscheinlich eine finden können, die bereits für Ihre Anforderungen erstellt wurde. Für Ihre Bequemlichkeit enthält java.time statische Methoden, um spezielle Implementierungen zu erhalten. Diese alternativen Implementierungen können beim Testen hilfreich sein.Veränderte Trittfrequenz
Die verschiedenen
tick…
Methoden erzeugen Uhren, die das aktuelle Moment mit einer anderen Trittfrequenz erhöhen.Die Standardeinstellung gibt
Clock
eine Zeit an, die in Java 8 so häufig wie Millisekunden und in Java 9 so fein wie Nanosekunden aktualisiert wird (abhängig von Ihrer Hardware). Sie können verlangen, dass der wahre aktuelle Moment mit einer anderen Granularität gemeldet wird.tickSeconds
- Inkremente in ganzen SekundentickMinutes
- Inkremente in ganzen Minutentick
- Inkremente durch das übergebeneDuration
Argument.Falsche Uhren
Einige Uhren können lügen und ein anderes Ergebnis als die Hardware-Uhr des Host-Betriebssystems erzeugen.
fixed
- Meldet einen einzelnen unveränderlichen (nicht inkrementierenden) Moment als aktuellen Moment.offset
- Meldet den aktuellen Moment, wird jedoch durch das übergebeneDuration
Argument verschoben .Schließen Sie zum Beispiel den ersten Moment des frühesten Weihnachtsfestes in diesem Jahr ein. Mit anderen Worten, wenn der Weihnachtsmann und sein Rentier ihren ersten Halt machen . Die früheste Zeitzone scheint heute zu sein ,
Pacific/Kiritimati
an+14:00
.Verwenden Sie diese spezielle feste Uhr, um immer den gleichen Moment zurückzugeben. Wir haben den ersten Moment des Weihnachtstages in Kiritimati , wobei UTC eine Wanduhrzeit von vierzehn Stunden früher anzeigt, 10 Uhr am vorherigen Datum des 24. Dezember.
Siehe Live-Code in IdeOne.com .
Wahre Zeit, andere Zeitzone
Sie können steuern, welche Zeitzone von der
Clock
Implementierung zugewiesen wird . Dies kann bei einigen Tests hilfreich sein. Ich empfehle dies jedoch nicht im Produktionscode, wo Sie immer explizit die optionalenZoneId
oderZoneOffset
Argumente angeben sollten .Sie können festlegen, dass UTC die Standardzone ist.
Sie können eine bestimmte Zeitzone angeben. Geben Sie einen richtigen Zeitzonennamen im Format
continent/region
, wie zum BeispielAmerica/Montreal
,Africa/Casablanca
oderPacific/Auckland
. Verwenden Sie niemals die Abkürzung für 3-4 Buchstaben, wieEST
oder,IST
da es sich nicht um echte Zeitzonen handelt, die nicht standardisiert und nicht einmal eindeutig (!) Sind.Sie können angeben, dass die aktuelle Standardzeitzone der JVM die Standardzeitzone für ein bestimmtes
Clock
Objekt sein soll.Führen Sie diesen Code zum Vergleichen aus. Beachten Sie, dass alle denselben Moment und denselben Punkt auf der Zeitachse melden. Sie unterscheiden sich nur in der Wanduhrzeit ; Mit anderen Worten, drei Möglichkeiten, dasselbe zu sagen, drei Möglichkeiten, denselben Moment anzuzeigen.
America/Los_Angeles
war die aktuelle JVM-Standardzone auf dem Computer, auf dem dieser Code ausgeführt wurde.Die
Instant
Klasse ist per Definition immer in UTC. Diese dreiClock
zonenbezogenen Verwendungen haben also genau den gleichen Effekt.Standarduhr
Die standardmäßig für verwendete Implementierung
Instant.now
ist die vonClock.systemUTC()
. Dies ist die Implementierung, die verwendet wird, wenn Sie a nicht angebenClock
. Überzeugen Sie sich selbst im Java 9-Quellcode vor der Veröffentlichung fürInstant.now
.Der Standardwert
Clock
fürOffsetDateTime.now
undZonedDateTime.now
istClock.systemDefaultZone()
. Siehe Quellcode .Das Verhalten der Standardimplementierungen hat sich zwischen Java 8 und Java 9 geändert. In Java 8 wird der aktuelle Moment mit einer Auflösung nur in Millisekunden erfasst, obwohl die Klassen eine Auflösung von Nanosekunden speichern können . Java 9 bietet eine neue Implementierung, mit der der aktuelle Moment mit einer Auflösung von Nanosekunden erfasst werden kann - natürlich abhängig von der Leistungsfähigkeit Ihrer Computerhardware-Uhr.
Über java.time
Das java.time- Framework ist in Java 8 und höher integriert. Diese Klassen verdrängen die lästigen alten Legacy - Datum-Zeit - Klassen wie
java.util.Date
,Calendar
, &SimpleDateFormat
.Das Joda-Time- Projekt, das sich jetzt im Wartungsmodus befindet , empfiehlt die Migration zu den Klassen java.time .
Weitere Informationen finden Sie im Oracle-Lernprogramm . Suchen Sie im Stapelüberlauf nach vielen Beispielen und Erklärungen. Die Spezifikation ist JSR 310 .
Sie können java.time- Objekte direkt mit Ihrer Datenbank austauschen . Verwenden Sie einen JDBC-Treiber, der mit JDBC 4.2 oder höher kompatibel ist . Keine Notwendigkeit für Zeichenfolgen, keine Notwendigkeit für
java.sql.*
Klassen.Woher bekomme ich die java.time-Klassen?
Das ThreeTen-Extra- Projekt erweitert java.time um zusätzliche Klassen. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen von java.time. Sie können einige nützliche Klassen hier wie finden
Interval
,YearWeek
,YearQuarter
, und mehr .quelle
Wie von Jon Skeet gesagt :
Also los (vorausgesetzt, Sie haben gerade alle Ihre
new Date()
durch ersetztnew DateTime().toDate()
)Wenn Sie eine Bibliothek mit einer Schnittstelle importieren möchten (siehe Jons Kommentar unten), können Sie einfach Prevaylers Uhr verwenden , die Implementierungen sowie die Standardschnittstelle bereitstellt. Das volle Glas ist nur 96kB groß, also sollte es nicht die Bank sprengen ...
quelle
Die Verwendung eines DateFactory-Musters scheint zwar nett zu sein, deckt jedoch keine Bibliotheken ab, die Sie nicht steuern können. Stellen Sie sich die Validierungsanmerkung @Past vor, deren Implementierung auf System.currentTimeMillis basiert (es gibt solche).
Deshalb verwenden wir jmockit, um die Systemzeit direkt zu verspotten:
Da es nicht möglich ist, den ursprünglichen nicht verspotteten Wert von Millis zu erreichen, verwenden wir stattdessen einen Nano-Timer - dies hängt nicht mit der Wanduhr zusammen, aber die relative Zeit reicht hier aus:
Es gibt ein dokumentiertes Problem, dass mit HotSpot die Zeit nach einer Reihe von Anrufen wieder normal wird - hier ist der Problembericht: http://code.google.com/p/jmockit/issues/detail?id=43
Um dies zu überwinden, müssen wir eine bestimmte HotSpot-Optimierung aktivieren - JVM mit diesem Argument ausführen
-XX:-Inline
.Dies ist zwar möglicherweise nicht perfekt für die Produktion, aber für Tests in Ordnung und für die Anwendung absolut transparent, insbesondere wenn DataFactory wirtschaftlich nicht sinnvoll ist und nur aufgrund von Tests eingeführt wird. Es wäre schön, eine integrierte JVM-Option zu haben, die zu einer anderen Zeit ausgeführt werden kann. Schade, dass dies ohne solche Hacks nicht möglich ist.
Die vollständige Geschichte finden Sie in meinem Blog-Beitrag hier: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
Die komplette praktische Klasse SystemTimeShifter finden Sie in der Post. Die Klasse kann in Ihren Tests verwendet werden, oder sie kann sehr einfach als erste Hauptklasse vor Ihrer eigentlichen Hauptklasse verwendet werden, um Ihre Anwendung (oder sogar den gesamten Anwendungsserver) zu einem anderen Zeitpunkt auszuführen. Dies ist natürlich hauptsächlich zu Testzwecken gedacht, nicht für die Produktionsumgebung.
BEARBEITEN Juli 2014: JMockit hat sich in letzter Zeit stark verändert und Sie müssen JMockit 1.0 verwenden, um dies korrekt zu verwenden (IIRC). Kann definitiv nicht auf die neueste Version aktualisieren, bei der die Benutzeroberfläche völlig anders ist. Ich habe darüber nachgedacht, nur das Notwendige einzubringen, aber da wir dieses Ding in unseren neuen Projekten nicht brauchen, entwickle ich dieses Ding überhaupt nicht.
quelle
Powermock funktioniert großartig. Ich habe es nur benutzt, um mich zu verspotten
System.currentTimeMillis()
.quelle
@PrepareForTest
Anmerkung zur Testklasse).System.currentTimeMillis
beliebige Stelle in Ihrem Klassenpfad (eine beliebige Bibliothek) erwarten, müssen Sie jede Klasse überprüfen. Das habe ich gemeint, sonst nichts. Der Punkt ist, dass Sie dieses Verhalten auf der Anruferseite verspotten ("Sie sagen es spezifisch"). Dies ist für einfache Tests in Ordnung, jedoch nicht für Tests, bei denen Sie nicht sicher sind, wie diese Methode von wo aufgerufen wird (z. B. komplexere Komponententests mit beteiligten Bibliotheken). Das bedeutet nicht, dass Powermock überhaupt falsch ist, sondern nur, dass Sie es nicht für diese Art von Test verwenden können.Verwenden Sie die aspektorientierte Programmierung (AOP, z. B. AspectJ), um die Systemklasse so zu verweben, dass ein vordefinierter Wert zurückgegeben wird, den Sie in Ihren Testfällen festlegen können.
Oder weben Sie die Anwendungsklassen, um den Aufruf an
System.currentTimeMillis()
oder zunew Date()
einer anderen Dienstprogrammklasse umzuleiten .Das Weben
java.lang.*
von Systemklassen ( ) ist jedoch etwas schwieriger, und Sie müssen möglicherweise das Offline-Weben für rt.jar durchführen und für Ihre Tests ein separates JDK / rt.jar verwenden.Es wird als binäres Weben bezeichnet und es gibt auch spezielle Tools , um das Weben von Systemklassen durchzuführen und einige Probleme damit zu umgehen (z. B. funktioniert das Bootstrapping der VM möglicherweise nicht).
quelle
Es gibt wirklich keine Möglichkeit, dies direkt in der VM zu tun, aber Sie könnten alle etwas tun, um die Systemzeit auf dem Testcomputer programmgesteuert einzustellen. Die meisten (alle?) Betriebssysteme verfügen über Befehlszeilenbefehle, um dies zu tun.
quelle
date
undtime
. Unter Linux dendate
Befehl.Eine funktionierende Methode zum Überschreiben der aktuellen Systemzeit für JUnit-Testzwecke in einer Java 8-Webanwendung mit EasyMock, ohne Joda Time und ohne PowerMock.
Folgendes müssen Sie tun:
Was ist in der getesteten Klasse zu tun?
Schritt 1
Fügen Sie
java.time.Clock
der getesteten Klasse ein neues Attribut hinzuMyService
und stellen Sie sicher, dass das neue Attribut bei Standardwerten mit einem Instanziierungsblock oder einem Konstruktor ordnungsgemäß initialisiert wird:Schritt 2
Fügen Sie das neue Attribut
clock
in die Methode ein, die ein aktuelles Datum und eine aktuelle Uhrzeit anfordert. In meinem Fall musste ich beispielsweise überprüfen, ob ein in der Datenbank gespeichertes Datum zuvor aufgetreten istLocalDateTime.now()
, das ichLocalDateTime.now(clock)
wie folgt ersetzt habe:Was ist in der Testklasse zu tun?
Schritt 3
Erstellen Sie in der Testklasse ein Mock-Clock-Objekt, fügen Sie es in die Instanz der getesteten Klasse ein, bevor Sie die getestete Methode aufrufen
doExecute()
, und setzen Sie es anschließend wie folgt zurück:Überprüfen Sie es im Debug-Modus und Sie werden sehen, dass das Datum des 3. Februar 2017 korrekt in die
myService
Instanz eingefügt und in der Vergleichsanweisung verwendet wurde und dann ordnungsgemäß auf das aktuelle Datum mit zurückgesetzt wurdeinitDefaultClock()
.quelle
Meiner Meinung nach kann nur eine nicht-invasive Lösung funktionieren. Besonders wenn Sie externe Bibliotheken und eine große Legacy-Codebasis haben, gibt es keine zuverlässige Möglichkeit, die Zeit zu verspotten.
JMockit ... funktioniert nur für eine begrenzte Anzahl von Malen
PowerMock & Co ... muss die Clients auf System.currentTimeMillis () verspotten . Wieder eine invasive Option.
Daraus ergibt sich nur, dass der erwähnte Javaagent- oder AOP- Ansatz für das gesamte System transparent ist. Hat jemand das getan und könnte auf eine solche Lösung hinweisen?
@jarnbjo: Könnten Sie bitte einen Teil des Javaagent-Codes anzeigen?
quelle
Wenn Sie Linux ausführen, können Sie den Hauptzweig von libfaketime oder zum Zeitpunkt des Testens Commit 4ce2835 verwenden .
Stellen Sie einfach die Umgebungsvariable mit der Zeit ein, mit der Sie Ihre Java-Anwendung verspotten möchten, und führen Sie sie mit ld-pretoading aus:
Die zweite Umgebungsvariable ist für Java-Anwendungen von größter Bedeutung, die sonst einfrieren würden. Es erfordert den Hauptzweig von libfaketime zum Zeitpunkt des Schreibens.
Wenn Sie die Zeit eines von systemd verwalteten Dienstes ändern möchten, fügen Sie einfach Folgendes zu Ihren Überschreibungen von Gerätedateien hinzu, z. B. für elasticsearch
/etc/systemd/system/elasticsearch.service.d/override.conf
:Vergessen Sie nicht, systemd mit `systemctl daemon-reload neu zu laden
quelle
Wenn Sie die Methode mit
System.currentTimeMillis()
Argument verspotten möchten, können Sie dieanyLong()
Matchers-Klasse als Argument übergeben.PS: Ich kann meinen Testfall mit dem oben genannten Trick erfolgreich ausführen und nur weitere Details zu meinem Test mitteilen, wenn ich PowerMock- und Mockito-Frameworks verwende.
quelle