Czy testy jednostkowe powinny być napisane dla metod pobierających i ustawiających?

141

Czy mamy pisać testy dla naszych metod pobierających i ustawiających, czy jest to przesada?

Hector421
źródło
Nie sądzę. Nie powinieneś pisać przypadków testowych dla getter / setter.
Harry Joy
1
Zakładam, że masz na myśli Javę? Jest to szczególnie dotkliwe pytanie w przypadku języka Java, a tym bardziej w przypadku bardziej nowoczesnych języków.
skaffman
@skaffman Jakie współczesne języki nie mają właściwości? Jasne, języki takie jak Java wymagają, aby były one pełnymi treściami metod, ale to nie sprawia, że ​​logicznie różni się od, powiedzmy, C #.
Claus Jørgensen
2
@Claus: Nie powiedział o właściwościach, powiedział o getterach i ustawiaczach. W java piszesz je ręcznie, w innych językach otrzymujesz lepsze wsparcie.
skaffman
2
Można by zapytać, po co w ogóle mają gettery i setery .
Raedwald

Odpowiedzi:

180

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.

skaffman
źródło
7
Dążenie do 100% pokrycia kodu jest głupie. Skończysz na pokryciu kodu, który nie jest ujawniony publicznie i / lub kod bez złożoności. Takie rzeczy najlepiej pozostawić dla autogeneracji, ale nawet testy autogenerowane są dość bezcelowe. Lepiej skupić się na ważnych testach, takich jak testy integracyjne.
Claus Jørgensen
7
@Claus: Zgadzam się poza tym, że skupiam się na testowaniu integracji. Testy jednostkowe mają kluczowe znaczenie dla dobrego projektu i uzupełniają testy integracyjne.
skaffman
5
Myślę, że 100% pokrycia kodu, gdy uruchomiony jest cały zestaw testów, jest dobrym celem. Metody ustawiające właściwości i inny kod są uruchamiane w ramach większych testów. Ostatnie elementy do omówienia to prawdopodobnie obsługa błędów. A jeśli obsługa błędów nie jest objęta testami jednostkowymi, nigdy nie zostanie omówiona. Czy naprawdę chcesz wysłać produkt zawierający kod, który nigdy nie był uruchamiany?
Anders Abel
Nie zgadzam się z tą radą. Tak, metody pobierające i ustawiające są proste, ale są również zaskakująco łatwe do zepsucia. Kilka linijek prostych testów i wiesz, że działają i nadal działają.
Charles
Możesz testować niepotrzebne setery, które stworzyłeś (lub narzędzie) tylko po to, aby mieć „zaokrąglone” POJO. Możesz na przykład używać Gson.fromJson do "nadmuchiwania" POJOS (nie są potrzebne żadne setery). W tym przypadku moim wyborem jest usunięcie nieużywanych ustawiaczy.
Alberto Gaona
36

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.

azhidkov
źródło
1
Biorąc pod uwagę, jak mało czasu zajmuje ich przetestowanie i prawdopodobieństwo dodania jakiegoś zachowania, nie rozumiem, dlaczego nie. Podejrzewam, że jeśli zostanie naciśnięty, mógłby odpowiedzieć „cóż, przypuszczam, dlaczego nie”.
Sanki
1
Ważne wczesne książki często zawierają złe rady. Widziałem wiele przypadków, w których ludzie szybko wrzucają gettery i setery i popełniają błędy, ponieważ wycinają i wklejają lub zapominają o tym. lub to-> lub coś takiego. Są łatwe do przetestowania i na pewno powinny zostać przetestowane.
Charles
35

Głośne TAK z TDD


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.

DDD w TDD

TDD jest często cytowane jako posiadające główne zalety.

  • Obrona
    • Zapewnienie, że kod może się zmienić, ale nie jego zachowanie .
    • Pozwala to na tak ważną praktykę refaktoryzacji .
    • Zdobywasz ten TDD lub nie.
  • Projekt
    • Ci określić, co należy zrobić coś, jak należy to zachowuje się przed wprowadzeniem go.
    • Często oznacza to bardziej świadome decyzje wdrożeniowe .
  • Dokumentacja
    • Zestaw testów powinien służyć jako dokumentacja specyfikacji (wymagań).
    • Korzystanie z testów w tym celu oznacza, że ​​dokumentacja i implementacja są zawsze w stanie spójnym - zmiana na jedno oznacza zmianę na inną. Porównaj z zachowaniem wymagań i projektu w osobnym dokumencie tekstowym.

