Pójdę przez scenariusz, w którym powinniśmy wykorzystać Supplier<LocalDate>
zamiast LocalDate
.
kod, który bezpośrednio dzwoni do metod statycznych jak LocalDate.now()
jest bardzo trudne do testów jednostkowych. Rozważmy scenariusz, w którym chcemy jednostki przetestować metodę getAge()
który oblicza wiek osoby:
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());
}
}
Działa to dobrze w produkcji. Ale test jednostkowy będzie musiał albo ustawić datę systemu na znaną wartość, albo być aktualizowany co roku, aby oczekiwać, że zwrócony wiek zostanie zwiększony o jedno, oba dość przerażające rozwiązania.
Lepszym rozwiązaniem byłoby wprowadzenie testu jednostkowego w znanej dacie przy jednoczesnym umożliwieniu użycia kodu produkcyjnego pod numerem LocalDate.now()
. Może coś takiego:
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);
}
}
Rozważ scenariusz, w którym urodziny osoby minęły od czasu utworzenia obiektu. Dzięki tej implementacji getAge()
będzie oparty na momencie utworzenia obiektu Person zamiast bieżącej daty. Możemy rozwiązać ten problem za pomocą Supplier<LocalDate>
:
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());
}
}
Wyjście będzie poprawnie być:
John Doe is 5
John Doe is 6
Nasz test jednostka może wstrzyknąć "teraz" datę takiego:
@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' da ci "teraz" w momencie, w którym 's2' został przypisany. 's1.get()' da ci "teraz" w momencie, gdy wywołasz 'get()'. To może nie być to samo, jeśli przekazałeś 'Dostawcę 'gdzieś do użycia, gdy jest to wymagane. Następnie, jeśli ponownie nazwiesz 's1.get()' ponownie, dostaniesz inny czas ponownie. – khelwood
@khelwood, nie dostałem tego i zaktualizowałem pytanie, proszę sprawdzić. – badCoder
Użyj go, gdy musisz przekazać _wielkie wartości_ w _inną metodę._ To prawie 100% ważnych przypadków użycia. –