Jak przetestować funkcję prywatną lub klasę, która ma prywatne metody, pola lub klasy wewnętrzne?


2726

Jak przetestować jednostkę (używając xUnit) klasy, która ma wewnętrzne metody prywatne, pola lub klasy zagnieżdżone? A może funkcja, która jest prywatna dzięki wewnętrznemu powiązaniu ( staticw C / C ++) lub jest w prywatnej ( anonimowej ) przestrzeni nazw?

Zmiana modyfikatora dostępu do metody lub funkcji wydaje się zła, aby móc uruchomić test.


257
Najlepszym sposobem na przetestowanie prywatnej metody nie jest jej bezpośrednie testowanie
Surya


383
Nie zgadzam się. Metodę (publiczną), która jest długa lub trudna do zrozumienia, należy ponownie przeanalizować. Szaleństwem byłoby nie testować małych (prywatnych) metod, które otrzymujesz zamiast tylko publicznej.
Michael Piefel

181
Nie testowanie żadnych metod tylko dlatego, że ich widoczność jest głupia. Nawet test jednostkowy powinien dotyczyć najmniejszego fragmentu kodu, a jeśli przetestujesz tylko metody publiczne, nigdy nie będziesz na pewno wiedział, gdzie wystąpił błąd - ta metoda lub inna.
Dainius

126
Musisz przetestować funkcjonalność klasy , a nie jej implementację . Chcesz przetestować prywatne metody? Przetestuj publiczne metody, które je wywołują. Jeśli funkcjonalność oferowana przez klasę zostanie dokładnie przetestowana, jej elementy wewnętrzne okazały się prawidłowe i niezawodne; nie musisz testować warunków wewnętrznych. Testy powinny utrzymywać oddzielenie od testowanych klas.
Calimar41

Odpowiedzi:


1640

Aktualizacja:

Jakieś 10 lat później być może najlepszym sposobem przetestowania prywatnej metody lub dowolnego niedostępnego członka jest skorzystanie @Jailbreakz frameworka Manifold .

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

W ten sposób Twój kod pozostaje bezpieczny i czytelny. Żadnych kompromisów projektowych, żadnych metod i pól prześwietlania na potrzeby testów.

Jeśli masz nieco starszą aplikację Java i nie możesz zmienić widoczności swoich metod, najlepszym sposobem na przetestowanie metod prywatnych jest użycie refleksji .

Wewnętrznie używamy pomocników do pobierania / ustawiania privatei private staticzmiennych, a także wywoływania privatei private staticmetod. Poniższe wzory pozwolą ci zrobić prawie wszystko, co jest związane z prywatnymi metodami i polami. Oczywiście nie można zmieniać private static finalzmiennych poprzez odbicie.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

A dla pól:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Uwagi:
1. TargetClass.getDeclaredMethod(methodName, argClasses)pozwala spojrzeć na privatemetody. To samo dotyczy getDeclaredField.
2. Wymagane setAccessible(true)jest, aby bawić się z szeregowymi.


350
Przydatne, jeśli nie znasz API, ale jeśli musisz przetestować prywatne metody w ten sposób, coś jest nie tak z twoim projektem. Jak mówi inny plakat, testy jednostkowe powinny przetestować kontrakt klasy: jeśli kontrakt jest zbyt szeroki i tworzy za dużo systemu, projekt powinien zostać rozwiązany.
andygavin

21
Bardzo przydatne. Korzystając z tego, należy pamiętać, że nie powiedzie się to źle, jeśli testy zostaną przeprowadzone po zaciemnieniu.
Rick Minerich

20
Przykładowy kod nie działał dla mnie, ale dzięki temu thigs były
Rob

35
O wiele lepszym rozwiązaniem niż bezpośrednie odbicie byłoby użycie do tego biblioteki, takiej jak Powermock .
Michael Piefel

25
Dlatego pomocny jest Test Driven Design. Pomaga ustalić, co należy ujawnić, aby potwierdzić zachowanie.
Thorbjørn Ravn Andersen

621

Najlepszym sposobem przetestowania metody prywatnej jest inna metoda publiczna. Jeśli nie można tego zrobić, spełniony jest jeden z następujących warunków:

  1. Metoda prywatna to martwy kod
  2. W pobliżu testowanej klasy wyczuwa się zapach wzorniczy
  3. Metoda, którą próbujesz przetestować, nie powinna być prywatna

6
Co więcej, nigdy nie osiągamy prawie 100% pokrycia kodu, więc dlaczego nie skoncentrować czasu na testowaniu jakości na metodach, które klienci będą faktycznie stosować bezpośrednio.
uśmiechnij się

30
@grinch Spot na. Jedyne, co zyskasz dzięki testowaniu metod prywatnych, to debugowanie informacji i do tego służą debuggery. Jeśli Twoje testy umowy klasy mają pełne pokrycie, masz wszystkie potrzebne informacje. Metody prywatne są szczegółami implementacji . Jeśli je przetestujesz, będziesz musiał zmieniać testy za każdym razem, gdy zmieni się implementacja, nawet jeśli umowa tego nie zrobi. W całym cyklu życia oprogramowania może to kosztować znacznie więcej niż daje to korzyści.
Erik Madsen,

