Jak uniknąć konieczności testowania prywatnych metod Jednostki

15

Wiem, że nie powinieneś testować prywatnych metod, a jeśli wygląda na to, że musisz, może tam być klasa, która czeka na wyjście.

Ale nie chcę mieć klas gazillionowych tylko po to, abym mógł przetestować ich publiczne interfejsy i stwierdzam, że w przypadku wielu klas, jeśli tylko przetestuję publiczne metody, w końcu będę musiał kpić z wielu zależności, a testy jednostkowe są ogromne i trudne do naśladowania.

Wolę kpić z prywatnych metod podczas testowania metod publicznych i kpić z zewnętrznych zależności podczas testowania metod prywatnych.

Czy jestem szalony?

Fran Sevillano
źródło
Dokładny test jednostkowy powinien domyślnie obejmować wszystkich prywatnych członków danej klasy, ponieważ chociaż nie można wywoływać ich bezpośrednio, ich zachowanie nadal będzie miało wpływ na wynik. Jeśli nie, to dlaczego w ogóle są? Pamiętaj, że w testach jednostkowych najważniejszy jest wynik, a nie sposób jego uzyskania.
GordonM,

Odpowiedzi:

24

Częściowo masz rację - nie powinieneś bezpośrednio testować prywatnych metod. Metody prywatne w klasie powinny być wywoływane przez jedną lub więcej metod publicznych (być może pośrednio - metoda prywatna wywoływana przez metodę publiczną może wywoływać inne metody prywatne). Dlatego podczas testowania metod publicznych przetestujesz również metody prywatne. Jeśli masz prywatne metody, które pozostają nieprzetestowane, twoje przypadki testowe są niewystarczające lub metody prywatne są nieużywane i można je usunąć.

Jeśli stosujesz podejście do testowania białych skrzynek, powinieneś wziąć pod uwagę szczegóły implementacji metod prywatnych podczas konstruowania testów jednostkowych wokół metod publicznych. Jeśli przyjmujesz podejście „czarnej skrzynki”, nie powinieneś testować żadnych szczegółów implementacyjnych ani w metodach publicznych, ani prywatnych, ale pod kątem oczekiwanego zachowania.

Osobiście wolę podejście oparte na białej skrzynce niż testy jednostkowe. Potrafię tworzyć testy, aby przetestować metody i klasy w różnych stanach, które powodują interesujące zachowanie w moich metodach publicznych i prywatnych, a następnie twierdzę, że wyniki są takie, jakich się spodziewam.

Więc - nie kpij z prywatnych metod. Skorzystaj z nich, aby zrozumieć, co musisz przetestować, aby zapewnić dobry zasięg oferowanych funkcji. Jest to szczególnie prawdziwe na poziomie testu jednostkowego.

Thomas Owens
źródło
1
„nie kpij swoim prywatnym metodom” tak, rozumiem testowanie, kiedy kpisz z nich, być może przekroczyłeś cienką linię do szaleństwa
Ewan
Tak, ale jak już powiedziałem, moim problemem z testowaniem białych skrzynek jest to, że zwykle kpienie ze wszystkich zależności dla metod publicznych i prywatnych tworzy naprawdę duże metody testowania. Wszelkie pomysły, jak rozwiązać ten problem?
Fran Sevillano
8
@FranSevillano Jeśli musisz tak często kpić lub kpić, przyjrzałbym się twojemu ogólnemu projektowi. Coś jest nie tak.
Thomas Owens
Pomaga w tym narzędzie do pokrywania kodu.
Andy
5
@FranSevillano: Nie ma wielu dobrych powodów, dla których klasa robi jedną zależność od mnóstwa zależności. Jeśli masz mnóstwo zależności, prawdopodobnie masz klasę Boga.
Mooing Duck
4

Myślę, że to bardzo zły pomysł.

Problem z tworzeniem testów jednostkowych członków prywatnych polega na tym, że źle pasuje do cyklu życia produktu.

Powodem, dla którego WYBRALIŚCIE, aby te metody były prywatne, jest to, że nie są one kluczowe dla tego, co starają się zrobić wasze klasy - są jedynie pomocnikami w tym, jak obecnie wdrażacie tę funkcjonalność. Podczas refaktoryzacji te prywatne dane prawdopodobnie zmienią się i spowodują tarcie w wyniku refaktoryzacji.

