Właśnie uczę się TDD. Rozumiem, że prywatne metody są nie do przetestowania i nie powinny się martwić, ponieważ publiczny interfejs API dostarczy wystarczających informacji do weryfikacji integralności obiektu.
Przez jakiś czas rozumiałem OOP. Rozumiem, że prywatne metody sprawiają, że obiekty są bardziej enkapsulowane, a przez to bardziej odporne na zmiany i błędy. Dlatego powinny być używane domyślnie i tylko te metody, które mają znaczenie dla klientów, powinny być upublicznione.
Cóż, mogę stworzyć obiekt, który ma tylko prywatne metody i wchodzi w interakcje z innymi obiektami, słuchając ich wydarzeń. Byłoby to bardzo zamknięte, ale całkowicie nie do przetestowania.
Ponadto uważanie za złe praktyki dodawania metod w celu testowania.
Czy to oznacza, że TDD stoi w sprzeczności z enkapsulacją? Jaka jest odpowiednia równowaga? Jestem skłonny upublicznić większość lub wszystkie moje metody ...
źródło
Odpowiedzi:
Wolę testowanie interfejsu niż testowanie implementacji.
Zależy to od środowiska programistycznego, patrz poniżej.
Zgadza się, TDD koncentruje się na testowaniu interfejsu.
Metody prywatne są szczegółami implementacji, które mogą ulec zmianie w dowolnym cyklu ponownego czynnikowania. Powinna istnieć możliwość ponownego uwzględnienia współczynnika bez zmiany interfejsu lub zachowania czarnej skrzynki . W rzeczywistości jest to część korzyści TDD, łatwość, z jaką można wygenerować pewność, że zmiany wewnętrzne w klasie nie wpłyną na użytkowników tej klasy.
Nawet jeśli klasa nie ma metod publicznych, to moduły obsługi zdarzeń są interfejsem publicznym i działają przeciwko temu interfejsowi publicznemu , który można przetestować.
Ponieważ zdarzenia są interfejsem, należy je wygenerować, aby przetestować ten obiekt.
Spójrz na użycie fałszywych obiektów jako kleju dla twojego systemu testowego. Powinno być możliwe utworzenie prostego obiektu próbnego, który generuje zdarzenie i odbiera wynikową zmianę stanu (możliwe przez inny obiekt próbny odbiornika).
Absolutnie powinieneś bardzo uważać na ujawnianie stanu wewnętrznego.
Absolutnie nie.
TDD nie powinno zmieniać implementacji twoich klas inaczej niż być może je uprościć (poprzez zastosowanie YAGNI z wcześniejszego punktu).
Najlepsza praktyka z TDD jest identyczna z najlepszą praktyką bez TDD, po prostu dowiedz się, dlaczego wcześniej, ponieważ używasz interfejsu podczas jego opracowywania.
Byłoby to raczej wylewanie dziecka z kąpielą.
Nie powinno się potrzeba , aby wszystkie metody publiczne, tak aby można rozwijać w sposób TDD. Zobacz moje notatki poniżej, aby sprawdzić, czy twoje prywatne metody naprawdę nie są sprawdzalne.
Bardziej szczegółowe spojrzenie na testowanie metod prywatnych
Jeśli bezwzględnie musisz przeprowadzić test jednostkowy prywatnego zachowania klasy, w zależności od języka / środowiska, możesz mieć trzy opcje:
Oczywiście trzecia opcja jest zdecydowanie najlepsza.
1) Umieść testy w klasie, którą chcesz przetestować (nie idealna)
Przechowywanie przypadków testowych w tym samym pliku klasy / źródłowym co testowany kod produkcyjny jest najprostszą opcją. Ale bez wielu dyrektyw lub adnotacji przedprocesowych skończy się to, że kod testowy niepotrzebnie rozszerzy kod produkcyjny, a w zależności od tego, jak zorganizujesz swój kod, możesz przypadkowo ujawnić wewnętrzną implementację użytkownikom tego kodu.
2) Ujawnij metody prywatne, które chcesz przetestować, jako metody publiczne (naprawdę nie jest to dobry pomysł)
Jak sugerowano, jest to bardzo zła praktyka, niszczy enkapsulację i ujawnia wewnętrzną implementację użytkownikom kodu.
3) Użyj lepszego środowiska testowego (najlepsza opcja, jeśli jest dostępna)
W świecie Eclipse 3. można osiągnąć za pomocą fragmentów . W świecie C # moglibyśmy używać klas częściowych . Inne języki / środowiska często mają podobną funkcjonalność, wystarczy ją znaleźć.
Ślepe założenie, że 1. lub 2. są jedynymi opcjami, prawdopodobnie spowodowałoby, że oprogramowanie produkcyjne byłoby nadęte kodem testowym lub nieprzyjemnymi interfejsami klasowymi, które publicznie piorą brudną bieliznę. * 8 ')
źródło
Oczywiście możesz mieć metody prywatne i oczywiście możesz je przetestować.
Albo jest jakiś sposób na uruchomienie prywatnej metody, w którym to przypadku możesz to przetestować, albo nie ma sposobu na uruchomienie prywatnej, w takim przypadku: dlaczego, do cholery, próbujesz ją przetestować, po prostu usuń to cholerstwo!
W twoim przykładzie:
Dlaczego miałoby to być niestabilne? Jeśli metoda zostanie wywołana w reakcji na zdarzenie, wystarczy, że test poda obiektowi odpowiednie zdarzenie.
Nie chodzi o brak prywatnych metod, chodzi o to, by nie zerwać enkapsulacji. Możesz mieć metody prywatne, ale powinieneś je przetestować za pomocą publicznego interfejsu API. Jeśli publiczny interfejs API jest oparty na zdarzeniach, użyj zdarzeń.
W przypadku bardziej powszechnego przypadku metod prywatnych pomocników można je przetestować za pomocą publicznych metod, które je wywołują. W szczególności, ponieważ możesz pisać tylko kod, aby przejść test zakończony niepowodzeniem, a twoje testy testują publiczny interfejs API, cały nowy kod, który piszesz, zwykle będzie publiczny. Metody prywatne pojawiają się tylko w wyniku refaktoryzacji metody wyodrębniania , gdy są wyciągane z już istniejącej metody publicznej. Ale w takim przypadku oryginalny test, który testuje metodę publiczną, obejmuje również metodę prywatną, ponieważ metoda publiczna wywołuje metodę prywatną.
Tak więc zwykle prywatne metody pojawiają się tylko wtedy, gdy są wyodrębnione z już przetestowanych metod publicznych, a zatem również są już przetestowane.
źródło
internal
metody lub metody publiczne winternal
klasach powinny być testowane bezpośrednio dość często. Na szczęście .net obsługuje tęInternalsVisibleToAttribute
metodę, ale bez niej testowanie tej metody byłoby PITA.Kiedy tworzysz nową klasę w swoim kodzie, robisz to, aby odpowiedzieć na niektóre wymagania. Wymagania mówią, co musi zrobić kod, a nie jak . Ułatwia to zrozumienie, dlaczego większość testów odbywa się na poziomie metod publicznych.
Poprzez testy weryfikujemy, czy kod działa zgodnie z oczekiwaniami , generuje odpowiednie wyjątki, gdy jest oczekiwany itp. Naprawdę nie obchodzi nas, w jaki sposób kod jest implementowany przez programistę. Chociaż nie obchodzi nas implementacja, czyli sposób, w jaki kod robi to, co robi, warto unikać testowania metod prywatnych.
Jeśli chodzi o testowanie klas, które nie mają żadnych publicznych metod i wchodzą w interakcję ze światem zewnętrznym tylko poprzez zdarzenia, możesz to również przetestować, wysyłając, poprzez testy, zdarzenia i słuchając odpowiedzi. Na przykład, jeśli klasa musi zapisać plik dziennika za każdym razem, gdy odbierze zdarzenie, test jednostkowy wyśle zdarzenie i sprawdzi, czy plik dziennika został zapisany.
Wreszcie, w niektórych przypadkach testowanie metod prywatnych jest całkowicie poprawne. Dlatego na przykład w .NET możesz testować nie tylko klasy publiczne, ale także prywatne, nawet jeśli rozwiązanie nie jest tak proste jak w przypadku metod publicznych.
źródło
Nie zgadzam się z tym stwierdzeniem lub powiedziałbym, że nie testujesz metod prywatnych bezpośrednio . Metoda publiczna może wywoływać różne metody prywatne. Być może autor chciał mieć „małe” metody i rozpakować część kodu w sprytnie nazwaną metodę prywatną.
Bez względu na sposób pisania metody publicznej kod testowy powinien obejmować wszystkie ścieżki. Jeśli po testach odkryjesz, że jedna z instrukcji gałęzi (if / switch) w jednej prywatnej metodzie nigdy nie była objęta twoimi testami, oznacza to, że masz problem. Albo przegapiłeś przypadek i implementacja jest prawidłowa LUB implementacja jest niepoprawna, a gałąź ta nigdy nie powinna istnieć.
Dlatego często używam Cobertura i NCover, aby upewnić się, że mój test metody publicznej obejmuje również metody prywatne. Nie krępuj się pisać dobrych obiektów OO metodami prywatnymi i nie pozwól, aby TDD / Testowanie poszło ci na drodze w takich sprawach.
źródło
Twój przykład jest nadal doskonale testowalny, dopóki używasz wstrzykiwania zależności w celu zapewnienia instancji, z którymi współdziała Twój CUT. Następnie możesz użyć makiety, wygenerować interesujące zdarzenia, a następnie obserwować, czy CUT podejmuje właściwe działania w zależności od swoich zależności.
Z drugiej strony, jeśli masz język z dobrą obsługą zdarzeń, możesz pójść nieco inną ścieżką. Nie podoba mi się, gdy obiekty same subskrybują zdarzenia, zamiast tego mam fabrykę, która tworzy obiekt, łącząc zdarzenia z publicznymi metodami obiektu. Łatwiej jest przetestować i sprawia, że zewnętrznie widać, na jakie zdarzenia należy przetestować CUT.
źródło
Nie powinieneś rezygnować z prywatnych metod. Korzystanie z nich jest całkowicie uzasadnione, ale z perspektywy testowania trudniej jest przetestować bezpośrednio, bez przerywania enkapsulacji lub dodawania kodu specyficznego dla testów do twoich klas. Sztuką jest zminimalizowanie rzeczy, o których wiesz, że powodują skurcze jelit, ponieważ czujesz, jakbyś zabrudził kod.
To są rzeczy, o których pamiętam, aby spróbować osiągnąć równowagę.
Pomyśl bocznie. Dbaj o to, by twoje zajęcia były małe, a metody mniejsze, i używaj dużej ilości kompozycji. To brzmi jak więcej pracy, ale w końcu skończysz z bardziej indywidualnie testowanymi przedmiotami, twoje testy będą prostsze, będziesz mieć więcej opcji korzystania z prostych prób w miejsce prawdziwych, dużych i złożonych obiektów, miejmy nadzieję, że dobrze- podzielony na fragmenty i luźno powiązany kod, a co ważniejsze, dajesz sobie więcej opcji. Utrzymywanie drobnych rzeczy zazwyczaj oszczędza Twój czas, ponieważ zmniejszasz liczbę rzeczy, które musisz indywidualnie sprawdzać w każdej klasie, i naturalnie redukujesz spaghetti z kodem, które czasami mogą się zdarzyć, gdy klasa staje się duża i ma dużo współzależne zachowanie kodu wewnętrznie.
źródło
Jak ten obiekt reaguje na te zdarzenia? Przypuszczalnie musi wywoływać metody na innych obiektach. Możesz to przetestować, sprawdzając, czy te metody zostaną wywołane. Wywołaj obiekt próbny, a następnie możesz łatwo stwierdzić, że robi to, czego oczekujesz.
Problem polega na tym, że chcemy jedynie przetestować interakcję obiektu z innymi obiektami. Nie obchodzi nas, co dzieje się wewnątrz obiektu. Więc nie, nie powinieneś mieć więcej publicznych metod niż wcześniej.
źródło
Walczyłem również z tym samym problemem. Naprawdę, sposób na obejście tego jest następujący: w jaki sposób oczekujesz, że reszta twojego programu będzie się komunikować z tą klasą? Sprawdź swoją klasę odpowiednio. Zmusi cię to do zaprojektowania klasy w oparciu o sposób, w jaki reszta programu się z nią łączy, i w rzeczywistości zachęci do enkapsulacji i dobrego zaprojektowania twojej klasy.
źródło
Zamiast prywatnego użytku domyślny modyfikator. Następnie możesz przetestować te metody indywidualnie, a nie tylko w połączeniu z metodami publicznymi. Wymaga to, aby twoje testy miały taką samą strukturę pakietu jak główny kod.
źródło
internal
w .net.Kilka prywatnych metod zwykle nie stanowi problemu. Po prostu testujesz je za pomocą publicznego interfejsu API, tak jakby kod był wprowadzony do twoich publicznych metod. Nadmiar prywatnych metod może być oznaką słabej spójności. Twoja klasa powinna ponosić jedną spójną odpowiedzialność i często ludzie wybierają metody prywatne, aby wyglądać na spójne tam, gdzie tak naprawdę ich nie ma.
Na przykład może istnieć moduł obsługi zdarzeń, który wykonuje wiele wywołań bazy danych w odpowiedzi na te zdarzenia. Ponieważ oczywistą złą praktyką jest tworzenie instancji modułu obsługi zdarzeń w celu wykonywania wywołań bazy danych, istnieje pokusa, aby wszystkie wywołania związane z bazą danych były metodami prywatnymi, kiedy naprawdę należy je rozdzielić do osobnej klasy.
źródło
TDD nie stoi w sprzeczności z enkapsulacją. Weź najprostszy przykład metody lub właściwości gettera, w zależności od wybranego języka. Powiedzmy, że mam obiekt klienta i chcę, aby zawierał pole identyfikatora. Pierwszy test, który zamierzam napisać, to taki, który mówi coś w stylu „customer_id_initializes_to_zero”. Definiuję moduł pobierający do zgłaszania niezaimplementowanego wyjątku i obserwowania, jak test się nie powiódł. Następnie najprostszą rzeczą, jaką mogę zrobić, aby przejść test, jest to, aby getter zwrócił zero.
Stamtąd przechodzę do innych testów, prawdopodobnie tych, w których identyfikator klienta jest rzeczywistą, funkcjonalną dziedziną. W pewnym momencie prawdopodobnie muszę utworzyć prywatne pole, z którego korzysta klasa klienta, aby śledzić, co powinien zwrócić moduł pobierający. Jak dokładnie to śledzić? Czy to proste int? Czy mogę śledzić ciąg, a następnie przekonwertować go na int? Czy śledzę 20 ints i je uśredniam? Świat zewnętrzny to nie obchodzi - a twoje testy TDD nie obchodzą. To jest zamknięty szczegół.
Myślę, że nie zawsze jest to od razu oczywiste przy uruchamianiu TDD - nie testujesz, jakie metody działają wewnętrznie - testujesz mniej szczegółowe obawy klasy. Więc nie chcesz testować tej metody, która
DoSomethingToFoo()
tworzy instancję Bar, wywołuje na niej metodę, dodaje dwie do jednej z jej właściwości itp. Testujesz, że po zmutowaniu stanu obiektu zmienił się niektóre akcesorium stanu (albo nie). Oto ogólny wzór twoich testów: „kiedy wykonam X na testowanej klasie, mogę później obserwować Y”. Sposób, w jaki dostaje się do Y, nie jest przedmiotem zainteresowania testów, i to właśnie jest zamknięte i dlatego TDD nie stoi w sprzeczności z kapsułkowaniem.źródło
Unikaj używania? Nie.
Unikaj rozpoczynania od ? Tak.
Zauważyłem, że nie pytałeś o to, czy możesz mieć abstrakcyjne klasy z TDD; jeśli zrozumiesz, jak powstają klasy abstrakcyjne podczas TDD, ta sama zasada dotyczy również metod prywatnych.
Nie możesz bezpośrednio testować metod w klasach abstrakcyjnych, tak jak nie możesz bezpośrednio testować metod prywatnych, ale dlatego nie zaczynasz od klas abstrakcyjnych i metod prywatnych; zaczynasz od konkretnych klas i publicznych interfejsów API, a następnie refaktoryzujesz wspólną funkcjonalność.
źródło