Czy mamy pisać testy dla naszych metod pobierających i ustawiających, czy jest to przesada?
Czy mamy pisać testy dla naszych metod pobierających i ustawiających, czy jest to przesada?
Odpowiedzi:
Powiedziałbym nie.
@Will powiedział, że powinieneś dążyć do 100% pokrycia kodu, ale moim zdaniem jest to niebezpieczne rozproszenie. Możesz pisać testy jednostkowe, które mają 100% pokrycia, a mimo to testować absolutnie nic.
Testy jednostkowe służą do testowania zachowania twojego kodu w sposób ekspresyjny i znaczący, a metody pobierające / ustawiające są tylko środkiem do osiągnięcia celu. Jeśli testy wykorzystują metody pobierające / ustawiające, aby osiągnąć swój cel, jakim jest testowanie „prawdziwej” funkcjonalności, to wystarczy.
Z drugiej strony, jeśli twoje metody pobierające i ustawiające robią coś więcej niż tylko pobieranie i ustawianie (tj. Są to odpowiednio złożone metody), to tak, powinny zostać przetestowane. Ale nie pisz przypadków testów jednostkowych tylko po to, aby przetestować metodę pobierającą lub ustawiającą, to strata czasu.
Roy Osherove w swojej słynnej książce „The Art Of Unit Testing” mówi:
Właściwości (metody pobierające / ustawiające w Javie) są dobrymi przykładami kodu, który zwykle nie zawiera żadnej logiki i nie wymaga testowania. Ale uważaj: po dodaniu jakiegokolwiek czeku wewnątrz właściwości będziesz chciał się upewnić, że logika jest testowana.
Uwaga : ta odpowiedź wciąż zyskuje na popularności, choć może to być zła rada. Aby zrozumieć, dlaczego, spójrz na jego młodszą siostrę poniżej.
W porządku, kontrowersyjne, ale twierdzę, że każdemu, kto odpowie „nie” na to pytanie, brakuje fundamentalnej koncepcji TDD.
Dla mnie odpowiedź brzmi: tak, jeśli podążasz za TDD. Jeśli nie, to wiarygodną odpowiedzią jest nie.
TDD jest często cytowane jako posiadające główne zalety.
Jako programiści strasznie kusi się myślenie o atrybutach jako o czymś istotnym, a pobierających i ustawiających jako o jakimś narzutu.
Ale atrybuty są szczegółem implementacji, podczas gdy ustawiające i pobierające są interfejsem kontraktowym, który faktycznie sprawia, że programy działają.
O wiele ważniejsze jest, aby przeliterować, że obiekt powinien:
Pozwól swoim klientom zmienić swój stan
i
Pozwól swoim klientom zapytać o jego stan
następnie jak ten stan jest faktycznie przechowywany (dla którego atrybut jest najpowszechniejszy, ale nie jedyny).
Test taki jak
(The Painter class) should store the provided colour
jest ważny dla części dokumentacyjnej TDD.
Fakt, że ostateczna implementacja jest trywialna (atrybut) i nie niesie żadnej korzyści z obrony, powinien być nieznany podczas pisania testu.
Jednym z kluczowych problemów w świecie rozwoju systemów jest brak inżynierii w obie strony 1 - proces rozwoju systemu jest podzielony na rozłączne podprocesy, których artefakty (dokumentacja, kod) są często niespójne.
1 Brodie, Michael L. "John Mylopoulos: wyszywanie nasion modelowania konceptualnego." Modelowanie koncepcyjne: podstawy i zastosowania. Springer Berlin Heidelberg, 2009. 1-9.
Jest to część dokumentacyjna TDD, która zapewnia, że specyfikacje systemu i jego kodu są zawsze spójne.
W ramach TDD najpierw piszemy negatywny test akceptacji, a dopiero potem piszemy kod, który pozwoli im przejść.
W ramach BDD wyższego poziomu najpierw piszemy scenariusze, a potem je sprawdzamy.
Dlaczego powinieneś wykluczyć setery i getter?
Teoretycznie jest całkowicie możliwe w ramach TDD, aby jedna osoba napisała test, a druga wdrożyła kod, który go zalicza.
Więc zadaj sobie pytanie:
Czy osoba pisząca testy dla klasy powinna wspomnieć o metodach pobierających i ustawiających.
Ponieważ metody pobierające i ustawiające są publicznym interfejsem klasy, odpowiedź jest oczywiście twierdząca lub nie będzie możliwości ustawienia lub zapytania o stan obiektu. Jednak sposobem na to niekoniecznie jest testowanie każdej metody osobno, zobacz moją drugą odpowiedź po więcej.
Oczywiście, jeśli najpierw napiszesz kod, odpowiedź może nie być tak jednoznaczna.
tl; dr: Tak , powinieneś , a przy OpenPojo to trywialne.
Powinieneś przeprowadzić walidację w swoich getterach i seterach, więc powinieneś to przetestować. Na przykład setMom(Person p)
nie powinni pozwalać na stawianie nikogo młodszego od siebie jako matki.
Nawet jeśli nie robisz tego teraz, są szanse, że to zrobisz w przyszłości, będzie to dobre dla analizy regresji. Jeśli chcesz pozwolić matkom na to, null
żebyś miał na to test, gdyby ktoś to później zmienił, wzmocni to twoje przypuszczenia.
Typowy błąd występuje void setFoo( Object foo ){ foo = foo; }
tam, gdzie powinien void setFoo( Object foo ){ this.foo = foo; }
. (W pierwszym przypadku foo
, który jest zapisywany jest parametr niefoo
pole na obiekcie ).
Jeśli zwracasz tablicę lub kolekcję, powinieneś przetestować, czy metoda pobierająca będzie wykonywała kopie obronne danych przekazanych do metody ustawiającej przed zwróceniem.
W przeciwnym razie, jeśli masz najbardziej podstawowe metody ustawiające / pobierające, testy jednostkowe dodadzą może maksymalnie około 10 minut na obiekt, więc jaka jest strata? Jeśli dodasz zachowanie, masz już test szkieletu i otrzymujesz ten test regresji za darmo. Jeśli używasz Javy, nie masz wymówki, ponieważ istnieje OpenPojo . Istnieje zestaw reguł, które możesz włączyć, a następnie zeskanować za ich pomocą cały projekt, aby upewnić się, że są one konsekwentnie stosowane w kodzie.
Z ich przykładów :
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
Pozwól mi rozwinąć:
Od efektywnej pracy ze starszym kodem 1 :
Termin test jednostkowy ma długą historię w tworzeniu oprogramowania. W przypadku większości koncepcji testów jednostkowych powszechna jest idea, że są to testy wyodrębnione z poszczególnych składników oprogramowania. Co to są komponenty? Definicje są różne, ale w testach jednostkowych zwykle mamy do czynienia z najbardziej atomowymi jednostkami behawioralnymi systemu. W kodzie proceduralnym jednostki są często funkcjami. W kodzie zorientowanym obiektowo jednostkami są klasy.
Zwróć uwagę, że w przypadku OOP, gdzie znajdują się metody pobierające i ustawiające, jednostką jest klasa , niekoniecznie poszczególne metody .
Wszystkie wymagania i testy są zgodne z logiką Hoare'a :
{P} C {Q}
Gdzie:
{P}
jest warunkiem wstępnym ( podanym )C
jest warunkiem wyzwalania ( kiedy ){Q}
jest warunkiem końcowym ( wtedy )Następnie pojawia się maksyma:
Testuj zachowanie, a nie implementację
Oznacza to, że nie powinieneś testować, jak C
osiąga się warunek {Q}
końcowy , powinieneś sprawdzić, czy jest to wynikiem C
.
Jeśli chodzi o OOP, C
to klasa. Nie powinieneś więc testować efektów wewnętrznych, tylko efekty zewnętrzne.
Metody pobierające i ustawiające mogą obejmować pewną logikę, ale tak długo, jak ta logika nie ma efektu zewnętrznego - czyniąc je akcesorami fasoli 2 ) test będzie musiał zajrzeć do wnętrza obiektu i przez to nie tylko naruszyć hermetyzację, ale także przetestować pod kątem implementacji.
Dlatego nie należy oddzielnie testować metod pobierających i ustawiających ziarna fasoli. To jest złe:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
Chociaż gdyby setVAT
zgłosił wyjątek, odpowiedni test byłby odpowiedni, ponieważ teraz występuje efekt zewnętrzny.
Praktycznie nie ma sensu zmieniać stanu wewnętrznego przedmiotu, jeśli taka zmiana nie ma wpływu na zewnątrz, nawet jeśli taki efekt nastąpi później.
Tak więc test ustawiający i pobierający powinien dotyczyć efektu zewnętrznego tych metod, a nie wewnętrznych.
Na przykład:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
Możesz pomyśleć:
Chwileczkę, testujemy
getGross()
tutaj niesetVAT()
.
Ale jeśli wystąpi setVAT()
awaria, ten test powinien zakończyć się niepowodzeniem.
1 Feathers, M., 2004. Efektywna praca ze starszym kodem. Prentice Hall Professional.
2 Martin, RC, 2009. Czysty kod: podręcznik zwinnego rzemiosła oprogramowania. Edukacja Pearson.
Chociaż istnieją uzasadnione powody dla właściwości, istnieje powszechne przekonanie, że ujawnianie państwa członkowskiego za pośrednictwem właściwości jest złym projektem. Artykuł Roberta Martina na temat zasady otwartej i zamkniętej rozszerza tę kwestię, stwierdzając, że właściwości zachęcają do łączenia, a zatem ograniczają możliwość zamknięcia klasy przed modyfikacją - jeśli zmodyfikujesz właściwość, wszyscy konsumenci klasy również będą musieli zmienić. Twierdzi, że ujawnianie zmiennych składowych niekoniecznie jest złym projektem, może to być po prostu kiepski styl. Jeśli jednak właściwości są tylko do odczytu, istnieje mniejsze prawdopodobieństwo nadużycia i skutków ubocznych.
Najlepszym podejściem, jakie mogę zapewnić w przypadku testów jednostkowych (i może się to wydawać dziwne), jest zapewnienie ochrony lub wewnętrznej jak największej liczby właściwości. Zapobiegnie to sprzężeniu i zniechęci do pisania głupich testów dla metod pobierających i ustawiających.
Istnieją oczywiste powody, dla których należy używać właściwości do odczytu / zapisu, takich jak właściwości ViewModel, które są powiązane z polami wejściowymi itp.
Praktycznie rzecz biorąc, testy jednostkowe powinny napędzać funkcjonalność metodami publicznymi. Jeśli testowany kod używa tych właściwości, otrzymujesz pokrycie kodu za darmo. Jeśli okaże się, że te właściwości nigdy nie zostaną wyróżnione przez pokrycie kodu, istnieje bardzo duże prawdopodobieństwo, że:
Jeśli piszesz testy dla metod pobierających i ustawiających, uzyskasz fałszywe poczucie pokrycia i nie będziesz w stanie określić, czy właściwości są rzeczywiście używane przez zachowanie funkcjonalne.
Jeśli cykliczna złożoność metody pobierającej i / lub ustawiającej wynosi 1 (a zazwyczaj są), to odpowiedź brzmi: nie, nie powinieneś.
Więc jeśli nie masz umowy SLA, która wymaga 100% pokrycia kodu, nie przejmuj się i skup się na testowaniu ważnego aspektu oprogramowania.
PS Pamiętaj, aby rozróżnić metody pobierające i ustawiające, nawet w językach takich jak C #, w których właściwości mogą wydawać się tym samym. Złożoność metody ustawiającej może być wyższa niż metoda pobierająca, a tym samym weryfikować test jednostkowy.
Zabawne , ale mądre ujęcie: The Way of Testivus
„Napisz test, który możesz dzisiaj”
Testowanie metod pobierających / ustawiających może być przesadą, jeśli jesteś doświadczonym testerem, a to jest mały projekt. Jeśli jednak dopiero zaczynasz uczyć się testowania jednostkowego lub te metody pobierające / ustawiające mogą zawierać logikę (jak w setMom()
przykładzie @ ArtB ), dobrym pomysłem byłoby napisanie testów.
To faktycznie był ostatnio temat między moim zespołem a mną. Kręcimy do 80% kodu. Mój zespół twierdzi, że metody pobierające i ustawiające są implementowane automatycznie, a kompilator generuje podstawowy kod za kulisami. W tym przypadku, biorąc pod uwagę, że wygenerowany kod jest nieinwazyjny, nie ma sensu testować kodu, który kompilator tworzy dla Ciebie. Mieliśmy również dyskusję na temat metod asynchronicznych iw takim przypadku kompilator generuje całą masę kodu za kulisami. To jest inny przypadek i coś, co testujemy. Krótka odpowiedź długa, omów ją ze swoim zespołem i sam zdecyduj, czy warto ją przetestować.
Ponadto, jeśli używasz raportu pokrycia kodu, tak jak my, jedną rzeczą, którą możesz zrobić, jest dodanie atrybutu [ExcludeFromCodeCoverage]. Nasze rozwiązanie polegało na użyciu tego dla modeli, które mają tylko właściwości korzystające z metod pobierających i ustawiających lub w samej właściwości. W ten sposób nie wpłynie to na procent całkowitego pokrycia kodu, gdy zostanie uruchomiony raport pokrycia kodu, przy założeniu, że właśnie tego używasz do obliczania procentowego pokrycia kodu. Miłego testowania!
Moim zdaniem pokrycie kodu to dobry sposób, aby sprawdzić, czy nie przegapiłeś żadnej funkcji, którą powinieneś uwzględnić.
Kiedy ręcznie sprawdzisz pokrycie pod kątem ładnego zabarwienia, można argumentować, że zwykłe metody pobierające i ustawiające nie muszą być testowane (chociaż zawsze to robię).
Kiedy sprawdzasz tylko procent pokrycia kodu w projekcie, procent pokrycia testowego, taki jak 80%, jest bez znaczenia. Możesz przetestować wszystkie nie logiczne części i zapomnieć o niektórych kluczowych częściach. W tym przypadku tylko 100% oznacza, że przetestowałeś cały kluczowy kod (a także cały kod nielogiczny). Jak tylko będzie to 99,9%, wiesz, że o czymś zapomniałeś.
Przy okazji: Pokrycie kodu to ostateczne sprawdzenie, czy klasa została w pełni przetestowana (jednostkowa). Jednak 100% pokrycie kodu niekoniecznie oznacza, że faktycznie przetestowałeś wszystkie funkcje tej klasy. Dlatego testy jednostkowe powinny być zawsze implementowane zgodnie z logiką klasy. W końcu sprawdzasz, czy o czymś zapomniałeś. Kiedy zrobiłeś to dobrze, za pierwszym razem trafiłeś w 100%.
Jeszcze jedno: pracując ostatnio w dużym banku w Holandii zauważyłem, że Sonar wskazał 100% pokrycie kodu. Jednak wiedziałem, że czegoś brakuje. Po sprawdzeniu procentów pokrycia kodu na plik wskazano, że plik ma niższy procent. Cały procent podstawowy kodu był tak duży, że jeden plik nie powodował wyświetlenia wartości procentowej jako 99,9%. Więc warto uważać na to ...
Zrobiłem małą analizę pokrycia uzyskanego w samym kodzie JUnit .
Jedna kategoria odkrytego kodu jest „zbyt prosta do przetestowania” . Obejmuje to proste metody pobierające i ustawiające, których twórcy JUnit nie robią testują.
Z drugiej strony JUnit nie ma żadnej (nie wycofanej) metody dłuższej niż 3 linie, która nie jest objęta żadnym testem.
Powiedziałbym: TAK Błędy w metodach getter / setter mogą po cichu wkradać się i powodować brzydkie błędy.
Napisałem bibliotekę, aby ułatwić to i kilka innych testów. Jedyne, co musisz napisać w swoich testach JUnit, to:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
Tak, zwłaszcza jeśli element do pobrania jest obiektem klasy podklasowej z klasy abstrakcyjnej. Twoje IDE może ostrzegać, ale nie musi, że określona właściwość nie została zainicjowana.
A potem niektóre pozornie niepowiązane testy powodują awarię z a NullPointerException
i zajmuje ci trochę czasu, zanim zorientujesz się, że właściwość gettable w rzeczywistości nie istnieje.
Chociaż nadal nie byłoby to tak złe, jak odkrycie problemu w produkcji.
Warto upewnić się, że wszystkie klasy abstrakcyjne mają konstruktory. Jeśli nie, test gettera może ostrzec Cię o problemie.
Jeśli chodzi o metody pobierające i ustawiające prymitywy, pytanie może brzmieć: Czy testuję swój program, czy też testuję JVM lub CLR? Ogólnie rzecz biorąc, JVM nie wymaga testowania.