Jeśli mam w kodzie funkcję, która wygląda następująco:
class Employee{
public string calculateTax(string name, int salary)
{
switch (name)
{
case "Chris":
doSomething($salary);
case "David":
doSomethingDifferent($salary);
case "Scott":
doOtherThing($salary);
}
}
Normalnie przefakturowałbym to, aby użyć Ploymorfizmu przy użyciu fabrycznego wzorca klasy i strategii:
public string calculateTax(string name)
{
InameHandler nameHandler = NameHandlerFactory::getHandler(name);
nameHandler->calculateTax($salary);
}
Teraz, gdybym używał TDD, miałbym kilka testów, które działają na oryginale calculateTax()
przed refaktoryzacją.
dawny:
calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}
calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){}
calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}
Po refaktoryzacji będę miał klasę Factory NameHandlerFactory
i co najmniej 3 implementacje InameHandler
.
Jak przejść do refaktoryzacji moich testów? Powinienem usunąć testów jednostkowych dla claculateTax()
od EmployeeTests
i utworzyć klasę testową dla każdej implementacji InameHandler
?
Czy powinienem również przetestować klasę Factory?
salary
do funkcjicalculateTax()
. W ten sposób myślę, że powielę kod testowy oryginalnej funkcji i 3 implementacje klasy strategicznej.Zacznę od stwierdzenia, że nie jestem ekspertem od TDD ani testów jednostkowych, ale oto jak bym to przetestował (użyję kodu pseudo-podobnego):
Więc sprawdziłbym, czy
calculateTax()
metoda klasy pracowniczej poprawnie pytaNameHandlerFactory
o a,NameHandler
a następnie wywołujecalculateTax()
metodę zwróconąNameHandler
.źródło
Employee.calculateTax()
metodzie. W ten sposób nie musisz dodawać dodatkowych testów pracowników podczas wprowadzania nowego narzędzia NameHandler.Bierzesz jedną klasę (pracownik, który robi wszystko) i tworzysz 3 grupy klas: fabrykę, pracownika (który zawiera tylko strategię) i strategie.
Zrób więc 3 grupy testów:
Oczywiście możesz wykonywać automatyczne testy dla całego shebang, ale teraz są one bardziej jak testy integracyjne i powinny być traktowane jako takie.
źródło
Przed napisaniem jakiegokolwiek kodu zacznę od testu dla fabryki. Kpiąc z potrzebnych rzeczy zmusiłbym się do myślenia o implementacjach i przypadkach użycia.
Następnie wdrożyłbym fabrykę i kontynuowałem test dla każdej implementacji, a na koniec same implementacje dla tych testów.
Na koniec usunę stare testy.
źródło
Uważam, że nie powinieneś nic robić, co oznacza, że nie powinieneś dodawać żadnych nowych testów.
Podkreślam, że jest to opinia, która w rzeczywistości zależy od tego, jak postrzegasz oczekiwania względem obiektu. Czy uważasz, że użytkownik klasy chciałby przedstawić strategię obliczania podatków? Jeśli go to nie obchodzi, testy powinny to odzwierciedlać, a zachowanie odzwierciedlone w testach jednostkowych powinno polegać na tym, że nie powinno ich obchodzić, że klasa zaczęła używać obiektu strategii do obliczania podatku.
Rzeczywiście napotkałem ten problem kilka razy podczas korzystania z TDD. Myślę, że głównym powodem jest to, że obiekt strategii nie jest naturalną zależnością, w odróżnieniu od zależności architektonicznej granicy, takiej jak zasób zewnętrzny (plik, baza danych, usługa zdalna itp.). Ponieważ nie jest to naturalna zależność, zwykle nie opieram zachowania mojej klasy na tej strategii. Odradzam instynkt, że powinienem zmieniać testy tylko wtedy, gdy zmieniły się oczekiwania mojej klasy.
Jest świetny post od wuja Boba, który mówi dokładnie o tym problemie podczas korzystania z TDD.
Myślę, że tendencja do testowania każdej oddzielnej klasy zabija TDD. Całe piękno TDD polega na tym, że używasz testów, aby pobudzić schematy projektowe, a nie odwrotnie.
źródło