Wenn Sie an einer Funktion arbeiten, die von der Zeit abhängt ... Wie organisieren Sie Unit-Tests? Wenn Ihre Unit-Testszenarien davon abhängen, wie Ihr Programm "jetzt" interpretiert, wie richten Sie sie ein?
Zweite Bearbeitung: Nach ein paar Tagen lesen Sie Ihre Erfahrungen
Ich kann sehen, dass sich die Techniken zur Bewältigung dieser Situation normalerweise um eines dieser drei Prinzipien drehen:
- Hinzufügen (Hardcode) einer Abhängigkeit: Fügen Sie eine kleine Ebene über der Zeitfunktion / dem Objekt hinzu und rufen Sie Ihre Datums- / Uhrzeitfunktion immer über diese Ebene auf. Auf diese Weise können Sie die Zeit während der Testfälle steuern.
- Verwenden Sie Mocks: Ihr Code bleibt genau gleich. In Ihren Tests ersetzen Sie das Zeitobjekt durch ein gefälschtes Zeitobjekt. Manchmal besteht die Lösung darin, das echte Zeitobjekt zu ändern, das von Ihrer Programmiersprache bereitgestellt wird.
- Abhängigkeitsinjektion verwenden: Erstellen Sie Ihren Code so, dass jede Zeitreferenz als Parameter übergeben wird. Anschließend haben Sie während der Tests die Kontrolle über die Parameter.
Sprachspezifische Techniken (oder Bibliotheken) sind sehr willkommen und werden verbessert, wenn ein illustrativer Code hinzukommt. Am interessantesten ist dann, wie ähnliche Prinzipien auf jeder Plattform angewendet werden können. Und ja ... Wenn ich es sofort in PHP anwenden kann, besser als besser;)
Beginnen wir mit einem einfachen Beispiel: einer einfachen Buchungsanwendung.
Angenommen, wir haben eine JSON-API und zwei Nachrichten: eine Anforderungsnachricht und eine Bestätigungsnachricht. Ein Standardszenario sieht folgendermaßen aus:
- Sie stellen eine Anfrage. Sie erhalten eine Antwort mit einem Token. Die Ressource, die zur Erfüllung dieser Anforderung benötigt wird, wird vom System für 5 Minuten blockiert.
- Sie bestätigen eine durch das Token gekennzeichnete Anforderung. Wenn das Token innerhalb von 5 Minuten ausgestellt wurde, wird es akzeptiert (die Ressource ist noch verfügbar). Wenn mehr als 5 Minuten vergangen sind, müssen Sie eine neue Anfrage stellen (die Ressource wurde freigegeben. Sie müssen ihre Verfügbarkeit erneut überprüfen).
Hier kommt das entsprechende Testszenario:
Ich mache eine Anfrage. Ich bestätige (sofort) mit dem Token, den ich erhalten habe. Meine Bestätigung wird akzeptiert.
Ich mache eine Anfrage. Ich warte 3 Minuten. Ich bestätige mit dem Token, den ich erhalten habe. Meine Bestätigung wird akzeptiert.
Ich mache eine Anfrage. Ich warte 6 Minuten. Ich bestätige mit dem Token, den ich erhalten habe. Meine Bestätigung wird abgelehnt.
Wie können wir diese Unit-Tests programmieren? Welche Architektur sollten wir verwenden, damit diese Funktionen testbar bleiben?
Bearbeiten Hinweis: Wenn wir eine Anfrage aufnehmen, wird die Zeit in einer Datenbank in einem Format gespeichert, das alle Informationen über Millisekunden verliert.
EXTRA - aber vielleicht etwas ausführlich: Hier kommen Details darüber, was ich bei meinen "Hausaufgaben" herausgefunden habe:
Ich habe mein Feature mit einer Abhängigkeit von einer eigenen Zeitfunktion erstellt. Meine Funktion VirtualDateTime hat eine statische Methode get_time (), die ich dort aufrufe, wo ich neues DateTime () aufgerufen habe. Mit dieser Zeitfunktion kann ich simulieren und steuern, wie spät es ist, damit ich Tests erstellen kann wie: "Jetzt auf den 21. Januar 2014, 16:15 Uhr einstellen. Eine Anfrage stellen. 3 Minuten weitermachen. Bestätigung vornehmen." Dies funktioniert gut, auf Kosten der Abhängigkeit und "nicht so hübscher Code".
Eine etwas stärker integrierte Lösung wäre, eine eigene myDateTime-Funktion zu erstellen, die DateTime um zusätzliche Funktionen für die "virtuelle Zeit" erweitert (vor allem jetzt auf das einstellen, was ich möchte). Dies macht den Code der ersten Lösung etwas eleganter (Verwendung von new myDateTime anstelle von new DateTime), ist jedoch sehr ähnlich: Ich muss mein Feature mit meiner eigenen Klasse erstellen und so eine Abhängigkeit erstellen.
Ich habe darüber nachgedacht, die DateTime-Funktion zu hacken, damit sie mit meiner Abhängigkeit funktioniert, wenn ich sie brauche. Gibt es jedoch eine einfache und übersichtliche Möglichkeit, eine Klasse durch eine andere zu ersetzen? (Ich glaube, ich habe eine Antwort darauf bekommen: Siehe Namespace unten).
In einer PHP-Umgebung, in der ich "Runkit" gelesen habe, kann ich dies möglicherweise dynamisch tun (die DateTime-Funktion hacken). Das Schöne ist, dass ich überprüfen kann, ob ich in einer Testumgebung ausgeführt werde, bevor ich etwas an DateTime ändere, und es in der Produktion unberührt lassen kann [1] . Dies klingt viel sicherer und sauberer als jeder manuelle DateTime-Hack.
Abhängigkeitsinjektion einer Taktfunktion in jeder Klasse, die Zeit verwendet [2] . Ist das nicht übertrieben? Ich sehe, dass es in einigen Umgebungen sehr nützlich wird [5] . In diesem Fall gefällt es mir allerdings nicht so gut.
Entfernen Sie die Zeitabhängigkeit in allen Funktionen [2] . Ist das immer machbar? (Weitere Beispiele finden Sie unten)
Namespaces verwenden [2] [3] ? Das sieht ganz gut aus. Ich könnte DateTime mit meiner eigenen DateTime-Funktion verspotten, die \ DateTime erweitert ... Verwenden Sie DateTime :: setNow ("2014-01-21 16:15:00") und DateTime :: wait ("+ 3 Minuten"). Dies deckt ziemlich genau das ab, was ich brauche. Was ist, wenn wir die time () -Funktion verwenden? Oder andere Zeitfunktionen? Ich muss ihre Verwendung in meinem Originalcode immer noch vermeiden. Oder ich müsste sicherstellen, dass jede PHP-Zeitfunktion, die ich in meinem Code verwende, in meinen Tests überschrieben wird ... Gibt es eine Bibliothek, die genau dies tun würde?
Ich habe nach einer Möglichkeit gesucht, die Systemzeit nur für einen Thread zu ändern. Es scheint, dass es keine gibt [4] . Das ist schade: Eine "einfache" PHP-Funktion zum "Einstellen der Zeit auf den 21. Januar 2014, 16h15 für diesen Thread" wäre eine großartige Funktion für diese Art von Tests.
Ändern Sie das Datum und die Uhrzeit des Systems für den Test mit exec (). Dies kann funktionieren, wenn Sie keine Angst haben, andere Dinge auf dem Server durcheinander zu bringen. Und Sie müssen die Systemzeit zurücksetzen, nachdem Sie Ihren Test ausgeführt haben. Es kann in einigen Situationen den Trick machen, fühlt sich aber ziemlich "hacky" an.
Das scheint mir ein sehr normales Problem zu sein. Ich vermisse jedoch immer noch eine einfache und generische Methode, um damit umzugehen. Vielleicht habe ich etwas verpasst? Bitte teilen Sie Ihre Erfahrungen!
HINWEIS: In einigen anderen Situationen haben wir möglicherweise ähnliche Testanforderungen.
Jede Funktion, die mit einer Art Timeout funktioniert (z. B. Schachspiel?)
Verarbeiten einer Warteschlange, die Ereignisse zu einem bestimmten Zeitpunkt auslöst (im obigen Szenario könnten wir so weitermachen: Einen Tag vor Beginn der Reservierung möchte ich dem Benutzer eine E-Mail mit allen Details senden. Testszenario ...)
Sie möchten eine Testumgebung mit Daten einrichten, die in der Vergangenheit gesammelt wurden - Sie möchten es so sehen, als ob es jetzt wäre. [1]
1 /programming/3271735/simulate-different-server-datetimes-in-php
2 /programming/4221480/how-to-change-current-time-for-unit-testing-date-functions-in-php
3 http://www.schmengler-se.de/de/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/
DateTime now
anstelle einer Uhr einen Wert an den Code zu übergeben.