Ich muss eine Funktion testen, deren Ergebnis von der aktuellen Zeit abhängt (unter Verwendung der Joda-Zeit isBeforeNow()
passiert dies).
public boolean isAvailable() {
return (this.someDate.isBeforeNow());
}
Ist es möglich, die Systemzeit mit (z. B. mit Mockito) zu stummeln, um die Funktion zuverlässig zu testen?
Antworten:
Die Joda-Zeit unterstützt das Festlegen einer "falschen" aktuellen Zeit über die Methoden
setCurrentMillisFixed
undsetCurrentMillisOffset
derDateTimeUtils
Klasse.Siehe https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html
quelle
DateTimeUtils.setCurrentMillisProvider(DateTimeUtils.MillisProvider)
Methode, die sicherlich eine threadgebundene Implementierung ermöglichen würde.Der beste Weg (IMO), Ihren Code testbar zu machen, besteht darin, die Abhängigkeit von "Was ist die aktuelle Zeit?" In eine eigene Schnittstelle zu extrahieren. Dabei wird eine Implementierung verwendet, die die aktuelle Systemzeit verwendet (normalerweise verwendet), und eine Implementierung, mit der Sie die Zeit einstellen können , schieben Sie es vor, wie Sie wollen usw.
Ich habe diesen Ansatz in verschiedenen Situationen verwendet und er hat gut funktioniert. Es ist einfach einzurichten - erstellen Sie einfach eine Schnittstelle (z. B.
Clock
), die über eine einzige Methode verfügt, um den aktuellen Zeitpunkt in einem beliebigen Format anzuzeigen (z. B. mithilfe von Joda Time oder möglicherweise aDate
).quelle
Java 8 hat die abstrakte Klasse eingeführt
java.time.Clock
, mit der Sie eine alternative Implementierung zum Testen haben können. Genau das schlug Jon damals in seiner Antwort vor.quelle
Supplier
der interessierenden Zeiteinheiten (zLocalDateTime
. B. ).LocalDateTime.now()
ohneClock
als Parameter übergeben zu werdennow()
.Supplier<LocalDateTime>
, an den Sie entweder einen echten Adapter anschließen können - z. B.LocalDateTime::now
einen gefälschten Adapter - z. B. -() -> LocalDateTime.of(2017, 10, 24, 0, 0)
wo es zweckmäßig ist (Komponententests). Keine Notwendigkeit für ein GanzesClock
.Um Jon Skeets Antwort zu ergänzen, enthält Joda Time bereits eine aktuelle Zeitschnittstelle : DateTimeUtils.MillisProvider
Zum Beispiel:
import org.joda.time.DateTime; import org.joda.time.DateTimeUtils.MillisProvider; public class Check { private final MillisProvider millisProvider; private final DateTime someDate; public Check(MillisProvider millisProvider, DateTime someDate) { this.millisProvider = millisProvider; this.someDate = someDate; } public boolean isAvailable() { long now = millisProvider.getMillis(); return (someDate.isBefore(now)); } }
Verspotten Sie die Zeit in einem Unit-Test (mit Mockito, aber Sie können Ihre eigene Klasse MillisProviderMock implementieren):
DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10); MillisProvider mockMillisProvider = mock(MillisProvider.class); when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis()); Check check = new Check(mockMillisProvider, someDate);
Verwenden Sie die aktuelle Zeit in der Produktion ( DateTimeUtils.SYSTEM_MILLIS_PROVIDER wurde in 2.9.3 zu Joda Time hinzugefügt):
Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);
quelle
Ich verwende einen ähnlichen Ansatz wie Jon, aber anstatt nur für die aktuelle Zeit
Clock
eine spezielle Schnittstelle zu erstellen (z. B. ), erstelle ich normalerweise eine spezielle Testschnittstelle (z. B.MockupFactory
). Ich habe dort alle Methoden angegeben, die ich zum Testen des Codes benötige. In einem meiner Projekte habe ich dort beispielsweise vier Methoden:Die getestete Klasse verfügt über einen Konstruktor, der diese Schnittstelle unter anderem akzeptiert. Die ohne dieses Argument erstellt nur eine Standardinstanz dieser Schnittstelle, die "im wirklichen Leben" funktioniert. Sowohl die Schnittstelle als auch der Konstruktor sind paketprivat, sodass die Test-API nicht außerhalb des Pakets ausläuft.
Wenn ich mehr nachgeahmte Objekte benötige, füge ich dieser Schnittstelle einfach eine Methode hinzu und implementiere sie sowohl in Test- als auch in realen Implementierungen.
Auf diese Weise entwerfe ich Code, der in erster Linie zum Testen geeignet ist, ohne dem Code selbst zu viel aufzuerlegen. Tatsächlich wird der Code auf diese Weise noch sauberer, da viel Fabrikcode an einem Ort gesammelt wird. Wenn ich beispielsweise in realem Code zu einer anderen Datenbankclient-Implementierung wechseln muss, muss ich nur eine Zeile ändern, anstatt nach Verweisen auf den Konstruktor zu suchen.
Natürlich funktioniert es genau wie bei Jons Ansatz nicht mit Code von Drittanbietern, den Sie nicht ändern können oder dürfen.
quelle
class DefaultMockupFactory implements MockupFactory {Timer createTimer() {return new Timer();}}
komplex ist, nicht wahr? Undfactory.createTimer()
irgendwo im Code zu sein, macht es auch nicht schwieriger, den Code zu verstehen. Aber ich stimme zu, dass dies in einigen Fällen möglicherweise nicht der beste Weg ist, dies zu tun.MockupFactory
einige andere Methoden darauf hatte und alle Stellen im Code finden wollte, an denencreateTimer()
es verwendet wurde, muss ich von der zu testenden Klasse zur Benutzeroberfläche navigieren und dann suchen für die Verwendung der Methode, anstatt nur in der Klasse zu suchen.MockupFactory
für alle Abhängigkeiten, die emuliert werden müssen, eine einzige Schnittstelle ( ) verwende, während Jon vorschlägt, für jede Abhängigkeit (TimerFactory
und so weiter) eine separate Schnittstelle zu haben . Wie Sie den Code ohne Schnittstelle testen würden, ist mir ein Rätsel. Auf die eine oder andere Weise wird die zusätzliche Komplexität benötigt.TimerFactory
sie wiederverwendet werden. Daher können Sie sie einmal in einem DI-Container verkabeln und überall dieselbe verwenden, während diesMockupFactory
für eine bestimmte Klasse unwahrscheinlich ist mehr Orte verwendet werden - das bedeutet, dass im Vergleich zu gut getrennten Schnittstellen mehr Konfiguration erforderlich ist.