9
@AlexWien masz rację. Zakres nie był właściwym terminem. Powinienem był powiedzieć „jeśli twoje testy kontraktu klasy obejmują wszystkie znaczące dane wejściowe i wszystkie znaczące stany”. To, jak słusznie zauważyłeś, może okazać się niemożliwe do przetestowania za pomocą publicznego interfejsu do kodu lub pośrednio, jak się do niego odwołujesz. Jeśli tak jest w przypadku testowanego kodu, byłbym skłonny pomyśleć: (1) Umowa jest zbyt hojna (np. Zbyt wiele parametrów w metodzie), lub (2) Umowa jest zbyt niejasna (np. Zachowanie metody różni się znacznie w zależności od stanu klasy). Rozważę oba zapachy projektowe.
Erik Madsen

16
Prawie wszystkie prywatne metody nie powinny być testowane bezpośrednio (w celu zmniejszenia kosztów utrzymania). Wyjątek: metody naukowe mogą mieć formy takie jak f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) gdzie a, b, c, d, e i f są strasznie skomplikowanymi wyrażeniami, ale poza tym są bezużyteczne poza tą pojedynczą formułą. Niektóre funkcje (zdaniem MD5) są trudne do odwrócenia, więc może być trudno (NP-Hard) wybrać parametry, które w pełni pokryją przestrzeń zachowań. Łatwiej jest przetestować a, b, c, d, e, f niezależnie. Upublicznianie lub kłamstwa, które mogą być ponownie wykorzystane. Rozwiązanie: ustaw je jako prywatne, ale przetestuj je.
Tytułowy

4
@Eponymous Jestem trochę rozdarty na ten temat. Zorientowany obiektowo purysta powiedziałby, że powinieneś używać podejścia zorientowanego obiektowo, a nie statycznych metod prywatnych, umożliwiających testowanie za pomocą metod instancji publicznej. Ale z drugiej strony, w naukowym schemacie narzut pamięci może być problemem, który moim zdaniem przemawia za podejściem statycznym.
Erik Madsen,

323

Kiedy mam prywatne metody w klasie, które są na tyle skomplikowane, że odczuwam potrzebę bezpośredniego przetestowania metod prywatnych, to jest zapach kodu: moja klasa jest zbyt skomplikowana.

Moje zwykłe podejście do rozwiązywania takich problemów polega na wprowadzeniu nowej klasy, która zawiera interesujące elementy. Często ta metoda i pola, z którymi wchodzi w interakcje, a może inna metoda lub dwie mogą zostać wyodrębnione do nowej klasy.

Nowa klasa ujawnia te metody jako „publiczne”, więc są one dostępne do testów jednostkowych. Zarówno nowa, jak i stara klasa są teraz prostsze niż klasa oryginalna, co jest dla mnie świetne (muszę zachować prostotę, inaczej się zgubię!).

Pamiętaj, że nie sugeruję, aby ludzie tworzyli zajęcia bez korzystania z mózgu! Chodzi o to, aby użyć siły testowania jednostkowego, aby pomóc ci znaleźć dobre nowe klasy.


24
Pytanie dotyczyło sposobu testowania metod prywatnych. Mówisz, że zrobiłbyś dla tego nową klasę (i dodałbyś znacznie więcej złożoności) i po tym sugerujesz, aby nie tworzyć nowej klasy. Jak więc przetestować prywatne metody?
Dainius

27
@Dainius: Nie sugeruję tworzenia nowej klasy wyłącznie po to, abyś mógł przetestować tę metodę. Sugeruję, że testy pisemne mogą pomóc w ulepszeniu projektu: dobre projekty są łatwe do przetestowania.
Jay Bazuzi,

13
Ale zgadzasz się, że dobre OOD ujawniłoby (upubliczniło) tylko te metody, które są niezbędne do poprawnego działania tej klasy / obiektu? wszystkie pozostałe powinny być prywatne / Protectec. Tak więc w niektórych prywatnych metodach będzie logika, a testowanie przez IMO tych metod poprawi jedynie jakość oprogramowania. Oczywiście zgadzam się, że jeśli jakiś fragment kodu jest złożony, należy go podzielić na osobne metody / klasy.
Dainius

6
@Danius: Dodanie nowej klasy w większości przypadków zmniejsza złożoność. Po prostu zmierz. Testowalność w niektórych przypadkach walczy z OOD. Nowoczesne podejście sprzyja testowalności.
AlexWien

5
To jest dokładnie to, w jaki sposób korzystać z informacji zwrotnych od testów do napędu i poprawić swój projekt! Posłuchaj swoich testów. jeśli są trudne do napisania, oznacza to coś ważnego o kodzie!
pnschofield,

289

W przeszłości korzystałem z refleksji, aby to zrobić dla Javy, i moim zdaniem był to duży błąd.