Kluczową różnicą między członkami publicznymi i prywatnymi jest to, że powinieneś dokładnie przemyśleć swój publiczny interfejs API, dobrze go udokumentować i dobrze sprawdzić (twierdzenia itp.). Ale z prywatnym API nie byłoby sensu myśleć tak ostrożnie (zmarnowany wysiłek, ponieważ jego użycie jest tak zlokalizowane). Umieszczenie metod prywatnych w testach jednostkowych sprowadza się do tworzenia zewnętrznych zależności od tych metod. Oznacza to, że API musi być stabilny i dobrze udokumentowany (ponieważ ktoś musi dowiedzieć się, dlaczego te testy jednostkowe zakończyły się niepowodzeniem, jeśli / kiedy to robią).

Sugeruje Ci:

  • RETHINK, czy te metody powinny być prywatne
  • Napisz testy jednorazowe (aby upewnić się, że są teraz poprawne, tymczasowo upublicznij je, aby móc przetestować, a następnie usuń test)
  • Wbudowane testowanie warunkowe w implementacji za pomocą #ifdef i asercji (testy są wykonywane tylko w kompilacjach debugowania).

Dziękujemy za chęć przetestowania tej funkcji i trudność w przetestowaniu jej za pomocą bieżącego publicznego interfejsu API. Ale osobiście cenię modułowość bardziej niż zasięg testowy.

Lewis Pringle
źródło
6
Nie zgadzam się. IMO, jest to w 100% poprawne dla testów integracyjnych. Ale w testach jednostkowych sprawy wyglądają inaczej; Celem testów jednostkowych jest wskazanie, gdzie jest błąd, wystarczająco wąski, aby można go było szybko naprawić. Często znajduję się w takiej sytuacji: mam bardzo mało publicznych metod, ponieważ tak naprawdę jest to podstawowa wartość moich zajęć (tak jak powiedziałeś, że należy to zrobić). Jednak unikam pisania 400 metod liniowych, ani publicznych, ani prywatnych. Tak więc moje nieliczne metody publiczne mogą osiągnąć swój cel jedynie przy pomocy dziesiątek metod prywatnych. To za dużo kodu, aby „szybko to naprawić”. Muszę uruchomić debuger itp.
marstato,
6
@marstato: sugestia: najpierw zacznij pisać testy i zmień zdanie na temat unittests: nie znajdują błędów, ale sprawdzają, czy kod działa zgodnie z zamierzeniami programisty.
Timothy Truckle,
@marstato Dziękujemy! Tak. Testy zawsze kończą się pierwszym razem, gdy rejestrujesz rzeczy (inaczej byś tego nie zrobił!). Są przydatne, ponieważ ewoluujesz kod, dając ci przewagę, gdy coś psujesz, a jeśli masz DOBRE testy regresji, to dają ci KOMFORT / WSKAZÓWKI na temat tego, gdzie szukać problemów (nie w tym, co jest stabilne i dobrze przetestowana regresja).
Lewis Pringle
@marstato „Celem testów jednostkowych jest wskazanie, gdzie znajduje się błąd” - właśnie takie nieporozumienie prowadzi do pytania PO. Celem testów jednostkowych jest weryfikacja zamierzonego (i najlepiej udokumentowanego) zachowania interfejsu API.
StackOverthrow
4
@marstato Nazwa „test integracji” pochodzi od testowania, czy wiele komponentów współpracuje ze sobą (tzn. czy integrują się prawidłowo). Testowanie jednostkowe polega na testowaniu, czy pojedynczy komponent wykonuje to, co powinien robić w izolacji, co w zasadzie oznacza, że ​​jego publiczny interfejs API działa zgodnie z dokumentacją / wymaganiami. Żaden z tych terminów nie mówi nic o tym, czy uwzględnisz testy wewnętrznej logiki implementacji w ramach zapewnienia, że ​​integracja działa, czy też działa pojedyncza jednostka.
Ben
3

UnitTests testują obserwowalne zachowanie publiczne , a nie kod, gdzie „public” oznacza: zwracane wartości i komunikację z zależnościami.

„Jednostka” to dowolny kod, który rozwiązuje ten sam problem (a ściślej: ma ten sam powód do zmiany). Może to być pojedyncza metoda lub kilka klas.

