TDD Red-Green-Refactor i czy / jak testować metody, które stają się prywatne


91

o ile rozumiem, większość ludzi zgadza się, że prywatne metody nie powinny być testowane bezpośrednio, ale raczej za pomocą jakichkolwiek publicznych metod, które je nazywają. Rozumiem ich punkt widzenia, ale mam z tym pewne problemy, gdy próbuję postępować zgodnie z „Trzema prawami TDD” i stosować cykl „Czerwony - zielony - refaktor”. Myślę, że najlepiej to wyjaśnić przykładem:

W tej chwili potrzebuję programu, który może odczytać plik (zawierający dane rozdzielone tabulatorami) i odfiltrować wszystkie kolumny zawierające dane nienumeryczne. Sądzę, że prawdopodobnie są już dostępne proste narzędzia, ale sam zdecydowałem się na wdrożenie od podstaw, głównie dlatego, że pomyślałem, że może to być fajny i czysty projekt, aby uzyskać trochę praktyki z TDD.

Po pierwsze „zakładam czerwony kapelusz”, czyli potrzebuję testu, który się nie powiedzie. Pomyślałem, że potrzebuję metody, która znajdzie wszystkie pola nienumeryczne w linii. Więc piszę prosty test, oczywiście, że nie kompiluje się od razu, więc zaczynam pisać samą funkcję, a po kilku cyklach tam iz powrotem (czerwony / zielony) mam działającą funkcję i kompletny test.

Następnie kontynuuję funkcję „gatherNonNumericColumns”, która odczytuje plik, jeden wiersz na raz, i wywołuje moją funkcję „findNonNumericFields” w każdym wierszu, aby zebrać wszystkie kolumny, które ostatecznie muszą zostać usunięte. Kilka cykli czerwono-zielonych, i skończyłem, mając ponownie działającą funkcję i pełny test.

Teraz myślę, że powinienem dokonać refaktoryzacji. Ponieważ moja metoda „findNonNumericFields” została zaprojektowana tylko dlatego, że doszedłem do wniosku, że będę jej potrzebować podczas implementowania „gatherNonNumericColumns”, wydaje mi się, że rozsądne byłoby pozostawienie „findNonNumericFields” prywatnemu. To jednak przerwałoby moje pierwsze testy, ponieważ nie miałyby one już dostępu do metody, którą testowały.

W rezultacie otrzymuję prywatne metody i zestaw testów, które to sprawdzają. Ponieważ tak wiele osób doradza, że ​​nie należy testować prywatnych metod, wydaje mi się, że zamalowałem się tutaj w kącie. Ale gdzie dokładnie zawiodłem?

Rozumiem, że mogłem zacząć na wyższym poziomie, pisząc test, który testuje, co ostatecznie stanie się moją publiczną metodą (to znaczy findAndFilterOutAllNonNumericalColumns), ale wydaje mi się to sprzeczne z całym punktem TDD (przynajmniej według wuja Boba) : Należy stale przełączać się między pisaniem testów a kodem produkcyjnym oraz, że w dowolnym momencie wszystkie testy zadziałały w ciągu ostatniej minuty. Ponieważ jeśli zacznę od napisania testu dla metody publicznej, minie kilka minut (lub godzin, a nawet dni w bardzo skomplikowanych przypadkach), zanim uzyskam wszystkie szczegóły w metodach prywatnych, aby test mógł przetestować metodę publiczną metoda mija.

Co więc robić? Czy TDD (z szybkim cyklem refaktoryzacji czerwony-zielony) po prostu nie jest zgodny z metodami prywatnymi? Czy jest to błąd w moim projekcie?



2
Te dwa elementy funkcjonalności są na tyle różne, że mogą być różnymi jednostkami - w takim przypadku metody prywatne powinny prawdopodobnie znajdować się na ich własnych klasach - lub są to te same jednostki, w których to przypadku nie rozumiem, dlaczego piszesz testy dla zachowanie wewnętrzne jednostki. Jeśli chodzi o przedostatni akapit, nie widzę konfliktu. Dlaczego musiałbyś napisać całą złożoną metodę prywatną, aby przejść jeden przypadek testowy? Dlaczego nie wypędzić go stopniowo metodą publiczną lub zacząć od wbudowania, a następnie wyodrębnić?
Ben Aaronson,

