Upublicznienie prywatnej metody testowania urządzenia… dobry pomysł?

301

Uwaga moderatora: opublikowano już 39 odpowiedzi (niektóre zostały usunięte). Przed wysłaniem swoją odpowiedź, należy rozważyć, czy można dodać coś sensownego do dyskusji. Prawdopodobnie powtarzasz to, co ktoś już powiedział.


Czasami muszę stworzyć metodę prywatną w klasie publicznej, aby napisać dla niej kilka testów jednostkowych.

Zwykle dzieje się tak, ponieważ metoda zawiera logikę współużytkowaną przez inne metody w klasie, a testowanie logiki we własnym zakresie jest łatwiejsze. Innym powodem może być testowanie logiki używanej w wątkach synchronicznych bez martwienia się o problemy z wątkami. .

Czy inni to robią, bo tak naprawdę nie lubię tego robić? Osobiście uważam, że bonusy przeważają nad problemem upublicznienia metody, która tak naprawdę nie zapewnia żadnej usługi poza klasą ...

AKTUALIZACJA

Dzięki za odpowiedzi wszystkim, wydaje się, że wzbudził zainteresowanie ludzi. Myślę, że ogólna zgoda co do testowania powinna odbywać się za pośrednictwem publicznego interfejsu API, ponieważ jest to jedyny sposób, w jaki klasa będzie kiedykolwiek używana, i zgadzam się z tym. Kilka przypadków, o których wspomniałem powyżej, w których chciałbym to zrobić powyżej, były rzadkimi przypadkami i uważałem, że korzyści płynące z robienia tego były tego warte.

Widzę jednak, że wszyscy twierdzą, że tak naprawdę nigdy nie powinno tak się stać. A kiedy myślę o tym trochę więcej, myślę, że zmiana kodu w celu dostosowania go do testów jest złym pomysłem - w końcu przypuszczam, że testowanie jest w pewien sposób narzędziem wsparcia, a zmiana systemu na „wsparcie narzędzia wsparcia”, jeśli chcesz, jest rażąca zła praktyka.

jcvandan
źródło
2
„zmiana systemu na„ wsparcie narzędzia wsparcia ”, jeśli chcesz, jest rażącą złą praktyką”. Cóż, tak jakby. Ale OTOH, jeśli twój system nie działa dobrze z ustalonymi narzędziami, być może coś jest nie tak z systemem.
Thilo,
1
Jak to nie jest duplikat stackoverflow.com/questions/105007/do-you-test-private-method ?
Sanghyun Lee
1
Nie odpowiedź, ale zawsze możesz uzyskać do nich dostęp poprzez refleksję.
Zano
33
Nie, nie powinieneś ich narażać. Zamiast tego powinieneś przetestować, czy twoja klasa zachowuje się tak, jak powinna, za pośrednictwem swojego publicznego interfejsu API. A jeśli odsłonięcie elementów wewnętrznych jest naprawdę jedyną opcją (w co wątpię), to powinieneś przynajmniej zabezpieczyć pakiet modułów dostępu, aby tylko klasy w tym samym pakiecie (jak powinien być test) miały do ​​nich dostęp.
JB Nizet
4
Tak to jest. Dopuszczalne jest nadawanie widoczności pakietowi metody prywatnej (domyślnej), aby był dostępny z testów jednostkowych. Biblioteki takie jak Guava zawierają nawet @VisibileForTestingadnotacje do tworzenia takich metod - zalecam to zrobić, aby przyczyna niewłaściwej metody była privateodpowiednio udokumentowana.
Boris the Spider

Odpowiedzi:

186

Uwaga:
Ta odpowiedź została pierwotnie zamieszczona na pytanie Czy samo testowanie jednostkowe jest kiedykolwiek dobrym powodem do ujawnienia zmiennych instancji prywatnych za pośrednictwem metod pobierających? który został połączony z tym, więc może być nieco specyficzny dla przedstawionego tam przypadku użycia.

Ogólnie rzecz biorąc, zazwyczaj jestem za refaktoryzacją kodu „produkcyjnego”, aby ułatwić testowanie. Nie sądzę jednak, żeby był to dobry telefon. Dobry test jednostkowy (zwykle) nie powinien dbać o szczegóły implementacji klasy, a jedynie o jej widoczne zachowanie. Zamiast wystawiać na próbę wewnętrzne stosy, możesz sprawdzić, czy klasa zwraca strony w oczekiwanej kolejności po wywołaniu first()lub last().