Ściśle rzecz biorąc, powinno nie być pisanie testów jednostkowych, które bezpośrednio testowania metod prywatnych. To, co powinieneś przetestować, to umowa publiczna, którą klasa ma z innymi przedmiotami; nigdy nie należy bezpośrednio testować wewnętrznych elementów obiektu. Jeśli inny programista chce wprowadzić niewielką wewnętrzną zmianę w klasie, która nie wpływa na zamówienie publiczne na zajęcia, musi zmodyfikować test oparty na refleksji, aby upewnić się, że działa. Jeśli robisz to wielokrotnie w trakcie projektu, testy jednostkowe przestają być użytecznym pomiarem stanu kodu i stają się przeszkodą w rozwoju oraz irytacją dla zespołu programistów.

Zamiast tego zalecam korzystanie z narzędzia pokrycia kodu, takiego jak Cobertura, aby upewnić się, że pisane testy jednostkowe zapewniają przyzwoite pokrycie kodu metodami prywatnymi. W ten sposób pośrednio testujesz, co robią prywatne metody, i utrzymujesz wyższy poziom zwinności.


76
+1 do tego. Moim zdaniem jest to najlepsza odpowiedź na pytanie. Testując metody prywatne testujesz implementację. Jest to sprzeczne z celem testowania jednostkowego, którym jest testowanie danych wejściowych / wyjściowych kontraktu klasy. Test należy tylko wiedzieć wystarczająco dużo o realizacji mock metod wywołuje na jego zależnościami. Nic więcej. Jeśli nie możesz zmienić swojej implementacji bez konieczności zmiany testu - są szanse, że Twoja strategia testowa jest zła.
Colin M.,

10
@Colin M Tak naprawdę nie o to pyta;) Pozwól mu zdecydować, nie znasz projektu.
Aaron Marcus

2
Niezupełnie prawda. Testowanie niewielkiej części metody prywatnej za pomocą metody publicznej może wymagać wiele wysiłku. Metoda publiczna może wymagać znacznej konfiguracji, zanim dojdziesz do linii, która wywołuje twoją metodę prywatną
ACV

1
Zgadzam się. Powinieneś sprawdzić, czy umowa jest spełniona. Nie powinieneś testować, jak to się robi. Jeśli kontakt zostanie spełniony bez osiągnięcia 100% pokrycia kodu (metodami prywatnymi), może to być martwy lub bezużyteczny kod.
wuppi

207

Z tego artykułu: Testowanie metod prywatnych za pomocą JUnit i SuiteRunner (Bill Venners), w zasadzie masz 4 opcje:

  1. Nie testuj metod prywatnych.
  2. Daj dostęp do pakietu metod.
  3. Użyj zagnieżdżonej klasy testowej.
  4. Użyj refleksji.

@ JimmyT., Zależy od tego, dla kogo przeznaczony jest „kod produkcyjny”. Zadzwoniłbym do kodu produkcyjnego dla aplikacji stacjonujących w sieci VPN, których docelowymi użytkownikami są sysadmins.
Pacerier

@Pacerier Co masz na myśli?
Jimmy T.

1
Piąta opcja, jak wspomniano powyżej, to testowanie metody publicznej, która wywołuje metodę prywatną? Poprawny?
user2441441,

1
@LorenzoLerate Walczę z tym, ponieważ zajmuję się tworzeniem oprogramowania wbudowanego i chciałbym, aby moje oprogramowanie było jak najmniejsze. Ograniczenia wielkości i wydajność to jedyny powód, dla którego mogę myśleć.

Naprawdę podoba mi się test wykorzystujący odbicie: tutaj wzorzec Metoda metody = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid

127

Zasadniczo test jednostkowy ma na celu wykonanie publicznego interfejsu klasy lub jednostki. W związku z tym metody prywatne są szczegółami implementacji, których nie można oczekiwać bezpośrednio testować.


16
To najlepsza odpowiedź IMO lub, jak się zwykle mówi, zachowanie testowe, a nie metody. Testy jednostkowe nie zastępują metryk kodu źródłowego, narzędzi do analizy kodu statycznego i recenzji kodu. Jeśli prywatne metody są tak skomplikowane, że wymagają oddzielnych testów, prawdopodobnie należy je zrefaktoryzować, a nie poddać ich więcej testom.
Dan Haynes

14
więc nie pisz prywatnych metod, po prostu stwórz 10 innych małych klas?
brzytwa

1
Nie, oznacza to po prostu, że możesz przetestować funkcjonalność metod prywatnych przy użyciu metody publicznej, która je wywołuje.
Akshat Sharda

66

Tylko dwa przykłady, w których chciałbym przetestować metodę prywatną:

  1. Procedury deszyfrowania - nie chciałbym, aby były widoczne dla każdego, kto mógłby je zobaczyć tylko ze względu na testowanie, w przeciwnym razie każdy może ich użyć do odszyfrowania. Są one jednak nieodłącznie związane z kodem, skomplikowane i muszą zawsze działać (oczywistym wyjątkiem jest odbicie, które w większości przypadków można wykorzystać do przeglądania nawet prywatnych metod, jeśli SecurityManagernie jest skonfigurowane, aby temu zapobiec).
  2. Tworzenie zestawu SDK do użytku społeczności. Tutaj public ma zupełnie inne znaczenie, ponieważ jest to kod, który może zobaczyć cały świat (nie tylko wewnętrzny dla mojej aplikacji). Umieszczam kod w metodach prywatnych, jeśli nie chcę, aby użytkownicy SDK go widzieli - nie widzę w nim zapachu kodu, a jedynie działania programu SDK. Ale oczywiście nadal muszę przetestować moje prywatne metody i tam właśnie funkcjonuje mój zestaw SDK.