26
Dlaczego ludzie biorą idiomy i klisze z programowania książek i blogów, ponieważ rzeczywiste wytyczne dotyczące programowania są poza mną.
AK_

7
Nie podoba mi się TDD z tego właśnie powodu: jeśli jesteś w nowym obszarze, będziesz wykonywać dużo dodatkowej pracy, próbując dowiedzieć się, jaka powinna być architektura i jak działają pewne rzeczy. Z drugiej strony: jeśli jesteś w obszarze, z którym już się spotkałeś, korzyścią będzie napisanie testów najpierw irytujących, ponieważ intellisense nie rozumie, dlaczego piszesz niekompilowalny kod. Jestem znacznie większym fanem myślenia o projekcie, pisania go, a następnie testowania go.
Jeroen Vannevel

1
„większość ludzi zgadza się, że prywatne metody nie powinny być testowane bezpośrednio” - nie, testuj metodę bezpośrednio, jeśli ma to sens. Ukryj to tak, privatejakby miało to sens.
osa

Odpowiedzi:


44

Jednostki

Myślę, że mogę dokładnie wskazać, gdzie zaczął się problem:

Pomyślałem, że potrzebuję metody, która znajdzie wszystkie pola nienumeryczne w linii.

Po tym należy natychmiast zadać sobie pytanie: „Czy będzie to osobna jednostka do testowania, gatherNonNumericColumnsczy część tego samego?”

Jeśli odpowiedź brzmi „ tak, osobno ”, wówczas twój sposób działania jest prosty: metoda ta musi być publiczna w odpowiedniej klasie, aby można ją było przetestować jako całość. Twoja mentalność przypomina: „Muszę przetestować jedną metodę, a także przetestować inną metodę”

Z tego, co mówisz, doszedłeś jednak do wniosku, że odpowiedź brzmi „ nie, część tego samego ”. W tym momencie twój plan nie powinien już obejmować pełnego pisania i testowania, findNonNumericFields a następnie pisania gatherNonNumericColumns. Zamiast tego powinno być po prostu pisać gatherNonNumericColumns. Na razie findNonNumericFieldspowinien być po prostu prawdopodobną częścią miejsca docelowego, o którym myślisz, wybierając następną czerwoną skrzynkę testową i dokonując refaktoryzacji. Tym razem twoja mentalność brzmi: „Muszę przetestować jedną metodę, a robiąc to, powinienem pamiętać, że moja ukończona implementacja prawdopodobnie będzie zawierać tę drugą metodę”.


Utrzymanie krótkiego cyklu

Wykonanie powyższego nie powinno prowadzić do problemów, które opisujesz w przedostatnim akapicie:

Ponieważ jeśli zacznę od napisania testu dla metody publicznej, minie kilka minut (lub godzin, a nawet dni w bardzo skomplikowanych przypadkach), zanim uzyskam wszystkie szczegóły w metodach prywatnych, aby test mógł przetestować metodę publiczną metoda mija.

W żadnym momencie ta technika nie wymaga napisania czerwonego testu, który zmieni kolor na zielony tylko po wdrożeniu całości findNonNumericFieldsod zera. O wiele bardziej prawdopodobne, findNonNumericFieldsże zacznie się jako część kodu w publicznej metodzie, którą testujesz, która zostanie zbudowana w ciągu kilku cykli i ostatecznie wyodrębniona podczas refaktoryzacji.


Mapa drogowa

Aby podać przybliżoną mapę drogową dla tego konkretnego przykładu, nie znam dokładnych przypadków testowych, z których korzystałeś, ale mówię, że pisałeś gatherNonNumericColumnsjako metodę publiczną. Wtedy najprawdopodobniej przypadki testowe byłyby takie same jak te, dla których napisałeś findNonNumericFields, każdy z nich korzystałby z tabeli zawierającej tylko jeden wiersz. Kiedy ten jednorzędowy scenariusz został w pełni zaimplementowany i chciałeś napisać test, aby zmusić cię do wyodrębnienia metody, napiszesz dwurzędowy przypadek, który wymagałby dodania iteracji.


