ich durch ein Szenario gehen werde, wo wir Supplier<LocalDate>
statt LocalDate
verwenden sollten .
Code, der Aufrufe an statische Methoden wie LocalDate.now()
direkt macht, ist sehr schwierig zu Unit-Test. Betrachten wir ein Szenario, in dem wir an die Einheit wollen testen ein Verfahren getAge()
, die das Alter einer Person berechnet:
class Person {
final String name;
private final LocalDate dateOfBirth;
Person(String name, LocalDate dateOfBirth) {
this.name = name;
this.dateOfBirth = dateOfBirth;
}
long getAge() {
return ChronoUnit.YEARS.between(dateOfBirth, LocalDate.now());
}
}
Das in der Produktion gut funktioniert. Aber ein Komponententest müsste entweder das Systemdatum auf einen bekannten Wert setzen oder jedes Jahr aktualisiert werden, um zu erwarten, dass das zurückgegebene Alter um eins erhöht wird, beides ziemlich abscheuliche Lösungen.
Eine bessere Lösung wäre, dass der Komponententest ein bekanntes Datum eingibt und der Produktionscode trotzdem LocalDate.now()
verwendet werden kann. Vielleicht etwas in der Art:
class Person {
final String name;
private final LocalDate dateOfBirth;
private final LocalDate currentDate;
// Used by regular production code
Person(String name, LocalDate dateOfBirth) {
this(name, dateOfBirth, LocalDate.now());
}
// Visible for test
Person(String name, LocalDate dateOfBirth, LocalDate currentDate) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.currentDate = currentDate;
}
long getAge() {
return ChronoUnit.YEARS.between(dateOfBirth, currentDate);
}
}
Betrachten Sie ein Szenario, in dem der Geburtstag der Person seit der Erstellung des Objekts abgelaufen ist. Bei dieser Implementierung basiert getAge()
darauf, wann das Person-Objekt erstellt wurde und nicht auf dem aktuellen Datum. Wir können dies lösen, indem Supplier<LocalDate>
mit:
class Person {
final String name;
private final LocalDate dateOfBirth;
private final Supplier<LocalDate> currentDate;
// Used by regular production code
Person(String name, LocalDate dateOfBirth) {
this(name, dateOfBirth,()-> LocalDate.now());
}
// Visible for test
Person(String name, LocalDate dateOfBirth, Supplier<LocalDate> currentDate) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.currentDate = currentDate;
}
long getAge() {
return ChronoUnit.YEARS.between(dateOfBirth, currentDate.get());
}
public static void main(String... args) throws InterruptedException {
// current date 2016-02-11
Person person = new Person("John Doe", LocalDate.parse("2010-02-12"));
printAge(person);
TimeUnit.DAYS.sleep(1);
printAge(person);
}
private static void printAge(Person person) {
System.out.println(person.name + " is " + person.getAge());
}
}
Der Ausgang würde richtig sein:
John Doe is 5
John Doe is 6
Unser Unit-Test das "jetzt" Datum wie folgt injizieren:
@Test
void testGetAge() {
Supplier<LocalDate> injectedNow =()-> LocalDate.parse("2016-12-01");
Person person = new Person("John Doe", LocalDate.parse("2004-12-01"), injectedNow);
assertEquals(12, person.getAge());
}
's2' gibt Ihnen "jetzt" zu dem Zeitpunkt, an dem 's2' zugewiesen wurde. 's1.get()' gibt Ihnen "jetzt" an dem Punkt, an dem Sie 'get()' aufrufen. Das ist vielleicht nicht das Gleiche, wenn Sie den "Lieferanten" irgendwo hingegeben haben, um ihn bei Bedarf zu verwenden. Wenn Sie später 's1.get()' erneut aufrufen, erhalten Sie erneut eine andere Zeit. – khelwood
@Khelwood, ich habe es nicht bekommen und aktualisierte Frage, bitte überprüfen. – badCoder
Verwenden Sie es, wenn Sie die Fähigkeit übergeben müssen, _mulpeple values_ in _andother method._ zu erhalten. Das sind ziemlich viele 100% der gültigen Anwendungsfälle. –