Rozumiem pomysł testowania tylko „kontraktu”. Ale nie widzę, aby można było opowiadać się za nie testowaniem kodu - przebieg może się różnić.

Więc mój kompromis polega na komplikowaniu JUnits refleksją, a nie na naruszeniu bezpieczeństwa i zestawu SDK.


5
Chociaż nigdy nie powinieneś upubliczniać metody w celu jej przetestowania, privatenie jest to jedyna alternatywa. Brak modyfikatora dostępu package privateoznacza, że ​​można go testować jednostkowo, dopóki test jednostkowy trwa w tym samym pakiecie.
ngreen

1
Komentowałem twoją odpowiedź, w szczególności punkt 1, a nie OP. Nie ma potrzeby, aby metoda była prywatna tylko dlatego, że nie chcesz, aby była publiczna.
ngreen

1
@ngreen true thx - byłem leniwy ze słowem „public”. Zaktualizowałem odpowiedź, aby uwzględnić publiczny, chroniony, domyślny (i wspomnieć o refleksji). Chodzi mi o to, że istnieje dobry powód, aby jakiś kod był tajny, jednak nie powinno to uniemożliwiać nam jego przetestowania.
Richard Le Mesurier

2
Dziękujemy za podanie każdemu prawdziwych przykładów. Większość odpowiedzi jest dobra, ale z teoretycznego punktu widzenia. W prawdziwym świecie musimy ukryć wdrożenie przed umową i nadal musimy to przetestować. Czytając te wszystkie, myślę, że może to powinien być zupełnie nowy poziom testowania, pod inną nazwą
Z. Khullah

1
Jeszcze jeden przykład - parsery HTML przeszukiwacza . Konieczność parsowania całej strony html w obiekty domeny wymaga tony drobiazgowej logiki sprawdzania poprawności małych części struktury. Rozsyłanie tego na metody i wywoływanie z jednej metody publicznej ma sens, ale nie ma sensu, aby wszystkie mniejsze metody były publiczne, ani też nie ma sensu tworzenie 10 klas na stronę HTML.
Nether

59

Metody prywatne są wywoływane przez metodę publiczną, więc dane wejściowe do metod publicznych powinny również testować metody prywatne wywoływane przez te metody publiczne. Gdy metoda publiczna zawiedzie, może to oznaczać awarię metody prywatnej.


Prawdziwy fakt. Problem polega na tym, że implementacja jest bardziej skomplikowana, na przykład gdy jedna metoda publiczna wywołuje kilka metod prywatnych. Który zawiódł? Lub jeśli jest to skomplikowana metoda prywatna, której nie można
ujawnić

Wspomniałeś już, dlaczego należy przetestować metodę prywatną. Powiedziałeś „to może być”, więc nie jesteś pewien. IMO, czy metoda powinna zostać przetestowana, jest prostopadła do swojego poziomu dostępu.
Wei Qiu

39

Innym podejściem, którego użyłem, jest zmiana prywatnej metody na prywatną lub chronioną, a następnie uzupełnienie jej adnotacją @VisibleForTesting biblioteki Google Guava.

To powie każdemu, kto używa tej metody, aby zachował ostrożność i nie uzyskał bezpośredniego dostępu nawet w pakiecie. Również klasa testowa nie musi znajdować się fizycznie w tym samym pakiecie, ale w tym samym pakiecie w folderze testowym .

Na przykład, jeśli istnieje metoda, która ma zostać przetestowana, src/main/java/mypackage/MyClass.javawówczas należy wprowadzić wywołanie testowe src/test/java/mypackage/MyClassTest.java. W ten sposób masz dostęp do metody testowej w swojej klasie testowej.


1
Nie wiedziałem o tym, to jest ciekawe, wciąż myślę, że jeśli potrzebujesz tego rodzaju adnotacji, masz problemy z projektowaniem.
Seb

2
Dla mnie jest to jak powiedzenie zamiast dawania jednorazowego klucza swojemu strażnikowi ognia w celu przetestowania wiertła przeciwpożarowego, pozostawiasz otwarte drzwi frontowe ze znakiem na drzwiach mówiącym: „odblokowanym do testowania - jeśli nie jesteś z nasza firma, proszę nie wpisywać „.
Ks. Jeremy Krieg

@FrJeremyKrieg - co to znaczy?
MasterJoe2

1
@ MasterJoe2: celem testu ogniowego jest poprawa bezpieczeństwa budynku przed ryzykiem pożaru. Musisz jednak zapewnić strażnikowi dostęp do swojego budynku. Jeśli pozostawisz otwarte drzwi frontowe otwarte, zwiększasz ryzyko nieautoryzowanego dostępu i czynisz je mniej bezpiecznym. To samo dotyczy wzorca VisibleForTesting - zwiększasz ryzyko nieautoryzowanego dostępu w celu zmniejszenia ryzyka „pożaru”. Lepiej przyznać dostęp jako jednorazowy test tylko wtedy, gdy jest to konieczne (np. Za pomocą odbicia), niż pozostawić go na stałe odblokowanym (nieprywatnym).
Ks. Jeremy Krieg