2
Myślę, że to jest odpowiedź właśnie tutaj. Przyjmując TDD w środowisku OOP, często miałem trudności z pokonaniem własnych instynktów oddolnych. Tak, funkcje powinny być małe, ale po refaktoryzacji. Wcześniej mogą być ogromnymi monolitami. +1
João Mendes

2
@ JoãoMendes Cóż, nie jestem pewien, czy powinieneś przejść do stanu wielkiego monolitu przed refaktoryzacją, szczególnie przy bardzo krótkich cyklach RGR. Ale tak, w obrębie testowalnej jednostki, działanie oddolne może prowadzić do problemów opisanych przez OP.
Ben Aaronson

1
OK, myślę, że teraz rozumiem, co poszło nie tak. Wielkie dzięki dla was wszystkich (zaznaczyłem tę jako odpowiedź, ale większość innych odpowiedzi jest równie pomocna)
Henrik Berg

66

Wiele osób uważa, że ​​testy jednostkowe są oparte na metodach; to nie jest. Powinien być oparty na najmniejszej jednostce, która ma sens. W większości przypadków oznacza to, że klasa powinna być testowana jako całość. Nie poszczególne metody.

Teraz oczywiście będziesz wywoływał metody w klasie, ale powinieneś pomyśleć o testach jako o zastosowaniu do obiektu czarnej skrzynki, który masz, więc powinieneś być w stanie zobaczyć, że niezależnie od logicznych operacji, które zapewnia twoja klasa; to są rzeczy, które musisz przetestować. Jeśli twoja klasa jest tak duża, że ​​operacja logiczna jest zbyt złożona, masz problem projektowy, który należy rozwiązać w pierwszej kolejności.

Klasa z tysiącem metod może wydawać się testowalna, ale jeśli testujesz każdą metodę osobno, tak naprawdę nie testujesz tej klasy. Niektóre klasy mogą wymagać określonego stanu przed wywołaniem metody, na przykład klasa sieci, która wymaga nawiązania połączenia przed wysłaniem danych. Metoda wysyłania danych nie może być rozpatrywana niezależnie od całej klasy.

Powinieneś więc zauważyć, że prywatne metody nie mają znaczenia dla testowania. Jeśli nie możesz ćwiczyć prywatnych metod przez wywołanie publicznego interfejsu klasy, wówczas te prywatne metody są bezużyteczne i i tak nie będą używane.

Myślę, że wiele osób próbuje przekształcić prywatne metody w testowalne jednostki, ponieważ wydaje się, że ich testowanie jest łatwe, ale przesadza to zbyt daleko. Martin Fowler mówi

Chociaż zaczynam od pojęcia, że ​​jednostka jest klasą, często biorę kilka ściśle powiązanych klas i traktuję je jako jedną całość

co ma sens w przypadku systemu obiektowego, ponieważ obiekty są zaprojektowane jako jednostki. Jeśli chcesz przetestować poszczególne metody, być może powinieneś stworzyć system proceduralny, taki jak C, lub klasę składającą się wyłącznie z funkcji statycznych.


14
Dla mnie wygląda na to, że ta odpowiedź całkowicie ignoruje podejście TDD w pytaniu PO. Jest to po prostu powtórzenie mantry „nie testuj metod prywatnych”, ale nie wyjaśnia, w jaki sposób TDD - która w rzeczywistości oparta jest na metodzie - może działać z podejściem opartym na braku metody testów jednostkowych.
Dok. Brown

6
@DocBrown nie, to odpowiada mu całkowicie, mówiąc „nie przesadzaj” z jednostkami i utrudniaj sobie życie. TDD nie jest oparte na metodzie, jest oparte na jednostkach, gdzie jednostka ma sens. Jeśli masz bibliotekę C, to tak, każda jednostka będzie funkcją. Jeśli masz klasę, jednostka jest przedmiotem. Jak mówi Fowler, czasami jednostka ma kilka ściśle powiązanych klas. Myślę, że wiele osób uważa testowanie jednostkowe za metodę po metodzie, ponieważ niektóre głupie narzędzia generują kody pośredniczące na podstawie metod.
gbjbaanb