Głównym powodem, dla którego nie chcesz testować, private methodssą: są to szczegóły implementacji i możesz je zmienić podczas refaktoryzacji ( popraw kod, stosując zasady OO bez zmiany funkcjonalności). To jest dokładnie wtedy, gdy nie chcesz, aby twoje najgroźniejsze osoby zmieniły się, aby zagwarantować, że zachowanie twojego CuT nie zmieniło się podczas refaktoryzacji.

Ale nie chcę mieć klas gazillionowych tylko po to, abym mógł przetestować ich publiczne interfejsy i stwierdzam, że w przypadku wielu klas, jeśli tylko przetestuję publiczne metody, w końcu będę musiał kpić z wielu zależności, a testy jednostkowe są ogromne i trudne do naśladowania.

Zwykle odczuwam coś przeciwnego: im mniejsze klasy (im mniejsza odpowiedzialność), tym mniej mają zależności, a tym łatwiejsze są zarówno pisanie, jak i czytanie.

Idealnie zastosujesz ten sam poziom wzoru abstrakcji do swoich klas. Oznacza to, że twoje klasy albo zapewniają pewną logikę biznesową (najlepiej jako „czyste funkcje” działające tylko na ich parametrach bez zachowania własnego stanu) (x), albo wywołują metody na innych obiektach, a nie jednocześnie.

W ten sposób unikanie zachowań biznesowych jest bułką z masłem, a obiekty „delegowania” są zwykle zbyt proste, aby upaść (brak rozgałęzień, bez zmiany stanu), dzięki czemu nie jest wymagane nieprzekraczanie, a ich testowanie można pozostawić do testów integracyjnych lub modułowych

Timothy Truckle
źródło
1

Testowanie jednostkowe ma pewną wartość, gdy jesteś jedynym programistą pracującym nad kodem, szczególnie jeśli aplikacja jest bardzo duża lub bardzo złożona. Testowanie jednostkowe staje się niezbędne, gdy większa liczba programistów pracuje na tej samej bazie kodu. Koncepcja testów jednostkowych została wprowadzona w celu rozwiązania niektórych trudności związanych z pracą w tych większych zespołach.

Powodem, dla którego testy jednostkowe pomagają większym zespołom, są umowy. Jeśli mój kod wywołuje kod napisany przez kogoś innego, przyjmuję założenia o tym, co zrobi kod innej osoby w różnych sytuacjach. Pod warunkiem, że te założenia są nadal prawdziwe, mój kod będzie nadal działał, ale skąd mam wiedzieć, które założenia są prawidłowe i skąd mam wiedzieć, kiedy te założenia się zmieniły?

W tym momencie pojawiają się testy jednostkowe. Autor klasy tworzy testy jednostkowe w celu udokumentowania oczekiwanego zachowania ich klasy. Test jednostkowy określa wszystkie prawidłowe sposoby korzystania z klasy, a uruchomienie testu jednostkowego sprawdza, czy przypadki użycia działają zgodnie z oczekiwaniami. Inny programista, który chce skorzystać z twojej klasy, może przeczytać testy jednostkowe, aby zrozumieć zachowanie, którego mogą oczekiwać w twojej klasie, i wykorzystać to jako podstawę do swoich założeń dotyczących działania twojej klasy.

W ten sposób publiczne podpisy metody klasy i testy jednostkowe razem tworzą umowę między autorem klasy a innymi programistami, którzy używają tej klasy w swoim kodzie.

W tym scenariuszu co się stanie, jeśli włączysz testowanie metod prywatnych? Oczywiście nie ma to sensu.

Jeśli jesteś jedynym programistą pracującym nad kodem i chcesz użyć testowania jednostkowego jako sposobu debugowania kodu, nie widzę w tym żadnej szkody, to tylko narzędzie i możesz go używać w dowolny sposób, który działa dla Ty, ale to nie był powód, dla którego wprowadzono testy jednostkowe i nie zapewnia ono głównych korzyści z testów jednostkowych.

bikeman868
źródło
Walczę z tym, wyśmiewając się z zależności, tak że jeśli jakakolwiek zależność zmieni swoje zachowanie, mój test nie zawiedzie. Czy ta awaria ma wystąpić w teście integracji, a nie w teście jednostkowym?
Todd
Makieta ma zachowywać się identycznie jak rzeczywista implementacja, ale nie ma zależności. Jeśli rzeczywista implementacja ulegnie zmianie i będą się teraz różnić, pojawi się to jako błąd testu integracji. Osobiście uważam, że opracowanie próbnych sztuczek i rzeczywistej implementacji to jedno zadanie. Nie buduję próbnych elementów w ramach pisania testów jednostkowych. W ten sposób, gdy zmieniam zachowanie klas i zmieniam próbę, aby dopasować, uruchomienie testów jednostkowych zidentyfikuje wszystkie pozostałe klasy, które zostały uszkodzone przez tę zmianę.
bikeman868
1