Rozważmy na przykład ten pseudo-kod:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
Mureinik
źródło
8
Całkowicie zgadzam się z twoją odpowiedzią. Wielu programistów pokusiłoby się o użycie domyślnego modyfikatora dostępu i uzasadnienie, że dobre platformy open source używają tej strategii, więc musi być poprawna. To, że możesz, nie oznacza, że ​​powinieneś. +1
CKing
6
Chociaż inni przedstawili przydatne informacje, muszę powiedzieć, że jest to rozwiązanie, które uważam za najbardziej odpowiednie dla tego problemu. Dzięki temu testy są całkowicie niezależne od tego, jak zamierzone zachowanie jest osiągane wewnętrznie. +10!
Gitahi Ng'ang'a
Nawiasem mówiąc, czy jest w porządku, że w przykładzie podanym przez @Mureinik tutaj metody testowe pokrywają więcej niż faktyczna metoda testowana? Na przykład testFirst () wywołuje next (), co tak naprawdę nie jest tutaj testowaną metodą, first () jest. Czy nie byłoby czystsze i jaśniejsze posiadanie tylko jednej metody testowej obejmującej wszystkie 4 metody nawigacji?
Gitahi Ng'ang'a
1
Chociaż jest to ideał, są chwile, kiedy trzeba sprawdzić, czy komponent zrobił coś we właściwy sposób, a nie tylko, że się udało. Testowanie czarnej skrzynki może być trudniejsze. Czasami trzeba w białym polu przetestować komponent.
Peter Lawrey,
1
Tworzysz programy pobierające tylko na potrzeby testów jednostkowych? Jest to zdecydowanie sprzeczne z zasadą OOP / Object Thinking , ponieważ interfejs powinien ujawniać zachowanie, a nie dane.
IntelliData,
151

Osobiście wolałbym badanej jednostki używając API publicznych i nigdy bym na pewno sprawiają, że metoda prywatny publiczny prostu zrobić to łatwe do testu.

Jeśli naprawdę chcesz przetestować metodę prywatną w izolacji, w Javie możesz użyć Easymock / Powermock, aby to zrobić.

Musisz być pragmatyczny i powinieneś również zdawać sobie sprawę z powodów, dla których trudno jest przetestować.

Posłuchaj testów ” - jeśli trudno jest przetestować, czy to mówi coś o twoim projekcie? Czy mógłbyś zastanowić się, gdzie test dla tej metody byłby trywialny i łatwo objęty testowaniem za pośrednictwem publicznego interfejsu API?

Oto, co ma do powiedzenia Michael Feathers w „ Skutecznej pracy ze starszym kodem

„Wiele osób spędza dużo czasu próbując dowiedzieć się, jak obejść ten problem ... prawdziwa odpowiedź brzmi: jeśli masz ochotę przetestować metodę prywatną, metoda nie powinna być prywatna; jeśli upublicznisz tę metodę przeszkadza wam, są szanse, ponieważ jest to część odrębnej odpowiedzialności; powinno być na innej klasie ”. [ Skutecznie współpracując ze starszym kodeksem (2005) M. Feathers]

pusty
źródło
5
+1 dla Easymock / Powermock i nigdy nie upubliczniać prywatnej metody
Leonard Brünings
51
+1 za „Posłuchaj testów”. Jeśli czujesz potrzebę przetestowania metody prywatnej, istnieje duże prawdopodobieństwo, że logika tej metody jest tak złożona, że ​​powinna ona znajdować się w osobnej klasie pomocniczej. Następnie możesz przetestować klasę pomocnika.
Phil
2
Wydaje się, że „Posłuchaj testów” nie działa. Oto link do pamięci podręcznej: webcache.googleusercontent.com/…
Jess Telford
1
Zgadzam się z @Phil (szczególnie w książce), ale często znajduję się w sytuacji kurczaka / jaja ze starszym kodem. Wiem, że ta metoda należy do innej klasy, jednak nie jestem w 100% wygodna w refaktoryzacji bez testów.
Kevin
Zgadzam się z lekarzem. Jeśli musisz przetestować metodę prywatną, prawdopodobnie powinna ona należeć do innej klasy. Możesz zauważyć, że inna klasa / klasa pomocnicza, której metoda prywatna prawdopodobnie stanie się publiczna / chroniona.
rupweb
67

Jak powiedzieli inni, w pewnym stopniu podejrzewa się, że w ogóle testuje się metody prywatne; przetestuj interfejs publiczny, a nie szczegóły dotyczące prywatnej implementacji.

To powiedziawszy, techniką, której używam, gdy chcę przetestować jednostkowo coś, co jest prywatne w języku C #, jest obniżenie poziomu ochrony dostępności z prywatnego na wewnętrzny, a następnie oznaczenie zestawu testującego jednostkę jako zestawu przyjaciela przy użyciu InternalsVisibleTo . Zespół testujący jednostki będzie wtedy mógł traktować elementy wewnętrzne jako publiczne, ale nie musisz się martwić o przypadkowe dodanie do swojej publicznej powierzchni.