3
@gbjbaanb: spróbuj zasugerować test, który pozwoli OP zaimplementować „zbieranie pól nienumerycznych w linii”, używając najpierw czystego TDD, bez publicznego interfejsu klasy, którą zamierza już napisać.
Dok. Brown

8
Muszę się zgodzić z @DocBrown tutaj. Problemem pytającego nie jest to, że pragnie większej szczegółowości testowania niż mógłby osiągnąć bez testowania metod prywatnych. Chodzi o to, że próbował zastosować ścisłe podejście TDD i - bez planowania jako takiego - doprowadził go do uderzenia w ścianę, gdzie nagle odkrył, że ma mnóstwo testów na to, co powinno być prywatną metodą. Ta odpowiedź nie pomaga w tym. To dobra odpowiedź na jakieś pytanie, tylko nie to.
Ben Aaronson,

7
@Matthew: Jego błędem jest to, że napisał tę funkcję w pierwszej kolejności. Idealnie byłoby, gdyby napisał metodę publiczną jako kod spaghetti, a następnie przekształcił ją w funkcję prywatną w cyklu refaktora - nie oznaczając jej jako prywatnej w cyklu refaktora.
slebetman

51

Fakt, że twoje metody gromadzenia danych są wystarczająco złożone, aby zasługiwać na testy i wystarczająco oddzielić się od twojego głównego celu, aby być metodami własnymi, a nie częścią niektórych punktów pętli rozwiązania: uczyń te metody nie prywatnymi, ale członkami innej klasy który zapewnia funkcje gromadzenia / filtrowania / tabelowania.

Następnie piszesz testy dla głupich aspektów klasy pomocniczej (np. „Rozróżnianie liczb od znaków”) w jednym miejscu, a testy dla twojego głównego celu (np. „Uzyskanie wyników sprzedaży”) w innym miejscu, a ty nie nie będziesz musiał powtarzać podstawowych testów filtrowania w testach dla normalnej logiki biznesowej.

Ogólnie rzecz biorąc, jeśli twoja klasa, która wykonuje jedną rzecz, zawiera obszerny kod do wykonywania innej czynności, która jest wymagana, ale odrębna od jej podstawowego celu, kod ten powinien żyć w innej klasie i być wywoływany za pomocą metod publicznych. Nie powinien być ukryty w prywatnych zakątkach klasy, która tylko przypadkowo zawiera ten kod. Zwiększa to jednocześnie testowalność i zrozumiałość.


Tak, zgadzam się z wami. Ale mam problem z twoim pierwszym stwierdzeniem, zarówno częścią „wystarczająco złożoną”, jak i częścią „wystarczająco oddzielną”. Jeśli chodzi o „wystarczająco skomplikowane”: staram się wykonać szybki cykl czerwono-zielony, co oznacza, że ​​mogę kodować maksymalnie przez około minutę przed przejściem do testowania (lub na odwrót). Oznacza to, że moje testy będą naprawdę bardzo szczegółowe. Uznałem, że jest to jedna z zalet TDD, ale może przesadziłem, więc staje się to wadą.
Henrik Berg,

Jeśli chodzi o „wystarczająco oddzielne”: nauczyłem się (ponownie od unclebob), że funkcje powinny być małe i że powinny być mniejsze. Zasadniczo więc staram się tworzyć 3-4 funkcje linii. Tak więc mniej więcej cała funkcjonalność jest podzielona na własne metody, bez względu na to, jak małe i proste.
Henrik Berg,

W każdym razie uważam, że aspekty mungingu danych (np. FindNonNumericFields) powinny być naprawdę prywatne. A jeśli podzielę to na inną klasę, i tak będę musiał to upublicznić, więc nie do końca rozumiem w tym sens.
Henrik Berg

6
@HenrikBerg przede wszystkim myśli, dlaczego masz obiekty - nie są to wygodne sposoby grupowania funkcji, ale są samodzielnymi jednostkami, które ułatwiają pracę ze złożonymi systemami. Dlatego powinieneś pomyśleć o przetestowaniu klasy jako rzeczy.
gbjbaanb

@gbjbaanb Twierdzę, że oba są jednym i tym samym.
RubberDuck

29