Oddzielna odpowiedzialność od wdrożenia

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.

Brak inżynierii w obie strony ...

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.

... i jak TDD to rozwiązuje

Jest to część dokumentacyjna TDD, która zapewnia, że ​​specyfikacje systemu i jego kodu są zawsze spójne.

Najpierw projektuj, później wdrażaj

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.

Izhaki
źródło
2
To tylko jedna strona medalu. Pomyśl o rzeczach, które mógłbyś zrobić, zamiast testować tylko dla samego testowania. Jak powiedział Kent Beck, otrzymujesz wynagrodzenie za działający kod, a nie za działające testy.
Georgii Oleinikov
@GeorgiiOleinikov Czy przeczytałeś moją inną odpowiedź poniżej? Jest to zgodne z Twoim zdaniem.
Izhaki
21

tl; dr: Tak , powinieneś , a przy OpenPojo to trywialne.

  1. 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.

  2. 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.

  3. 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 ).

  4. 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.

  5. 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 );
Sanki
źródło
11
Wiem, że to jest teraz przestarzałe, ale: CZY POWINIENEŚ wykonywać walidację w swoich getterach i seterach? Miałem wrażenie, że setMom powinna robić to, co mówi. Jeśli sprawdza, czy nie powinien to być validateAndSetMom? Albo lepiej, czy kod walidacyjny nie powinien istnieć GDZIEKOLWIEK niż prosty obiekt? Czego tu brakuje?
ndtreviv
14
Tak, zawsze powinieneś sprawdzać swoje dane wejściowe. Jeśli nie, to dlaczego nie użyć po prostu zmiennej publicznej? Staje się tym samym. Cała korzyść z używania seterów w porównaniu ze zmiennymi polega na tym, że pozwala ci upewnić się, że twój obiekt nigdy nie jest nieprawidłowy, na przykład wiek jest liczbą ujemną. Jeśli nie zrobisz tego w obiekcie, zrobisz to gdzie indziej (jak obiekt boga „usługi” ), a to nie jest tak naprawdę OOP w tym momencie.
Sanki,
1
Cóż, to prawdopodobnie najważniejszy punkt mojego tygodnia. Dziękuję Ci.
Sled
19

Tak, ale nie zawsze w izolacji

Pozwól mi rozwinąć:

Co to jest test jednostkowy?

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 .

Co to jest dobry test?

Wszystkie wymagania i testy są zgodne z logiką Hoare'a :

{P} C {Q}

Gdzie:

  • {P}jest warunkiem wstępnym ( podanym )
  • Cjest 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 Cosiąga się warunek {Q}końcowy , powinieneś sprawdzić, czy jest to wynikiem C.

Jeśli chodzi o OOP, Cto klasa. Nie powinieneś więc testować efektów wewnętrznych, tylko efekty zewnętrzne.

Dlaczego nie przetestować osobno pochłaniaczy i ustawiaczy fasoli

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 setVATzgłosił wyjątek, odpowiedni test byłby odpowiedni, ponieważ teraz występuje efekt zewnętrzny.

Jak należy testować metody pobierające i ustawiające?

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 nie setVAT() .

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.

Izhaki
źródło
13

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:

  1. Brakuje testów, które pośrednio używają właściwości
  2. Właściwości są nieużywane

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.

bryanbcook
źródło
12

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.

Claus Jørgensen
źródło
4
Chociaż należy przetestować kopiowanie obronne na getterach
Sled
8

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.

muymoo
źródło
1
Twój link powinien wskazywać na: The Way of Testivus
JJS,
2

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!

Devin C.
źródło
2

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 ...

Radboud
źródło
1

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.

avandeursen
źródło
1

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())));

-> https://github.com/Mixermachine/base-test

Aaron Dietz
źródło
0

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 NullPointerExceptioni 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.

Alonso del Arte
źródło