Eric Lippert
źródło
Ja również używam tej techniki, ale zwykle w ostateczności dla starszego kodu. Jeśli musisz przetestować kod prywatny, brakuje Ci klasy / klasa testowana robi za dużo. Wyodrębnij wspomniany kod do nowej klasy, którą możesz przetestować w izolacji. Takie sztuczki powinny być używane rzadko.
Finglas,
Ale wyodrębnianie klasy i testowanie tylko oznacza, że ​​testy tracą wiedzę, że oryginalna klasa faktycznie używa wyodrębnionego kodu. Jak zalecamy wypełnienie tej luki w testach? A jeśli odpowiedzią jest po prostu przetestowanie publicznego interfejsu oryginalnej klasy w sposób, który powoduje użycie wyodrębnionej logiki, to po co w ogóle zawracać sobie nią uwagę? (Nie chcę tu brzmieć konfrontacyjnie, przy okazji - po prostu zawsze zastanawiałem się, jak ludzie równoważą takie kompromisy.)
Mal Ross
@mal Miałem test współpracy. Innymi słowy, próbna weryfikacja nowej klasy została poprawnie wywołana. Zobacz moją odpowiedź poniżej.
Finglas,
Czy można napisać klasę testową, która dziedziczy po klasie z wewnętrzną logiką, którą chcesz przetestować, i podać metodę publiczną do testowania wewnętrznej logiki?
sholsinger
1
@sholsinger: w języku C # klasa publiczna nie może dziedziczyć po klasie wewnętrznej.
Eric Lippert,
45

Wiele odpowiedzi sugeruje jedynie przetestowanie interfejsu publicznego, ale IMHO jest to nierealne - jeśli metoda robi coś, co wymaga 5 kroków, będziesz chciał przetestować te pięć kroków osobno, a nie wszystkie razem. Wymaga to przetestowania wszystkich pięciu metod, które w innym przypadku (inne niż do testowania) mogłyby być private.

Zwykłym sposobem testowania metod „prywatnych” jest nadanie każdej klasie własnego interfejsu i utworzenie metod „prywatnych” public, ale nie włączenie ich do interfejsu. W ten sposób można je jeszcze przetestować, ale nie nadmuchują interfejsu.

Tak, spowoduje to wzdęcie pliku i klasy.

Tak, powoduje to, że specyfikatory publici są privatezbędne.

Tak, to boli w tyłek.