Osobiście uważam, że poszedłeś daleko w kierunku myślenia o implementacji, kiedy pisałeś testy. Państwo założyć musisz pewnych metod. Ale czy naprawdę potrzebujesz ich do robienia tego, co klasa ma robić? Czy klasa poniosłaby porażkę, gdyby ktoś przyszedł i dokonał ich wewnętrznej renowacji? Jeśli korzystałeś z tej klasy (i moim zdaniem powinien to być sposób myślenia testera), możesz naprawdę mniej się przejmować, jeśli istnieje wyraźna metoda sprawdzania liczb.

Powinieneś przetestować publiczny interfejs klasy. Prywatne wdrożenie jest prywatne z jakiegoś powodu. Nie jest to część publicznego interfejsu, ponieważ nie jest potrzebny i można go zmienić. To szczegół implementacji.

Jeśli napiszesz testy na interfejsie publicznym, nigdy nie dostaniesz problemu, na który natrafiłeś. Albo możesz utworzyć przypadki testowe dla interfejsu publicznego, które obejmują twoje prywatne metody (świetnie), albo nie możesz. W takim przypadku nadszedł czas, aby zastanowić się poważnie nad metodami prywatnymi i być może zeskrobać je całkowicie, jeśli i tak nie będzie można do nich dotrzeć.


1
„Szczegóły implementacji” to na przykład „czy użyłem XOR lub zmiennej tymczasowej, aby zamienić ints między zmiennymi”. Metody chronione / prywatne mają umowy, jak wszystko inne. Pobierają dane wejściowe, pracują z nimi i dają pewne wyniki, pod pewnymi ograniczeniami. Wszystko, co ma kontrakt, powinno ostatecznie zostać przetestowane - niekoniecznie dla osób zużywających bibliotekę, ale dla tych, którzy ją utrzymują i modyfikują po tobie. Tylko dlatego, że nie jest „publiczne” nie znaczy, że nie jest częścią o API.
Knetic

11

Nie wykonujesz TDD w oparciu o to, czego oczekujesz, że klasa zrobi wewnętrznie.

Twoje przypadki testowe powinny być oparte na tym, co klasa / funkcjonalność / program ma do czynienia ze światem zewnętrznym. W twoim przykładzie, czy użytkownik będzie kiedykolwiek dzwonił do klasy czytelnika zfind all the non-numerical fields in a line?

Jeśli odpowiedź brzmi „nie”, to w pierwszej kolejności jest to zły test. Chcesz napisać test na funkcjonalność na poziomie klasy / interfejsu - a nie na poziomie „co będzie musiała zaimplementować metoda, aby to zadziałało”, czyli test.

Przepływ TDD wynosi:

  • czerwony (co klasa / obiekt / funkcja / etc robi dla świata zewnętrznego)
  • zielony (wpisz minimalny kod, aby ta funkcja zewnętrznego świata działała)
  • refaktor (jaki jest lepszy kod, aby to działało)

NIE ma to robić „ponieważ będę potrzebował X w przyszłości jako metody prywatnej, pozwól mi go wdrożyć i przetestować najpierw”. Jeśli to robisz, robisz etap „czerwony” niepoprawnie. To wydaje się być twoim problemem tutaj.

Jeśli często piszesz testy dla metod, które stają się metodami prywatnymi, robisz jedną z kilku rzeczy:

  • Niewłaściwe zrozumienie przypadków użycia interfejsu / poziomu publicznego wystarczająco dobrze, aby napisać dla nich test
  • Dramatyczna zmiana projektu i refaktoryzacja kilku testów (co może być dobrą rzeczą, w zależności od tego, czy ta funkcjonalność jest testowana w nowszych testach)

9

Występuje powszechne nieporozumienie związane z testowaniem w ogóle.

Większość osób, które dopiero zaczynają testowanie, zaczyna myśleć w ten sposób:

  • napisz test dla funkcji F.
  • implement F
  • napisz test dla funkcji G.
  • zaimplementuj G za pomocą połączenia z F.
  • napisz test dla funkcji H
  • zaimplementuj H za pomocą połączenia z G

i tak dalej.

Problem polega na tym, że w rzeczywistości nie masz testu jednostkowego dla funkcji H. Test, który ma przetestować H, faktycznie testuje jednocześnie H, G i F.