34

Aby przetestować starszy kod za pomocą dużych i dziwacznych klas, często bardzo pomocne jest przetestowanie jednej prywatnej (lub publicznej) metody, którą piszę teraz .

Korzystam z pakietu junitx.util.PrivateAccessor dla Java. Wiele pomocnych jedno-liniowych narzędzi umożliwiających dostęp do prywatnych metod i prywatnych pól.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

2
Należy pobrać cały pakiet JUnit-addons ( sourceforge.net/projects/junit-addons ), a nie pakiet PrivateAccessor zalecany przez Source Forge.
skia.heliou,

2
I upewnij się, że kiedy używasz Classjako pierwszego parametru w tych metodach, masz dostęp tylko do staticczłonków.
skia.heliou,

31

W Spring Framework możesz testować prywatne metody przy użyciu tej metody:

ReflectionTestUtils.invokeMethod()

Na przykład:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Najczystsze i najbardziej zwięzłe jak dotąd rozwiązanie, ale tylko jeśli używasz Spring.
Denis Nikolaenko

28

Po wypróbowaniu rozwiązania Cem Catikkas za pomocą odbicia dla Javy, muszę powiedzieć, że jego rozwiązanie było bardziej eleganckie niż to tutaj opisałem. Jeśli jednak szukasz alternatywy dla używania refleksji i masz dostęp do testowanego źródła, nadal będzie to możliwe.

Możliwe są zalety testowania prywatnych metod klasy, szczególnie w przypadku programowania opartego na testach , w którym chciałbyś zaprojektować małe testy przed napisaniem jakiegokolwiek kodu.

Utworzenie testu z dostępem do prywatnych członków i metod może testować obszary kodu, które są trudne do ukierunkowania, szczególnie z dostępem tylko do metod publicznych. Jeśli metoda publiczna obejmuje kilka etapów, może składać się z kilku metod prywatnych, które można następnie przetestować indywidualnie.

Zalety:

  • Może testować z większą dokładnością

Niedogodności:

  • Kod testowy musi znajdować się w tym samym pliku co kod źródłowy, co może być trudniejsze w utrzymaniu
  • Podobnie z plikami wyjściowymi .class, muszą pozostać w tym samym pakiecie, co zadeklarowano w kodzie źródłowym

Jeśli jednak ciągłe testowanie wymaga tej metody, może to być sygnał, że należy wyodrębnić metody prywatne, które można przetestować w tradycyjny, publiczny sposób.

Oto skomplikowany przykład tego, jak to by działało:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

Klasa wewnętrzna zostałaby skompilowana ClassToTest$StaticInnerTest .

Zobacz także: Java Tip 106: Statyczne klasy wewnętrzne dla zabawy i zysku


26

Jak powiedzieli inni ... nie testuj metod prywatnych bezpośrednio. Oto kilka myśli:

  1. Wszystkie metody powinny być małe i skoncentrowane (łatwe do przetestowania, łatwe do znalezienia, co jest nie tak)
  2. Użyj narzędzi do pokrywania kodu. Lubię Coberturę (och, szczęśliwy dzień, wygląda na to, że nowa wersja została wydana!)

Uruchom obsługę kodu w testach jednostkowych. Jeśli zauważysz, że metody nie są w pełni przetestowane, dodaj je do testów, aby zwiększyć zasięg. Celuj w 100% pokrycie kodu, ale zdaj sobie sprawę, że prawdopodobnie go nie dostaniesz.


Do pokrycia kodu. Bez względu na to, jaki rodzaj logiki jest w metodzie prywatnej, nadal wywołujesz tę logikę za pomocą metody publicznej. Narzędzie pokrycia kodu może pokazać, które części są objęte testem, dlatego możesz sprawdzić, czy twoja metoda prywatna jest testowana.
coolcfan,

Widziałem klasy, w których jedyną publiczną metodą jest main [], a one wyświetlają GUI oraz łączą bazę danych i kilka serwerów WWW na całym świecie. Łatwo powiedzieć „nie testuj pośrednio” ...
Audrius Meskauskas,

26

Metody prywatne są konsumowane przez metody publiczne. W przeciwnym razie są martwym kodem. Dlatego testujesz metodę publiczną, potwierdzając oczekiwane wyniki metody publicznej, a tym samym metod prywatnych, które zużywa.

Testowanie metod prywatnych należy przetestować poprzez debugowanie przed uruchomieniem testów jednostkowych na metodach publicznych.

Mogą być również debugowane przy użyciu programowania opartego na testach, debugując testy jednostkowe, dopóki wszystkie asercje nie zostaną spełnione.