Jest to niestety jedna z wielu ofiar, które poświęcamy, aby kod był testowalny . Być może przyszły język (lub nawet przyszła wersja C # / Java) będzie zawierał funkcje ułatwiające testowanie klas i modułów; ale tymczasem musimy skakać przez te obręcze.


Są tacy, którzy twierdzą, że każdy z tych kroków powinien być własną klasą , ale nie zgadzam się - jeśli wszystkie mają ten sam stan, nie ma powodu, aby tworzyć pięć oddzielnych klas, w których byłoby pięć metod. Co gorsza, powoduje to wzdęcia plików i klas. Ponadto infekuje publiczne API twojego modułu - wszystkie te klasy muszą być koniecznie, publicjeśli chcesz je przetestować z innego modułu (albo to, albo dołączyć kod testowy do tego samego modułu, co oznacza wysyłkę kodu testowego z produktem) .

BlueRaja - Danny Pflughoeft
źródło
2
niezła odpowiedź na pierwszą połowę
cmcginty
7
+1: Najlepsza odpowiedź dla mnie. Jeśli producent samochodów nie testuje części swojego samochodu z tego samego powodu, nie jestem pewien, że ktoś by go kupił. Ważną część kodu należy przetestować, nawet jeśli zmienimy go na publiczny.
Tae-Sung Shin
1
Bardzo dobra odpowiedź. Masz mój ciepły głos. Dodałem odpowiedź. To może Cię zainteresować.
davidxxx
29

Test jednostkowy powinien przetestować zamówienie publiczne, jedyny sposób, w jaki klasę można wykorzystać w innych częściach kodu. Metodą prywatną są szczegóły implementacji, nie należy jej testować, o ile publiczny interfejs API działa poprawnie, implementacja nie ma znaczenia i można ją zmienić bez zmian w przypadkach testowych.

kan
źródło
+1 i prawdziwa odpowiedź: stackoverflow.com/questions/4603847/…
Raedwald
Afair, rzadką sytuacją, w której kiedykolwiek zdecydowałem się użyć szeregów prywatnych w przypadku testowym, było to, gdy weryfikowałem poprawność obiektu - jeśli zamknął on wszystkie programy obsługi JNI. Oczywiście nie jest ujawniany jako publiczny interfejs API, jednak jeśli tak się nie stanie, nastąpi wyciek pamięci. Ale zabawne, później się go pozbyłem po wprowadzeniu detektora wycieków pamięci jako części publicznego API.
kan
11
Jest to błędna logika. Celem testu jednostkowego jest wykrycie błędów wcześniej niż później. Nie testując metod prywatnych, pozostawiasz luki w swoich testach, które mogą zostać odkryte dopiero później. W przypadku gdy metoda prywatna jest złożona i nie jest łatwa do wykonania przez interfejs publiczny, należy ją przetestować jednostkowo.
cmcginty,
6
@Casey: jeśli metoda prywatna nie jest wykonywana przez interfejs publiczny, dlaczego ona istnieje?
Thilo,
2
@DaSilva_Ireland: Trudno będzie utrzymać przypadki testowe w przyszłości. Jedynym prawdziwym przypadkiem użycia klasy jest metoda publiczna. Tj. Wszystkie inne kody mogłyby korzystać z tej klasy tylko za pośrednictwem publicznego interfejsu API. Tak więc, jeśli chcesz zmienić implementację i niepowodzenie przypadku testowego metody prywatnej, nie oznacza to, że robisz coś złego, a ponadto, jeśli testujesz tylko prywatny, ale nie testujesz w pełni publiczny, nie obejmie on niektórych potencjalnych błędów . Idealnie, przypadek testowy powinien obejmować wszystkie możliwe przypadki i tylko przypadki rzeczywistego użycia klasy.
kan
22

IMO, powinieneś napisać testy, nie poczyniąc głębokich założeń dotyczących tego, jak twoja klasa zaimplementowała się w środku. Prawdopodobnie zechcesz go później zrefaktoryzować przy użyciu innego modelu wewnętrznego, ale nadal zapewniając te same gwarancje, które daje poprzednia implementacja.

Mając to na uwadze, sugeruję, abyś skupił się na testowaniu, czy twoja umowa nadal obowiązuje, bez względu na wewnętrzne wdrożenie, które obecnie ma Twoja klasa. Testowanie publicznych interfejsów API oparte na właściwościach.

SimY4
źródło
6
Zgadzam się, że testy jednostkowe, które nie muszą być przepisywane po refaktoryzacji kodu, są lepsze. I to nie tylko ze względu na czas poświęcony na przepisywanie testów jednostkowych. O wiele ważniejszym powodem jest to, że uważam, że najważniejszym celem testów jednostkowych jest to, że kod nadal działa poprawnie po refaktoryzacji. Jeśli po refaktoryzacji musisz przepisać wszystkie testy jednostkowe, tracisz tę korzyść z testów jednostkowych.
kasperd
20

Co powiesz na to, aby pakiet był prywatny? Następnie kod testowy może go zobaczyć (i inne klasy w pakiecie), ale nadal jest ukryty przed użytkownikami.

Ale tak naprawdę nie powinieneś testować prywatnych metod. Są to szczegóły dotyczące wdrożenia, a nie część umowy. Wszystko, co robią, powinno być objęte wywoływaniem metod publicznych (jeśli mają tam kod, który nie jest wykonywany przez metody publiczne, to powinno pójść). Jeśli kod prywatny jest zbyt skomplikowany, klasa prawdopodobnie robi zbyt wiele rzeczy i chce refaktoryzować.

Upublicznienie metody jest dużym zaangażowaniem. Gdy to zrobisz, ludzie będą mogli z niego korzystać i nie będziesz mógł ich więcej zmieniać.

Thilo
źródło
+1, jeśli chcesz przetestować swoich szeregowych, prawdopodobnie brakuje ci klasy
jk.
+1 za brakującą klasę. Cieszę się, że inni od razu nie wskakują na modą „zaznacz to wewnętrzne”.
Finglas
Chłopcze, musiałem zejść daleko w dół, aby znaleźć pakiet prywatnej odpowiedzi.
Bill K
17

Aktualizacja : W wielu innych miejscach dodałem bardziej rozbudowaną i pełniejszą odpowiedź na to pytanie. Można to znaleźć na moim blogu .

Jeśli kiedykolwiek będę musiał podać coś do wiadomości publicznej, aby to przetestować, zwykle oznacza to, że testowany system nie przestrzega zasady pojedynczej odpowiedzialności . Dlatego brakuje klasy, którą należy wprowadzić. Po wyodrębnieniu kodu do nowej klasy, upublicznij go. Teraz możesz łatwo testować i śledzisz SRP. Twoja druga klasa musi po prostu przywołać tę nową klasę poprzez kompozycję.

Upublicznianie metod / stosowanie sztuczek językowych, takich jak oznaczanie kodu jako widoczne dla zespołów testowych, zawsze powinno być ostatecznością.

Na przykład:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Przeanalizuj to, wprowadzając obiekt sprawdzania poprawności.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Teraz wszystko, co musimy zrobić, to przetestować poprawność wywołania walidatora. Rzeczywisty proces sprawdzania poprawności (wcześniej prywatna logika) można przetestować w czystej izolacji. Nie będzie potrzeby konfigurowania złożonego testu, aby upewnić się, że ta weryfikacja zakończy się pomyślnie.

Finglas
źródło
1
Osobiście uważam, że SRP można posunąć za daleko, na przykład jeśli piszę usługę konta, która zajmuje się aktualizowaniem / tworzeniem / usuwaniem kont w aplikacji, czy mówisz mi, że powinieneś utworzyć osobną klasę dla każdej operacji? A co na przykład z sprawdzaniem poprawności, czy naprawdę warto wyodrębnić logikę sprawdzania poprawności do innej klasy, gdy nigdy nie będzie ona używana w innym miejscu? imo sprawia, że ​​kod staje się mniej czytelny, mniej zwięzły i pakuje Twoją aplikację w zbędne klasy!
jcvandan
1
Wszystko zależy od scenariusza. Definicja jednej osoby dotycząca „robienia jednej rzeczy” może różnić się od drugiej. W tym przypadku, oczywiście, prywatny kod, który chcesz przetestować, jest skomplikowany / jest trudny do skonfigurowania. Sam fakt, że chcesz to przetestować, dowodzi, że robi to za dużo. Dlatego tak, posiadanie nowego obiektu będzie idealne.
Finglas,
1
Co do zmniejszenia czytelności kodu, zdecydowanie się nie zgadzam. Posiadanie klasy do sprawdzania poprawności jest łatwiejsze do odczytania niż osadzanie sprawdzania poprawności w innym obiekcie. Nadal masz taką samą ilość kodu, po prostu nie jest zagnieżdżona w jednej dużej klasie. Wolę mieć kilka klas, które wykonują jedną rzecz bardzo dobrze, niż jedną dużą klasę.
Finglas,
4
@dormisher, jeśli chodzi o obsługę konta, uważają SRP za „jeden powód do zmiany”. Jeśli Twoje operacje CRUD naturalnie zmienią się razem, pasują do SRP. Zwykle zmieniłbyś je, ponieważ schemat bazy danych mógłby się zmienić, co naturalnie wpłynęłoby na wstawianie i aktualizację (choć być może nie zawsze usuwane).
Anthony Pegram
@finglas co myślisz o tym: stackoverflow.com/questions/29354729/... . Nie mam ochoty testować metody prywatnej, więc może nie powinienem rozdzielać jej na klasy, ale jest ona używana w 2 innych metodach publicznych, więc skończyłbym na testowaniu dwukrotnie tej samej logiki.
Rodrigo Ruiz
13

Kilka świetnych odpowiedzi. Jedną rzeczą, o której nie wspomniałem, jest to, że w przypadku rozwoju opartego na testach (TDD), prywatne metody są tworzone podczas fazy refaktoryzacji (spójrz na metodę wyodrębniania na przykład wzorca refaktoryzacji), a zatem powinny już mieć niezbędne pokrycie testowe . Jeśli zrobisz to poprawnie (i oczywiście dostaniesz mieszany zestaw opinii, jeśli chodzi o poprawność), nie powinieneś się martwić o podanie publicznej metody prywatnej tylko po to, aby ją przetestować.

csano
źródło
11

Dlaczego nie podzielić algorytmu zarządzania stosami na klasę użyteczności? Klasa użyteczności może zarządzać stosami i zapewniać publiczne akcesoria. Testy jednostkowe można skoncentrować na szczegółach implementacji. Dogłębne testy dla algorytmicznie trudnych klas są bardzo pomocne w marszczeniu skrajnych przypadków i zapewnianiu zasięgu.

Wówczas twoja bieżąca klasa może czysto delegować się do klasy użyteczności bez ujawniania żadnych szczegółów implementacji. Jego testy będą dotyczyły wymogu podziału na strony, jak zalecili inni.

Alfred Armstrong
źródło
10

W Javie istnieje również opcja nadania pakietowi prywatności (tzn. Pominięcie modyfikatora widoczności). Jeśli testy jednostkowe znajdują się w tym samym pakiecie co testowana klasa, powinny one być w stanie zobaczyć te metody i jest nieco bezpieczniejsze niż upublicznienie metody.

Tom Jefferys
źródło
10

Metody prywatne są zwykle używane jako metody „pomocnicze”. Dlatego zwracają tylko podstawowe wartości i nigdy nie działają na określonych instancjach obiektów.

Masz kilka opcji, jeśli chcesz je przetestować.

  • Użyj refleksji
  • Daj dostęp do pakietu metod

Alternatywnie możesz utworzyć nową klasę za pomocą metody pomocniczej jako metody publicznej, jeśli jest ona wystarczająco dobrym kandydatem do nowej klasy.

Jest tutaj bardzo dobry artykuł na ten temat.

adamjmarkham
źródło
1
Uczciwy komentarz. To była tylko opcja, może zła.
adamjmarkham,
@Finglas Dlaczego testowanie prywatnego kodu przy użyciu refleksji jest złym pomysłem?
Anshul
Prywatne metody @Anshul to szczegóły implementacji, a nie zachowanie. Innymi słowy, zmieniają się. Nazwy się zmieniają. Parametry się zmieniają. Zawartość się zmienia. Oznacza to, że testy te cały czas się psują. Z drugiej strony metody publiczne są znacznie bardziej stabilne pod względem API, więc są bardziej odporne. Testy metod publicznych sprawdzają zachowanie, a nie szczegóły implementacji, powinieneś być dobry.
Finglas
1
@Finglas Dlaczego nie chcesz testować szczegółów implementacji? Jest to kod, który musi działać, a jeśli zmienia się częściej, oczekujesz, że będzie on częściej się psuł, co jest większym powodem do testowania go bardziej niż publicznego API. Po ustabilizowaniu bazy kodu, powiedzmy, że w przyszłości ktoś przyjdzie i zmieni prywatną metodę, która zakłóca wewnętrzne działanie twojej klasy. Test sprawdzający elementy wewnętrzne twojej klasy od razu powiedziałby im, że coś złamali. Tak, utrudniłoby to utrzymanie testów, ale tego można się spodziewać po testach pełnego pokrycia.
Anshul
1
@Finglas Szczegóły implementacji testów nie są nieprawidłowe. Takie jest twoje stanowisko w tej sprawie, ale jest to szeroko dyskutowany temat. Testowanie elementów wewnętrznych daje kilka korzyści, nawet jeśli nie są one wymagane do pełnego pokrycia. Twoje testy są bardziej skoncentrowane, ponieważ testujesz metody prywatne bezpośrednio, zamiast przechodzić przez publiczny interfejs API, który może zakończyć test. Negatywne testowanie jest łatwiejsze, ponieważ można ręcznie unieważnić członków prywatnych i upewnić się, że publiczny interfejs API zwraca odpowiednie błędy w odpowiedzi na niespójny stan wewnętrzny. Jest więcej przykładów.
Anshul
9

Jeśli używasz C #, możesz ustawić metodę wewnętrzną. W ten sposób nie zanieczyszczasz publicznego interfejsu API.

Następnie dodaj atrybut do biblioteki dll

[assembly: InternalsVisibleTo („MyTestAssembly”)]

Teraz wszystkie metody są widoczne w projekcie MyTestAssembly. Może nie jest idealny, ale lepszy niż upublicznienie prywatnej metody tylko po to, by ją przetestować.

Piotr Perak
źródło
7

W razie potrzeby użyj refleksji, aby uzyskać dostęp do zmiennych prywatnych.

Ale tak naprawdę nie zależy ci na wewnętrznym stanie klasy, po prostu chcesz przetestować, czy publiczne metody zwracają to, czego oczekujesz w sytuacjach, które możesz przewidzieć.

Daniel Alexiuc
źródło
Co zrobiłbyś z metodą void?
IntelliData,
@IntelliData W razie potrzeby użyj refleksji, aby uzyskać dostęp do zmiennych prywatnych.
Daniel Alexiuc
6

Powiedziałbym, że to zły pomysł, ponieważ nie jestem pewien, czy dostaniesz jakąś korzyść i potencjalnie problemy w dalszej kolejności. Jeśli zmieniasz umowę wywołań, tylko w celu przetestowania metody prywatnej, nie testujesz klasy pod kątem jej użycia, ale tworzysz sztuczny scenariusz, którego nigdy nie zamierzałeś się wydarzyć.

Co więcej, deklarując metodę jako publiczną, co powiedzieć, że za sześć miesięcy (po zapomnieniu, że jedynym powodem upublicznienia metody jest testowanie), że ty (lub jeśli przekazałeś projekt) ktoś zupełnie inny wygrał nie używaj go, co może prowadzić do niezamierzonych konsekwencji i / lub koszmaru związanego z konserwacją.

beny23
źródło
1
co jeśli klasa jest realizacją kontraktu serwisowego i jest używana tylko za pośrednictwem interfejsu - w ten sposób nawet jeśli metoda prywatna została upubliczniona, i tak nie zostanie wywołana, ponieważ nie jest widoczna
jcvandan
Nadal chciałbym przetestować klasę za pomocą publicznego interfejsu API (interfejsu), ponieważ w przeciwnym razie, jeśli refaktoryzujesz klasę do podziału metody wewnętrznej, będziesz musiał zmienić testy, co oznacza, że ​​będziesz musiał wydać trochę wysiłek zapewniający, że nowe testy działają tak samo jak stare testy.
beny23
Ponadto, jeśli jedynym sposobem na zapewnienie, że zasięg testu obejmuje każdy przypadek krawędzi w metodzie prywatnej, jest wywołanie go bezpośrednio, może to wskazywać, że złożoność klasy uzasadnia refaktoryzację w celu uproszczenia.
beny23
@dormisher - Jeśli klasa jest kiedykolwiek używana tylko przez interfejs, wystarczy przetestować, czy metody publiczne zachowują się poprawnie. Jeśli metody publiczne robią to, co powinny, dlaczego przejmujesz się metodami prywatnymi?
Andrzej Doyle,
@Andrzej - chyba nie powinienem, ale zdarzają się sytuacje, w których uważam, że może być poprawna, np. Metoda prywatna, która sprawdza stan modelu, warto przetestować taką metodę - ale taka metoda powinna być testowana za pomocą i tak interfejs publiczny
jcvandan
6

jeśli chodzi o testy jednostkowe, zdecydowanie nie powinieneś dodawać więcej metod; Uważam, że lepiej byś zrobił przypadek testowy dotyczący twojej first()metody, który byłby wywoływany przed każdym testem; Następnie można wywołać wiele razy - next(), previous()i last()zobaczyć, czy rezultaty dopasować swoje oczekiwania. Myślę, że jeśli nie dodasz więcej metod do swojej klasy (tylko do celów testowych), trzymasz się zasady testowania „czarnej skrzynki”;

Johny
źródło
5

Najpierw sprawdź, czy metodę należy wyodrębnić do innej klasy i podać do wiadomości publicznej. Jeśli tak nie jest, należy zabezpieczyć pakiet i napisać w Java adnotację @VisibleForTesting .

Noel Yap
źródło
5

W swojej aktualizacji mówisz, że dobrze jest po prostu przetestować przy użyciu publicznego interfejsu API. W rzeczywistości są tutaj dwie szkoły.

  1. Testowanie czarnej skrzynki

    Szkoła czarnej skrzynki mówi, że klasę należy uważać za czarną skrzynkę, której nikt nie widzi w niej implementacji. Jedynym sposobem na przetestowanie tego jest publiczny interfejs API - tak jak użytkownik klasy będzie go używał.

  2. testy białych skrzynek.

    Szkoła białych skrzynek uważa za naturalne wykorzystanie wiedzy na temat implementacji klasy, a następnie przetestowanie klasy pod kątem jej prawidłowości.

Naprawdę nie mogę się przyłączyć do dyskusji. Pomyślałem, że byłoby interesujące wiedzieć, że istnieją dwa różne sposoby testowania klasy (lub biblioteki lub cokolwiek innego).

bengtb
źródło
4

Są sytuacje, w których powinieneś to zrobić (np. Kiedy implementujesz skomplikowane algorytmy). Po prostu zrób to z pakietem prywatnym, a to wystarczy. Ale w większości przypadków prawdopodobnie masz zbyt złożone klasy, które wymagają podzielenia logiki na inne klasy.

Stanislav Bashkyrtsev
źródło
4

Prywatne metody, które chcesz przetestować w izolacji, wskazują, że w twojej klasie jest ukryta inna „koncepcja”. Wyodrębnij tę „koncepcję” do jej własnej klasy i przetestuj jako oddzielną „jednostkę”.

Obejrzyj ten film, aby uzyskać naprawdę interesujące podejście do tego tematu.

Jordão
źródło
4

Nigdy nie należy nigdy pozwalać testom na dyktowanie kodu. Nie mówię o TDD ani innych DD, mam na myśli dokładnie to, o co prosisz. Czy Twoja aplikacja potrzebuje tych metod, aby była publiczna. Jeśli tak, przetestuj je. Jeśli tak nie jest, to nie upubliczniaj ich tylko do testów. To samo dotyczy zmiennych i innych. Pozwól, aby potrzeby aplikacji podyktowały kod, a testy przetestuj, czy potrzeba jest spełniona. (Znowu nie mam na myśli testowania jako pierwszego, czy też nie, mam na myśli zmianę struktury klas, aby osiągnąć cel testowania).

Zamiast tego powinieneś „przetestować wyżej”. Przetestuj metodę wywołującą metodę prywatną. Ale twoje testy powinny testować potrzeby aplikacji, a nie „decyzje wdrożeniowe”.

Na przykład (pseudo kod bod tutaj);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Nie ma powodu, aby testować „dodaj”, można zamiast tego przetestować „książki”.

Nigdy nie pozwól, aby Twoje testy podejmowały decyzje dotyczące projektowania kodu za Ciebie. Sprawdź, czy uzyskasz oczekiwany wynik, a nie jak uzyskasz ten wynik.

Coteyr
źródło
1
„Nigdy nie pozwól, aby twoje testy podejmowały decyzje dotyczące projektowania kodu za Ciebie” - muszę się nie zgodzić. Trudność w testowaniu to zapach kodu. Jeśli nie możesz go przetestować, skąd wiesz, że nie jest uszkodzony?
Alfred Armstrong,
Dla wyjaśnienia zgadzam się, że nie powinieneś zmieniać na przykład zewnętrznego interfejsu API biblioteki. Ale może być konieczne przebudowanie, aby uczynić go bardziej testowalnym.
Alfred Armstrong,
Przekonałem się, że jeśli musisz dokonać refaktoryzacji w celu przetestowania, nie powinieneś martwić się zapachem kodu. Masz coś innego nie tak. Oczywiście to osobiste doświadczenie i obaj moglibyśmy prawdopodobnie kłócić się z solidnymi danymi w obie strony. Po prostu nigdy nie miałem „trudnego do przetestowania”, który nie byłby „złym projektem”.
coteyr
To rozwiązanie jest dobre dla metody takiej jak książki, która zwraca wartość - możesz sprawdzić typ zwrotu w asercie ; ale jak poszedłbyś sprawdzić metodę nieważności ?
IntelliData,
Metody void coś robią lub nie muszą istnieć. Sprawdź, co robią
coteyr
2

Zgadzam się, że premie za testowanie tej jednostki przeważają nad problemami związanymi ze zwiększeniem widoczności niektórych członków. Nieznaczne ulepszenie polega na tym, aby był chroniony i wirtualny, a następnie zastąpić go w klasie testowej, aby go ujawnić.

Alternatywnie, jeśli jego funkcjonalność chcesz przetestować osobno, czy nie sugeruje to brakującego obiektu w twoim projekcie? Może mógłbyś umieścić go w osobnej klasie testowalnej ... wtedy twoja istniejąca klasa deleguje się tylko do instancji tej nowej klasy.

IanR
źródło
2

Ogólnie utrzymuję klasy testowe w tym samym projekcie / zestawie co klasy testowane.
W ten sposób potrzebuję tylko internalwidoczności, aby funkcje / klasy mogły być testowane.

To nieco komplikuje proces budowania, który musi odfiltrować klasy testowe. Osiągam to, nazywając wszystkie moje klasy testowe TestedClassTesti używając wyrażenia regularnego do filtrowania tych klas.

Dotyczy to oczywiście tylko części C # / .NET pytania

yas4891
źródło
2

Ja często dodać metody nazywanej coś podobnego validate, verify, check, etc, do klasy tak, że może być wezwany do sprawdzenia stanu wewnętrznego obiektu.

Czasami ta metoda jest zapakowana w blok ifdef (piszę głównie w C ++), więc nie jest kompilowana do wydania. Ale często jest przydatne w wydaniu, aby zapewnić metody sprawdzania, które sprawdzają drzewa obiektów w programie.

Zan Lynx
źródło
2

Guava ma adnotację @VisibleForTesting do oznaczania metod, które mają większy zakres (pakietowy lub publiczny), niż w innym przypadku. Używam adnotacji @Private dla tego samego.

Podczas gdy publiczny interfejs API musi zostać przetestowany, czasem wygodnie i sensownie jest dostać się do rzeczy, które normalnie nie byłyby publiczne.

Kiedy:

  • klasa jest znacznie mniej czytelna, w całości, poprzez podzielenie jej na wiele klas,
  • żeby uczynić go bardziej testowalnym,
  • i zapewnienie testowego dostępu do wnętrzności załatwi sprawę

wygląda na to, że religia przewyższa inżynierię.

Ed Staub
źródło
2

Zazwyczaj zostawiam te metody jako protectedi umieszczam test jednostkowy w tym samym pakiecie (ale w innym projekcie lub folderze źródłowym), gdzie mogą uzyskać dostęp do wszystkich chronionych metod, ponieważ moduł ładujący klasy umieści je w tej samej przestrzeni nazw.

hiergiltdiestfu
źródło
2

Nie, ponieważ istnieją lepsze sposoby na skórowanie tego kota.

Niektóre wiązki testów jednostkowych opierają się na makrach w definicji klasy, które automatycznie rozszerzają się, tworząc haki, gdy są wbudowane w trybie testowym. Bardzo styl C, ale działa.

Łatwiejszym idiomem OO jest uczynienie czegokolwiek, co chcesz przetestować, „chronionym” niż „prywatnym”. Wiązka testowa dziedziczy po testowanej klasie, a następnie może uzyskać dostęp do wszystkich chronionych elementów.

Lub wybierz opcję „przyjaciel”. Osobiście jest to funkcja C ++, którą lubię najmniej, ponieważ łamie ona zasady enkapsulacji, ale zdarza się, że jest to konieczne do tego, jak C ++ implementuje niektóre funkcje, więc hej ho.

W każdym razie, jeśli przeprowadzasz testy jednostkowe, równie prawdopodobne jest, że będziesz musiał wprowadzić wartości do tych członków. Białe pola tekstowe są całkowicie poprawne. I to naprawdę byłoby złamać enkapsulacji.

Graham
źródło
2

W .Net istnieje specjalna klasa o nazwie PrivateObjectdeign specjalnie, aby umożliwić ci dostęp do prywatnych metod klasy.

Zobacz więcej na ten temat w MSDN lub tutaj na Przepełnienie stosu

(Zastanawiam się, jak dotąd nikt o tym nie wspominał.)

Są jednak sytuacje, których to nie wystarczy, w takim przypadku będziesz musiał użyć refleksji.

Nadal bym przestrzegał ogólnej rekomendacji, by nie testować metod prywatnych, jednak jak zwykle zawsze są wyjątki.

yoel halb
źródło
1

Jak zauważono w komentarzach innych, testy jednostkowe powinny koncentrować się na publicznym interfejsie API. Jednak poza zaletami / wadami i uzasadnieniem można wywoływać metody prywatne w teście jednostkowym za pomocą odbicia. Musisz oczywiście upewnić się, że zabezpieczenia JRE na to pozwalają. Wywoływanie metod prywatnych jest czymś, co wykorzystuje Spring Framework ze swoim ReflectionUtils (zobacz makeAccessible(Method)metodę).

Oto mała przykładowa klasa z metodą instancji prywatnej.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

I przykładowa klasa, która wykonuje metodę instancji prywatnej.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Wykonanie B, wydrukuje Doing something private. Jeśli naprawdę tego potrzebujesz, odbicie może być użyte w testach jednostkowych w celu uzyskania dostępu do metod instancji prywatnej.

Dan Cruz
źródło
0

Osobiście mam te same problemy podczas testowania metod prywatnych, a to dlatego, że niektóre narzędzia testowe są ograniczone. Nie jest dobrze, aby twój projekt był napędzany ograniczonymi narzędziami, jeśli nie reagują na twoją potrzebę, zmień narzędzie, a nie projekt. Ponieważ pytając o C # nie mogę zaproponować dobrych narzędzi do testowania, ale dla Javy istnieją dwa potężne narzędzia: TestNG i PowerMock, i można znaleźć odpowiednie narzędzia do testowania dla platformy .NET

Xoke
źródło