Aby rozwiązać ten problem, musisz zdać sobie sprawę, że testowalne jednostki nigdy nie mogą zależeć od siebie, a raczej od ich interfejsów . W twoim przypadku, gdy jednostki są prostymi funkcjami, interfejsy są tylko ich sygnaturami połączeń. Musisz zatem zaimplementować G w taki sposób, aby można go było używać z dowolną funkcją mającą taki sam podpis jak F.

To, jak dokładnie można to zrobić, zależy od języka programowania. W wielu językach możesz przekazywać funkcje (lub wskaźniki do nich) jako argumenty dla innych funkcji. Umożliwi to przetestowanie każdej funkcji w izolacji.


3
Chciałbym móc głosować jeszcze wiele razy. Podsumowując, nie zaprojektowałeś poprawnie swojego rozwiązania.
Justin Ohms

W języku takim jak C ma to sens, ale w przypadku języków OO, w których jednostką powinna być na ogół klasa (z metodami publicznymi i prywatnymi), należy przetestować klasę, a nie każdą metodę prywatną w oderwaniu. Tak, izoluję twoje klasy. Izolowanie metod w każdej klasie, nie.
gbjbaanb

8

Testy, które piszesz podczas Test Driven Development, mają zapewnić, że klasa poprawnie implementuje swój publiczny interfejs API, jednocześnie upewniając się, że ten publiczny interfejs API jest łatwy do przetestowania i użycia.

Możesz za wszelką cenę użyć prywatnych metod do implementacji tego API, ale nie ma potrzeby tworzenia testów poprzez TDD - funkcjonalność zostanie przetestowana, ponieważ publiczny API będzie działał poprawnie.

Załóżmy teraz, że twoje prywatne metody są na tyle skomplikowane, że zasługują na samodzielne testy - ale nie mają sensu jako część publicznego interfejsu API twojej klasy. Cóż, prawdopodobnie oznacza to, że powinny to być publiczne metody innej klasy - takiej, z której korzysta Twoja oryginalna klasa w swojej własnej implementacji.

Testując tylko publiczny interfejs API, znacznie ułatwisz modyfikowanie szczegółów implementacji w przyszłości. Niepomocne testy będą Cię drażnić dopiero później, gdy będą musiały zostać przepisane, aby obsługiwać eleganckie refaktoryzowanie, które właśnie odkryłeś.


4

Myślę, że właściwą odpowiedzią jest wniosek, do którego doszedłeś, rozpoczynając od metod publicznych. Zacząłbyś od napisania testu, który wywołuje tę metodę. Nie powiedzie się, więc utworzysz metodę o tej nazwie, która nic nie robi. Być może masz rację w teście, który sprawdza wartość zwracaną.

(Nie jestem do końca jasne, co robi twoja funkcja. Czy zwraca ciąg z zawartością pliku z usuniętymi wartościami nienumerycznymi?)

Jeśli metoda zwraca ciąg znaków, sprawdzasz tę zwracaną wartość. Więc po prostu dalej go budujesz.

Myślę, że wszystko, co dzieje się w metodzie prywatnej, powinno być w metodzie publicznej w pewnym momencie procesu, a następnie zostać przeniesione do metody prywatnej tylko w ramach etapu refaktoryzacji. O ile mi wiadomo, refaktoryzacja nie wymaga testów negatywnych. Potrzebujesz tylko nieudanych testów podczas dodawania funkcjonalności. Musisz tylko przeprowadzić testy po refaktorze, aby upewnić się, że wszystkie pomyślnie przejdą.


3

wydaje mi się, że zamalowałem się tutaj w kącie. Ale gdzie dokładnie zawiodłem?

Jest stara przysłowie.

Kiedy nie planujesz, planujesz ponieść porażkę.

Wydaje się, że ludzie myślą, że kiedy TDD, po prostu usiądziesz, napiszesz testy, a projekt zacznie się magicznie. To nie jest prawda. Musisz mieć plan wysokiego poziomu. Odkryłem, że najlepsze wyniki uzyskuję od TDD, kiedy najpierw projektuję interfejs (publiczne API). Osobiście tworzę rzeczywisty, interfacektóry definiuje klasę jako pierwszy.