Osobiście uważam, że lepiej jest tworzyć klasy przy użyciu TDD; tworzenie kodów pośredniczących metody publicznej, a następnie generowanie testów jednostkowych ze wszystkimi asercjami wcześniej zdefiniowanymi, więc oczekiwany wynik metody jest określany przed jej kodowaniem. W ten sposób nie pójdziesz niewłaściwą ścieżką dopasowania stwierdzeń testu jednostkowego do wyników. Twoja klasa jest wtedy solidna i spełnia wymagania, gdy wszystkie testy jednostkowe przejdą pomyślnie.


2
Chociaż jest to prawda, może prowadzić do bardzo skomplikowanych testów - lepiej jest testować pojedynczą metodę na raz (testowanie jednostkowe) niż całą ich grupę. Nie jest to okropna sugestia, ale myślę, że są tutaj lepsze odpowiedzi - to powinna być ostateczność.
Bill K

24

Jeśli używasz Springa, ReflectionTestUtils zapewnia kilka przydatnych narzędzi, które pomagają tutaj przy minimalnym wysiłku. Na przykład, aby skonfigurować próbkę na prywatnym elemencie, bez konieczności dodawania niepożądanego ustawienia publicznego:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

Jeśli próbujesz przetestować istniejący kod, którego nie chcesz lub nie możesz zmienić, odbicie jest dobrym wyborem.

Jeśli projekt klasy jest nadal elastyczny, a masz skomplikowaną metodę prywatną, którą chciałbyś przetestować osobno, sugeruję, abyś wyciągnął ją do osobnej klasy i przetestował osobno. Nie musi to zmieniać publicznego interfejsu oryginalnej klasy; może wewnętrznie utworzyć instancję klasy pomocniczej i wywołać metodę pomocniczą.

Jeśli chcesz przetestować trudne warunki błędu wynikające z metody pomocniczej, możesz pójść o krok dalej. Wyodrębnij interfejs z klasy pomocnika, dodaj publiczny moduł pobierający i ustawiający do klasy oryginalnej, aby wstrzyknąć klasę pomocnika (używaną przez interfejs), a następnie wstrząśnij próbną wersję klasy pomocnika do klasy oryginalnej, aby sprawdzić, jak klasa oryginalna reaguje na wyjątki od pomocnika. Takie podejście jest również pomocne, jeśli chcesz przetestować oryginalną klasę bez testowania również klasy pomocniczej.


19

Testowanie metod prywatnych przerywa enkapsulację klasy, ponieważ za każdym razem, gdy zmieniasz wewnętrzną implementację, łamiesz kod klienta (w tym przypadku testy).

Więc nie testuj prywatnych metod.


4
test jednostkowy i kod src są parą. Jeśli zmienisz src, być może będziesz musiał zmienić test jednostkowy. To jest sens testu junit. Gwarantują, że wszystko działa jak poprzednio. i jest w porządku, jeśli się zepsują, jeśli zmienisz kod.
AlexWien

1
Nie zgadzaj się, że test jednostkowy powinien ulec zmianie w przypadku zmiany kodu. Jeśli zostaniesz poproszony o dodanie funkcjonalności do klasy bez zmiany istniejącej funkcjonalności klasy, wówczas istniejące testy jednostkowe powinny przejść pomyślnie nawet po zmianie kodu. Jak wspomina Peter, testy jednostkowe powinny testować interfejs, a nie sposób wykonywania tego wewnętrznie. W Test Driven Development testy jednostkowe są tworzone przed napisaniem kodu, aby skupić się na interfejsie klasy, a nie na tym, jak jest on rozwiązywany wewnętrznie.
Harald Coppoolse

16

Odpowiedź ze strony FAQ JUnit.org :

Ale jeśli musisz ...

Jeśli korzystasz z JDK 1.3 lub nowszego, możesz użyć refleksji, aby obalić mechanizm kontroli dostępu za pomocą PrivilegedAccessor . Aby uzyskać szczegółowe informacje na temat korzystania z niego, przeczytaj ten artykuł .

Jeśli używasz JDK 1.6 lub nowszego i adnotujesz swoje testy za pomocą @Test, możesz użyć Dp4j, aby wprowadzić odbicie w twoich metodach testowych. Aby uzyskać szczegółowe informacje na temat korzystania z niego, zobacz ten skrypt testowy .

PS Jestem głównym współpracownikiem Dp4j , zapytaj mnie, czy potrzebujesz pomocy. :)


16

Jeśli chcesz przetestować prywatne metody starszej aplikacji, w której nie możesz zmienić kodu, jedną z opcji dla Javy jest jMockit , który pozwoli ci tworzyć symulacje obiektu, nawet gdy są one prywatne dla klasy.


4
W ramach biblioteki jmockit masz dostęp do klasy Deencapsulation, która ułatwia testowanie metod prywatnych: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.,

Cały czas używam tego podejścia. Bardzo pomocny
MedicineMan

14

Nie testuję metod prywatnych. Istnieje szaleństwo. Osobiście uważam, że powinieneś testować tylko publicznie udostępnione interfejsy (i obejmuje to metody chronione i wewnętrzne).


14

Jeśli używasz JUnit, spójrz na junit-addons . Ma możliwość zignorowania modelu bezpieczeństwa Java i dostępu do prywatnych metod i atrybutów.


13

