Załóżmy, że mam menedżera klas wywodzącego się z pracownika klasy podstawowej i że pracownik ma metodę getEmail (), która jest dziedziczona przez menedżera . Czy powinienem sprawdzić, czy zachowanie metody getEmail () menedżera jest w rzeczywistości takie samo jak zachowanie pracownika?
W momencie pisania tych testów zachowanie będzie takie samo, ale oczywiście w pewnym momencie w przyszłości ktoś może zastąpić tę metodę, zmienić jej zachowanie, a zatem zepsuć moją aplikację. Jednak wydaje się nieco dziwne, aby zasadniczo przetestować pod kątem braku wtrącania się kodu.
(Należy pamiętać, że testowanie metody Manager :: getEmail () nie poprawia pokrycia kodu (ani nawet żadnych innych wskaźników jakości kodu (?)) Do momentu utworzenia / zastąpienia Manager :: getEmail () .)
(Jeśli odpowiedź brzmi „Tak”, przydatne byłyby pewne informacje na temat zarządzania testami dzielonymi między klasami podstawowymi i pochodnymi).
Równoważne sformułowanie pytania:
Jeśli klasa pochodna dziedziczy metodę z klasy podstawowej, jak wyrazić (przetestować), czy oczekuje się, że odziedziczona metoda:
- Zachowuj się dokładnie tak samo jak obecnie baza (jeśli zachowanie bazy zmienia się, zachowanie metody pochodnej nie zmienia się);
- Zachowuj się dokładnie tak samo jak baza przez cały czas (jeśli zmieni się zachowanie klasy podstawowej, zmieni się również zachowanie klasy pochodnej); lub
- Zachowuj się jednak, jak chce (nie obchodzi cię zachowanie tej metody, ponieważ nigdy jej nie nazywasz).
źródło
Manager
z klasyEmployee
było pierwszym poważnym błędem.Odpowiedzi:
Przyjąłbym tutaj pragmatyczne podejście: jeśli ktoś w przyszłości zastąpi Manager :: getMail, to na deweloperze spoczywa obowiązek dostarczenia kodu testowego dla nowej metody.
Oczywiście jest to poprawne tylko wtedy, gdy
Manager::getEmail
naprawdę ma taką samą ścieżkę kodu jakEmployee::getEmail
! Nawet jeśli metoda nie zostanie zastąpiona, może zachowywać się inaczej:Employee::getEmail
mogłyby wywołać niektóre chronione wirtualnegetInternalEmail
, które jest zastępowane wManager
.Employee::getEmail
może uzyskać dostęp do jakiegoś stanu wewnętrznego (np. niektóre pola_email
), które mogą różnić się w dwóch implementacjach: Na przykład domyślna implementacjaEmployee
może zapewnić, że_email
jest zawsze[email protected]
, aleManager
jest bardziej elastyczna w przydzielaniu adresów e-mail.W takich przypadkach możliwe jest, że błąd przejawia się tylko w
Manager::getEmail
, nawet jeśli implementacja samej metody jest taka sama. W takim przypadkuManager::getEmail
osobne testowanie może mieć sens.źródło
Ja bym.
Jeśli myślisz „cóż, to tak naprawdę wywołujesz tylko Employee :: getEmail (), ponieważ go nie zastępuję, więc nie muszę testować Managera :: getEmail ()”, to tak naprawdę nie testujesz zachowania of Manager :: getEmail ().
Zastanowiłbym się, co powinien zrobić tylko Manager :: getEmail (), a nie to, czy jest to dziedziczone czy zastąpione. Jeśli zachowanie Managera :: getEmail () powinno polegać na zwracaniu tego, co zwraca Employee :: getMail (), to jest to test. Jeśli zachowanie ma zwrócić „[email protected]”, to jest to test. Nie ma znaczenia, czy zostanie zaimplementowane przez dziedziczenie, czy zastąpione.
Liczy się to, że jeśli zmieni się w przyszłości, twój test go złapie i wiesz, że coś się zepsuło lub trzeba to przemyśleć.
Niektóre osoby mogą nie zgadzać się z pozorną nadmiarowością czeku, ale moim przeciwnikiem byłoby testowanie zachowania Employee :: getMail () i Manager :: getMail () jako odrębnych metod, niezależnie od tego, czy są dziedziczone, czy nie. zastąpione. Jeśli przyszły programista musi zmienić zachowanie Managera :: getMail (), musi także zaktualizować testy.
Opinie mogą się różnić, ale myślę, że Digger i Heinzi podali uzasadnione powody przeciwne.
źródło
Nie testuj tego jednostkowo. Wykonaj test funkcjonalny / akceptacyjny.
Testy jednostkowe powinny testować każdą implementację, jeśli nie zapewniasz nowej implementacji, trzymaj się zasady DRY. Jeśli chcesz włożyć tutaj trochę wysiłku, możesz ulepszyć oryginalny test jednostkowy. Tylko jeśli zastąpisz metodę, powinieneś napisać test jednostkowy.
Jednocześnie testy funkcjonalne / akceptacyjne powinny upewnić się, że pod koniec dnia cały kod działa zgodnie z jego przeznaczeniem i, mam nadzieję, wyłapie wszelkie dziwactwa wynikające z dziedziczenia.
źródło
Reguły Roberta Martina dotyczące TDD to:
Jeśli będziesz przestrzegać tych zasad, nie będziesz w stanie zadać tego pytania. Jeśli
getEmail
metoda wymaga przetestowania, zostałaby przetestowana.źródło
Tak, powinieneś przetestować odziedziczone metody, ponieważ w przyszłości mogą zostać zastąpione. Ponadto odziedziczone metody mogą wywoływać metody wirtualne, które są zastępowane , co zmieniłoby zachowanie nieprzepisanej odziedziczonej metody.
Sposób, w jaki to testuję, polega na utworzeniu klasy abstrakcyjnej w celu przetestowania (ewentualnie abstrakcyjnej) klasy bazowej lub interfejsu, takich jak ten (w języku C # przy użyciu NUnit):
Następnie mam klasę z testami specyficznymi dla
Manager
i integruję testy pracownika w ten sposób:Powodem, dla którego mogę to zrobić, jest zasada podstawienia Liskowa: testy
Employee
powinny przejść pomyślnie, gdy minąManager
obiekt, ponieważ pochodziEmployee
. Muszę więc napisać moje testy tylko raz i mogę sprawdzić, czy działają one dla wszystkich możliwych implementacji interfejsu lub klasy bazowej.źródło
setUp()
metodę xUnit ! I prawie każda platforma MVC sieci, która obejmuje przesłonięcie metody „indeksu”, również łamie LSP, czyli w zasadzie wszystkie.class ManagerTests : ExployeeTests
? (tj. odzwierciedlenie dziedziczenia testowanych klas). Czy jest to głównie estetyka, która pomaga w przeglądaniu wyników?Powiedziałbym „nie”, ponieważ moim zdaniem byłby to powtarzany test. Raz przetestowałbym testy pracowników i to by było na tyle.
Jeśli metoda zostanie zastąpiona, potrzebne będą nowe testy, aby sprawdzić zastąpione zachowanie. Takie jest zadanie osoby wdrażającej
getEmail()
metodę nadpisywania .źródło
Nie, nie musisz testować odziedziczonych metod. Klasy i ich przypadki testowe, które opierają się na tej metodzie, i tak ulegną awarii, jeśli zachowanie się zmieni
Manager
.Pomyśl o następującym scenariuszu: Adres e-mail jest składany jako Imię[email protected]:
Jednostka przetestowała to i działa dobrze dla twojego
Employee
. Utworzyłeś także klasęManager
:Teraz masz klasę,
ManagerSort
która sortuje menedżerów na liście na podstawie ich adresów e-mail. Zakładasz, że generowanie wiadomości e-mail jest takie samo jak wEmployee
:Piszesz test dla
ManagerSort
:Wszystko dziala. Teraz ktoś przychodzi i zastępuje
getEmail()
metodę:Co się teraz dzieje? Twój
testManagerSort()
zawiedzie, ponieważgetEmail()
odManager
zostało nadpisane. Zbadasz ten problem i znajdziesz przyczynę. A wszystko to bez pisania osobnego testu dla odziedziczonej metody.Dlatego nie trzeba testować odziedziczonych metod.
W przeciwnym razie, na przykład w Javie, trzeba by przetestować wszystkie metody odziedziczone
Object
jaktoString()
,equals()
itd. W każdej klasie.źródło