dyszę Napisałem trochę „kodu” przed napisaniem jakichkolwiek testów! Więc nie. Ja nie. Napisałem umowę, której należy przestrzegać, projekt . Podejrzewam, że podobne wyniki można uzyskać, zapisując diagram UML na papierze milimetrowym. Chodzi o to, że musisz mieć plan. TDD nie jest licencją na złośliwe hakowanie fragmentu kodu.

Naprawdę czuję, że „Test First” to myląca nazwa. Pierwszy projekt następnie Test.

Oczywiście, postępuj zgodnie z radami udzielonymi przez innych na temat wydobywania kolejnych klas z twojego kodu. Jeśli zdecydowanie odczuwasz potrzebę przetestowania elementów wewnętrznych klasy, wyodrębnij te elementy wewnętrzne do łatwo testowanej jednostki i wstrzyknij je.


2

Pamiętaj, że testy można również refaktoryzować! Jeśli ustawisz metodę jako prywatną, zmniejszysz publiczny interfejs API, a zatem całkowicie dopuszczalne jest odrzucenie niektórych odpowiednich testów dla tej „utraconej funkcjonalności” (zmniejszona złożoność AKA).

Inni twierdzą, że twoja prywatna metoda zostanie wywołana jako część innych testów API lub będzie nieosiągalna, a zatem możliwa do usunięcia. W rzeczywistości rzeczy są bardziej szczegółowe, jeśli myślimy o ścieżkach wykonania .

Na przykład, jeśli mamy metodę publiczną, która wykonuje podział, możemy chcieć przetestować ścieżkę, która daje podział według zera. Jeśli ustawimy metodę na prywatną, otrzymamy wybór: albo możemy rozważyć ścieżkę dzielenia przez zero, albo możemy wyeliminować tę ścieżkę, biorąc pod uwagę, jak jest wywoływana przez inne metody.

W ten sposób możemy wyrzucić niektóre testy (np. Podzielić przez zero) i przefakturować inne pod względem pozostałego publicznego API. Oczywiście w idealnym świecie istniejące testy zajmują się wszystkimi pozostałymi ścieżkami, ale rzeczywistość jest zawsze kompromisem;)


1
Podczas gdy inne odpowiedzi są poprawne, ponieważ prywatna metoda nie powinna była zostać napisana w czerwonym cyklu, ludzie popełniają błędy. A gdy już wystarczająco daleko popełniłeś błąd, jest to właściwe rozwiązanie.
slebetman

2

Są chwile, kiedy prywatna metoda może być publiczną metodą innej klasy.

Na przykład możesz mieć prywatne metody, które nie są bezpieczne dla wątków i pozostawić klasę w stanie tymczasowym. Metody te można przenieść do osobnej klasy, która jest prowadzona prywatnie przez pierwszą klasę. Więc jeśli twoja klasa jest kolejką, możesz mieć klasę InternalQueue, która ma metody publiczne, a klasa kolejki prywatnie utrzymuje instancję InternalQueue. Pozwala to przetestować kolejkę wewnętrzną, a także wyjaśnia, jakie są poszczególne operacje na kolejce wewnętrznej.

(Jest to najbardziej oczywiste, gdy wyobrażasz sobie, że nie ma klasy List i jeśli próbowałeś zaimplementować funkcje List jako prywatne metody w klasie, która ich używa).


2
„Czasami metoda prywatna może być metodą publiczną innej klasy”. Nie mogę tego wystarczająco podkreślić. Czasami prywatna metoda to po prostu inna klasa krzycząca o własną tożsamość.

0

Zastanawiam się, dlaczego twój język ma tylko dwa poziomy prywatności, w pełni publiczny i całkowicie prywatny.

Czy możesz ustalić, że twoje niepubliczne metody są dostępne dla pakietów lub coś w tym rodzaju? Następnie umieść testy w tym samym pakiecie i ciesz się testowaniem wewnętrznych mechanizmów, które nie są częścią publicznego interfejsu. Twój system kompilacji wykluczy testy podczas budowania wersji binarnej wydania.