Sugerowałbym trochę przeredagować kod. Kiedy musisz zacząć zastanawiać się nad użyciem refleksji lub innego rodzaju rzeczy, do testowania kodu coś jest nie tak.

Wspomniałeś o różnych rodzajach problemów. Zacznijmy od prywatnych pól. W przypadku pól prywatnych dodałbym nowy konstruktor i do niego wstrzyknąłem pola. Zamiast tego:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Użyłbym tego:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Nie będzie to problemem nawet w przypadku niektórych starszych kodów. Stary kod będzie używał pustego konstruktora, a jeśli zapytasz mnie, zrefaktoryzowany kod będzie wyglądał na czystszy, a będziesz mógł wstrzyknąć niezbędne wartości w teście bez refleksji.

Teraz o metodach prywatnych. Z mojego osobistego doświadczenia, kiedy musisz wprowadzić prywatną metodę do testowania, ta metoda nie ma nic wspólnego z tą klasą. Typowym wzorcem w takim przypadku byłoby zawinięcie go w interfejs, na przykład, Callablea następnie przekazanie tego interfejsu również w konstruktorze (z tą sztuczką wielokrotnego konstruktora):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Przeważnie wszystko, co napisałem, wygląda na wzór wstrzyknięcia zależności. Z mojego osobistego doświadczenia jest to bardzo przydatne podczas testowania i myślę, że ten rodzaj kodu jest czystszy i łatwiejszy w utrzymaniu. To samo powiedziałbym o klasach zagnieżdżonych. Jeśli zagnieżdżona klasa zawiera ciężką logikę, lepiej byłoby przenieść ją jako prywatną klasę pakietu i wstrzyknąć do klasy, która tego potrzebuje.

Istnieje również kilka innych wzorców projektowych, których użyłem podczas refaktoryzacji i utrzymywania starszego kodu, ale wszystko zależy od przypadków testowania twojego kodu. Korzystanie z refleksji w większości nie stanowi problemu, ale jeśli masz mocno przetestowaną aplikację korporacyjną i testy są uruchamiane przed każdym wdrożeniem, wszystko staje się bardzo wolne (to po prostu denerwujące i nie lubię tego typu rzeczy).

Jest też zastrzyk setera, ale nie zalecałbym go używać. Lepiej trzymam się konstruktora i inicjuję wszystko, gdy jest to naprawdę konieczne, pozostawiając możliwość wstrzyknięcia niezbędnych zależności.


1
Nie zgadzaj się z ClassToTest(Callable)zastrzykiem. To ClassToTestkomplikuje sprawę. Nie komplikuj. To także wymaga od kogoś innego powiedzenia ClassToTestczegoś, co ClassToTestpowinno być szefem. Jest miejsce na zastrzykiwanie logiki, ale to nie wszystko. Właśnie sprawiłeś, że klasa jest trudniejsza w utrzymaniu, a nie łatwiejsza.
Loduwijk,

Zalecane jest również, jeśli twoja metoda testowania Xnie zwiększa Xzłożoności do tego stopnia, że ​​może powodować więcej problemów ... a zatem wymaga dodatkowych testów ... które zostały wdrożone w sposób, który może powodować więcej problemów ... (nie jest to nieskończona pętla; każda iteracja jest prawdopodobnie mniejsza niż poprzednia, ale wciąż denerwująca)
Loduwijk

2
Czy możesz wyjaśnić, dlaczego utrudniłoby to ClassToTestutrzymanie klasy ? W rzeczywistości ułatwia to utrzymanie aplikacji. Co sugerujesz, tworząc nową klasę dla każdej innej wartości, której potrzebujesz, firsti zmiennych „drugich”?
GROX13

1
Metoda testowania nie zwiększa złożoności. To tylko twoja klasa, która jest źle napisana, tak źle, że nie można jej przetestować.
GROX13

Trudniejsze w utrzymaniu, ponieważ klasa jest bardziej skomplikowana. class A{ A(){} f(){} }jest prostsze niż class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Gdyby nie było to prawdą i gdyby prawdą było, że dostarczanie logiki klasy jako argumentów konstruktora było łatwiejsze do utrzymania, wówczas przekazalibyśmy całą logikę jako argumenty konstruktora. Po prostu nie rozumiem, jak można twierdzić class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }, że kiedykolwiek ułatwia A. Są przypadki, w których takie wstrzykiwanie ma sens, ale nie uważam twojego przykładu za „łatwiejszy w utrzymaniu”
Loduwijk,

12

Dostęp do prywatnej metody można uzyskać tylko w ramach tej samej klasy. Nie ma więc możliwości przetestowania „prywatnej” metody klasy docelowej z żadnej klasy testowej. Wyjściem jest to, że możesz przeprowadzić testy jednostkowe ręcznie lub zmienić metodę z „prywatnej” na „chronioną”.

Następnie dostęp do chronionej metody można uzyskać tylko w tym samym pakiecie, w którym zdefiniowano klasę. Zatem testowanie chronionej metody klasy docelowej oznacza, że ​​musimy zdefiniować klasę testową w tym samym pakiecie co klasa docelowa.

Jeśli wszystkie powyższe warunki nie odpowiadają twoim wymaganiom, użyj metody odbicia, aby uzyskać dostęp do prywatnej metody.