Przed udzieleniem odpowiedzi na takie pytanie musisz zdecydować, co naprawdę chcesz osiągnąć.

Piszesz kod. Masz nadzieję, że spełni swój kontrakt (innymi słowy, zrobi to, co powinien. Zapisanie tego, co ma zrobić, jest dla niektórych ogromnym krokiem naprzód).

Aby mieć uzasadnione przekonanie, że kod robi to, co powinien, albo wpatrujesz się w niego wystarczająco długo, albo piszesz kod testowy, który testuje wystarczającą liczbę przypadków, aby cię przekonać „jeśli kod przejdzie wszystkie te testy, to jest poprawny”.

Często interesuje Cię tylko publicznie zdefiniowany interfejs jakiegoś kodu. Jeśli używam biblioteki, nie obchodzi mnie, jak to działa poprawnie wykonana, tylko że nie działa poprawnie. Weryfikuję poprawność Twojej biblioteki, przeprowadzając testy jednostkowe.

Ale tworzysz bibliotekę. Prawidłowe działanie może być trudne. Powiedzmy, że dbam tylko o to, aby biblioteka poprawnie wykonywała operację X, więc mam test jednostkowy dla X. Ty, twórca odpowiedzialny za utworzenie biblioteki, wdrażasz X, łącząc kroki A, B i C, z których każdy jest zupełnie nietrywialny. Aby biblioteka działała, dodajesz testy, aby sprawdzić, czy A, B i C działają poprawnie. Chcesz te testy. Mówienie „nie powinieneś mieć testów jednostkowych dla metod prywatnych” jest zupełnie bezcelowe. Ci chcą testy dla tych metod prywatnych. Może ktoś mówi ci, że testowanie metod prywatnych jest niepoprawne. Ale to oznacza tylko, że możesz nie nazwać ich „testami jednostkowymi”, ale „testami prywatnymi” lub jakkolwiek chcesz je nazwać.

Język Swift rozwiązuje problem polegający na tym, że nie chcesz ujawniać A, B, C jako metod publicznych tylko dlatego, że chcesz go przetestować, nadając funkcjom atrybut „testowalny”. Kompilator umożliwia wywoływanie prywatnych metod testowych z testów jednostkowych, ale nie z kodu nie testowego.

gnasher729
źródło
0

Tak, zwariowałeś .... JAK FOX!

Istnieje kilka sposobów testowania metod prywatnych, z których niektóre zależą od języka.

  • odbicie! zasady są po to, żeby je łamać!
  • uczyń ich chronionymi, dziedziczącymi i zastępującymi
  • przyjaciel / InternalsVisibleTo na zajęcia

Ogólnie rzecz biorąc, jeśli chcesz przetestować metody prywatne, prawdopodobnie chcesz przenieść je do metod publicznych w zależności i przetestować / wstrzyknąć.

Ewan
źródło
Nie wiem, w moich oczach wyjaśniłeś, że to nie jest dobry pomysł, ale nadal odpowiadałeś na pytanie
Liath
Myślałem, że mam wszystkie bazy:
Ewan
3
Głosowałem za tym, ponieważ pomysł ujawnienia metod prywatnego pomocnika publicznemu interfejsowi API tylko w celu ich przetestowania jest szalony i zawsze tak uważałem.
Robert Harvey
0

Pozostań pragmatyczny. Testowanie specjalnych przypadków w metodach prywatnych poprzez ustawienie stanu instancji i parametrów metody publicznej w taki sposób, że przypadki te się tam zdarzają, jest często zbyt skomplikowane.

Dodam dodatkowy internalakcesor (wraz z flagą InternalsVisibleTozestawu testowego), wyraźnie nazwany DoSomethingForTesting(parameters), aby przetestować te „prywatne” metody.

Oczywiście implementacja może się nieco zmienić, a testy, w tym akcesoria testowe, stają się przestarzałe. To wciąż lepsze niż nieprzetestowane przypadki lub nieczytelne testy.

Bernhard Hiller
źródło