Oczywiście czasami trzeba mieć naprawdę prywatne metody, niedostępne tylko dla klasy definiującej. Mam nadzieję, że wszystkie takie metody są bardzo małe. Ogólnie rzecz biorąc, utrzymanie małych metod (np. Poniżej 20 wierszy) bardzo pomaga: testowanie, konserwacja i samo zrozumienie kodu staje się łatwiejsze.


3
Zmiana modyfikatora dostępu do metody w celu przeprowadzenia testów to sytuacja, w której ogon porusza się psem. Myślę, że testowanie wewnętrznych części jednostki utrudnia później refaktoryzację. Wręcz przeciwnie, testowanie interfejsu publicznego jest świetne, ponieważ działa jako „kontrakt” dla jednostki.
scriptin

Nie musisz zmieniać poziomu dostępu do metody. Próbuję powiedzieć, że masz pośredni poziom dostępu, który pozwala na pisanie określonego kodu, w tym testów, łatwiej, bez zawierania umowy publicznej. Oczywiście musisz przetestować interfejs publiczny, ale dodatkowo czasem czasem warto przetestować niektóre wewnętrzne działania w izolacji.
9000

0

Czasami mam do czynienia z prywatnymi metodami, które mają być chronione, aby umożliwić bardziej szczegółowe testy (ściślejsze niż udostępniony publiczny interfejs API). Powinien to być (mam nadzieję, że bardzo rzadki) wyjątek, a nie reguła, ale może być pomocny w określonych przypadkach, z którymi możesz się spotkać. Jest to również coś, czego w ogóle nie należy brać pod uwagę przy tworzeniu publicznego interfejsu API, więcej „oszukiwania”, które można wykorzystać w oprogramowaniu do użytku wewnętrznego w tych rzadkich sytuacjach.


0

Doświadczyłem tego i poczułem twój ból.

Moje rozwiązanie polegało na:

przestań traktować testy jak budowanie monolitu.

Pamiętaj, że kiedy napisałeś zestaw testów, powiedzmy 5, aby wypróbować jakąś funkcjonalność, nie musisz trzymać tych wszystkich testów , zwłaszcza gdy staje się to częścią czegoś innego.

Na przykład często mam:

  • test niskiego poziomu 1
  • kod do spełnienia
  • test niskiego poziomu 2
  • kod do spełnienia
  • test niskiego poziomu 3
  • kod do spełnienia
  • test niskiego poziomu 4
  • kod do spełnienia
  • test niskiego poziomu 5
  • kod do spełnienia

więc mam

  • test niskiego poziomu 1
  • test niskiego poziomu 2
  • test niskiego poziomu 3
  • test niskiego poziomu 4
  • test niskiego poziomu 5

Jeśli jednak dodam teraz funkcje wywołujące wyższy poziom, które go wywołują i mają wiele testów, być może uda mi się teraz zmniejszyć liczbę testów niskiego poziomu, tak aby były:

  • test niskiego poziomu 1
  • test niskiego poziomu 5

Diabeł tkwi w szczegółach, a możliwość zrobienia tego będzie zależeć od okoliczności.


-2

Czy słońce krąży wokół Ziemi, czy Ziemia wokół Słońca? Według Einsteina odpowiedź brzmi tak, lub oba, ponieważ oba modele różnią się tylko pod względem punktu widzenia, podobnie enkapsulacja i rozwój oparty na testach są tylko w konflikcie, ponieważ naszym zdaniem są. Siedzimy tutaj jak Galileusz i papież, rzucając sobie nawzajem obelgi: głupcze, czy nie widzisz, że prywatne metody również wymagają testowania; heretyku, nie przerywaj enkapsulacji! Podobnie, gdy uznamy, że prawda jest większa, niż myśleliśmy, możemy spróbować czegoś takiego jak podsumowanie testów dla prywatnych interfejsów, aby testy dla publicznych interfejsów nie przerywały enkapsulacji.

Spróbuj tego: dodaj dwie metody: jedna, która nie ma danych wejściowych, tylko zwraca liczbę prywatnych testów, a druga przyjmuje numer testu jako parametr i zwraca pozytywny / negatywny wynik.


1
Wybrane obelgi zostały wykorzystane przez Galileusza i Papieża, a nie przez żadną odpowiedź na to pytanie.
hildred
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.