Miksujesz „chroniony” z „przyjacielskim”. Dostęp do metody chronionej może uzyskać tylko klasa, której obiekty można przypisać do klasy docelowej (tj. Podklasy).
Sayo Oladeji

1
W rzeczywistości w Javie nie ma „przyjaznego”, termin to „pakiet”, na co wskazuje brak modyfikatora prywatnego / publicznego / chronionego. Właśnie skorygowałbym tę odpowiedź, ale istnieje naprawdę dobra odpowiedź, która już to mówi - więc polecam ją usunąć.
Bill K

Prawie zlekceważyłem, cały czas myśląc: „Ta odpowiedź jest po prostu zła”. aż dotarłem do ostatniego zdania, które jest sprzeczne z resztą odpowiedzi. Ostatnie zdanie powinno być pierwsze.
Loduwijk,

11

Jak wielu sugerowało powyżej, dobrym sposobem jest przetestowanie ich za pomocą publicznych interfejsów.

Jeśli to zrobisz, dobrym pomysłem jest skorzystanie z narzędzia do pokrycia kodu (takiego jak Emma), aby sprawdzić, czy twoje prywatne metody są w rzeczywistości wykonywane na podstawie testów.


Nie powinieneś pośrednio testować! Nie tylko dotykając przez zasięg; sprawdź, czy oczekiwany wynik został dostarczony!
AlexWien

11

Oto moja ogólna funkcja do testowania pól prywatnych:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

10

Przykład poniżej;

Należy dodać następującą instrukcję importu:

import org.powermock.reflect.Whitebox;

Teraz możesz bezpośrednio przekazać obiekt, który ma metodę prywatną, nazwę metody do wywołania i dodatkowe parametry, jak poniżej.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

Dzisiaj wypchnąłem bibliotekę Java, aby pomóc w testowaniu prywatnych metod i pól. Został zaprojektowany z myślą o systemie Android, ale naprawdę można go używać w każdym projekcie Java.

Jeśli masz kod z prywatnymi metodami lub polami lub konstruktorami, możesz użyć BoundBox . Robi dokładnie to, czego szukasz. Poniżej znajduje się przykład testu, który uzyskuje dostęp do dwóch prywatnych pól działania Androida, aby go przetestować:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox ułatwia testowanie prywatnych / chronionych pól, metod i konstruktorów. Możesz nawet uzyskać dostęp do rzeczy ukrytych przez dziedziczenie. Rzeczywiście, BoundBox łamie enkapsulację. Zapewni ci to dostęp poprzez refleksję, ALE wszystko jest sprawdzane w czasie kompilacji.

Jest idealny do testowania starszego kodu. Używaj go ostrożnie. ;)

https://github.com/stephanenicolas/boundbox


1
Właśnie wypróbowałem, BoundBox to proste, eleganckie i rozwiązanie!
foras

9

Najpierw odpowiem na pytanie: Dlaczego twoi prywatni członkowie potrzebują izolowanych testów? Czy są one tak złożone, zapewniając tak skomplikowane zachowania, że ​​wymagają testów poza powierzchnią publiczną? To testy jednostkowe, a nie „liniowe”. Nie przejmuj się małymi rzeczami.

Jeśli są tak duże, wystarczająco duże, że każdy z tych prywatnych członków jest „jednostką” o dużej złożoności - rozważ refaktoryzację takich prywatnych członków z tej klasy.

Jeśli refaktoryzacja jest nieodpowiednia lub niewykonalna, czy możesz użyć wzorca strategii, aby zastąpić dostęp do tych prywatnych funkcji / klas członków podczas testowania jednostkowego? W teście jednostkowym strategia zapewniłaby dodatkowe sprawdzanie poprawności, ale w kompilacjach wersji byłaby to prosta transmisja.


3
Ponieważ często konkretny fragment kodu z metody publicznej jest przekształcany w wewnętrzną metodę prywatną i jest tak naprawdę kluczowym elementem logiki, który mógł być błędny. Chcesz to przetestować niezależnie od metody publicznej
oxbow_lakes

Nawet najkrótszy kod czasami bez testu jednostkowego jest nieprawidłowy. Po prostu spróbuj obliczyć różnicę między 2 kątami geograficznymi. 4 linie kodu i większość nie zrobi tego poprawnie przy pierwszej próbie. Takie metody wymagają testu jednostkowego, ponieważ stanowią podstawę zaufanego kodu. (Odpowiedz na taki przydatny kod może być również publiczny; mniej przydatny chroniony
AlexWien

8

Niedawno miałem ten problem i napisałem małe narzędzie o nazwie Picklock , które pozwala uniknąć problemów związanych z jawnym użyciem interfejsu API refleksji Java, dwa przykłady:

Metody wywoływania, np. private void method(String s)- przez odbicie Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Metody private void method(String s)wywoływania , np. - przez Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Ustawianie pól, np. private BigInteger amount;- przez odbicie Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Ustawianie pól, np. private BigInteger amount;- przez Picklocka

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

7

PowerMockito jest do tego stworzony. Użyj zależności maven

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

To możesz zrobić

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
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.