Jak przeprowadzasz testy jednostkowe metod prywatnych?

184

Pracuję nad projektem Java. Jestem nowy w testowaniu jednostkowym. Jaki jest najlepszy sposób na testowanie jednostkowe metod prywatnych w klasach Java?

Vinoth Kumar CM
źródło
1
Sprawdź to pytanie na StackOverflow. Wymieniono i omówiono kilka technik. Jaki jest najlepszy sposób testowania jednostkowego metod prywatnych?
Chiron
34
Zawsze uważałem, że prywatne metody nie wymagają testowania, ponieważ powinieneś testować to, co jest dostępne. Metoda publiczna. Jeśli nie możesz złamać metody publicznej, czy to naprawdę ma znaczenie, co robią metody prywatne?
rig
1
Należy przetestować zarówno metody publiczne, jak i prywatne. W związku z tym kierowca testowy musi zasadniczo znajdować się w testowanej klasie. jak to .
nadmierna wymiana
2
@Rig - +1 - powinieneś być w stanie wywołać wszystkie wymagane zachowania metody prywatnej z metod publicznych - jeśli nie możesz, nigdy nie można wywołać tej funkcji, więc nie ma sensu jej testować.
James Anderson
Użyj @Jailbreakze struktury Manifold , aby uzyskać bezpośredni dostęp do metod prywatnych. W ten sposób kod testowy pozostaje bezpieczny i czytelny. Przede wszystkim brak kompromisów projektowych, brak metod i pól prześwietlania na potrzeby testów.
Scott

Odpowiedzi:

239

Zasadniczo nie testuje się bezpośrednio metod prywatnych. Ponieważ są one prywatne, rozważ je jako szczegół implementacji. Nikt nigdy nie zadzwoni do jednego z nich i nie spodziewa się, że zadziała w określony sposób.

Zamiast tego powinieneś przetestować swój publiczny interfejs. Jeśli metody wywołujące metody prywatne działają zgodnie z oczekiwaniami, wówczas przyjmujemy przez rozszerzenie, że metody prywatne działają poprawnie.

Adam Lear
źródło
39
+1 bazillion. A jeśli prywatna metoda nigdy nie jest wywoływana, nie testuj jej jednostkowo, usuń ją!
Steven A. Lowe,
246
Nie zgadzam się. Czasami prywatna metoda jest tylko szczegółem implementacji, ale wciąż jest wystarczająco złożona, aby zagwarantować testowanie, aby upewnić się, że działa poprawnie. Interfejs publiczny może oferować zbyt wysoki poziom abstrakcji, aby napisać test ukierunkowany bezpośrednio na ten konkretny algorytm. Nie zawsze jest możliwe podzielenie go na osobną klasę, ponieważ ze względu na wspólne dane. W takim przypadku powiedziałbym, że można przetestować metodę prywatną.
quant_dev,
10
Oczywiście to usuń. Jedynym możliwym powodem, aby nie usuwać funkcji, której nie używasz, jest to, że uważasz, że możesz jej potrzebować w przyszłości, a nawet wtedy powinieneś ją usunąć, ale zanotuj tę funkcję w dziennikach zatwierdzeń lub przynajmniej skomentuj ją, aby ludzie wiedzą, że to dodatkowe rzeczy, które mogą zignorować.
jhocking
38
@quant_dev: Czasami klasa wymaga przekształcenia w kilka innych klas. Udostępnione dane można również przenieść do osobnej klasy. Powiedzmy, „klasa” kontekstowa. Następnie dwie nowe klasy mogą odwoływać się do klasy kontekstu w celu udostępnienia danych. Może to wydawać się nieuzasadnione, ale jeśli twoje prywatne metody są wystarczająco złożone, aby wymagać indywidualnych testów, to zapach kodu wskazuje, że wykres obiektu musi stać się nieco bardziej szczegółowy.
Phil
43
Ta odpowiedź NIE odpowiada na pytanie. Pytanie brzmi: w jaki sposób należy testować prywatne metody, a nie to , czy należy je przetestować. To, czy metoda prywatna powinna być testowana jednostkowo, jest ciekawym pytaniem i wartym debaty, ale nie tutaj . Odpowiednią odpowiedzią jest dodanie w pytaniu komentarza stwierdzającego, że testowanie metod prywatnych może nie być dobrym pomysłem, oraz link do osobnego pytania, które byłoby głębsze.
TallGuy
118

Ogólnie bym tego unikał. Jeśli Twoja prywatna metoda jest tak złożona, że ​​wymaga oddzielnego testu jednostkowego, często oznacza to, że zasłużyła na swoją klasę. Może to zachęcić Cię do napisania go w sposób, który można ponownie wykorzystać. Następnie powinieneś przetestować nową klasę i wywołać jej publiczny interfejs w starej klasie.

Z drugiej strony czasami dzielenie szczegółów implementacji na osobne klasy prowadzi do klas o złożonych interfejsach, dużej ilości danych przesyłanych między starą i nową klasą lub do projektu, który może wyglądać dobrze z punktu widzenia OOP, ale nie dopasować intuicje pochodzące z dziedziny problemowej (np. podzielenie modelu wyceny na dwie części, aby uniknąć testowania metod prywatnych, nie jest zbyt intuicyjne i może prowadzić do problemów w późniejszym czasie podczas utrzymywania / rozszerzania kodu). Nie chcesz mieć „klas bliźniaków”, które zawsze są zmieniane razem.

W obliczu wyboru między enkapsulacją a testowalnością wolałbym drugi. Ważniejsze jest, aby mieć poprawny kod (tj. Wygenerować poprawny wynik) niż ładny projekt OOP, który nie działa poprawnie, ponieważ nie został odpowiednio przetestowany. W Javie możesz po prostu dać dostęp do metody „domyślnej” i umieścić test jednostkowy w tym samym pakiecie. Testy jednostkowe są po prostu częścią pakietu, który opracowujesz, i dobrze jest mieć zależność między testami a testowanym kodem. Oznacza to, że po zmianie implementacji może być konieczna zmiana testów, ale to jest OK - każda zmiana implementacji wymaga ponownego przetestowania kodu, a jeśli testy muszą zostać zmodyfikowane, aby to zrobić, wystarczy to zrobić to.

Ogólnie klasa może oferować więcej niż jeden interfejs. Istnieje interfejs dla użytkowników i interfejs dla opiekunów. Drugi może ujawnić więcej, aby zapewnić odpowiednie przetestowanie kodu. Nie musi to być test jednostkowy metodą prywatną - może to być na przykład logowanie. Rejestrowanie również „przerywa enkapsulację”, ale wciąż to robimy, ponieważ jest to bardzo przydatne.

quant_dev
źródło
9
+1 Interesujący punkt. Ostatecznie chodzi o łatwość utrzymania, a nie przestrzeganie „zasad” dobrego OOP.
Phil
9
+1 Całkowicie zgadzam się z testowalnością nad enkapsulacją.
Richard
31

Testowanie metod prywatnych zależałoby od ich złożoności; niektóre prywatne metody jednowierszowe nie wymagałyby dodatkowego wysiłku związanego z testowaniem (można to również powiedzieć o metodach publicznych), ale niektóre prywatne metody mogą być tak samo złożone jak metody publiczne i trudne do przetestowania przez interfejs publiczny.

Moją preferowaną techniką jest uczynienie prywatnego pakietu metod prywatnym, który pozwoli na dostęp do testu jednostkowego w tym samym pakiecie, ale nadal będzie enkapsulowany z całego innego kodu. Daje to zaletę bezpośredniego testowania logiki metody prywatnej zamiast konieczności polegania na publicznym metodzie testu obejmującej wszystkie części (ewentualnie) złożonej logiki.

Jeśli jest to sparowane z adnotacją @VisibleForTesting w bibliotece Google Guava, wyraźnie zaznaczasz ten pakiet jako metodę prywatną jako widoczną tylko do testowania i jako taki nie powinien być wywoływany przez inne klasy.

Przeciwnicy tej techniki twierdzą, że przerwie to enkapsulację i otworzy prywatne metody kodowania w tym samym pakiecie. Chociaż zgadzam się, że przerywa to enkapsulację i otwiera prywatny kod dla innych klas, argumentuję, że testowanie złożonej logiki jest ważniejsze niż ścisłe enkapsulowanie i nie używanie prywatnych metod pakietu, które są wyraźnie oznaczone jako widoczne do testowania, musi być obowiązkiem programistów używając i zmieniając bazę kodu.

Metoda prywatna przed testowaniem:

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

Pakiet prywatnej metody gotowy do testowania:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Uwaga: Umieszczenie testów w tym samym pakiecie nie jest równoważne z umieszczeniem ich w tym samym folderze fizycznym. Rozdzielanie kodu głównego i kodu testowego na osobne struktury folderów fizycznych jest ogólnie dobrą praktyką, ale ta technika będzie działać, dopóki klasy będą zdefiniowane jak w tym samym pakiecie.

Richard
źródło
14

Jeśli nie możesz używać zewnętrznych interfejsów API lub nie chcesz, możesz nadal używać czystego standardowego interfejsu API JDK, aby uzyskać dostęp do prywatnych metod za pomocą refleksji. Oto przykład

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Sprawdź samouczek Java http://docs.oracle.com/javase/tutorial/reflect/ lub Java API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. HTML, aby uzyskać więcej informacji.

Jak cytował @kij w swojej odpowiedzi, zdarza się, że proste rozwiązanie wykorzystujące odbicie jest naprawdę dobre do przetestowania prywatnej metody.

Gustavo Coelho
źródło
9

Przypadek testowy jednostki oznacza testowanie jednostki kodu. Nie oznacza to testowania interfejsu, ponieważ jeśli testujesz interfejs, nie oznacza to, że testujesz jednostkę kodu. To staje się rodzajem testowania czarnej skrzynki. Ponadto lepiej jest znaleźć problemy na najmniejszym poziomie jednostki niż określić problemy na poziomie interfejsu, a następnie próbować debugować, który element nie działał. Dlatego przypadek testowy powinien być testowany niezależnie od jego zakresu. Poniżej przedstawiono sposób przetestowania metod prywatnych.

Jeśli używasz java, możesz użyć jmockit, który zapewnia Deencapsulation.invoke, aby wywołać dowolną prywatną metodę testowanej klasy. Używa refleksji, aby w końcu ją nazwać, ale zapewnia ładne otoczenie. ( https://code.google.com/p/jmockit/ )

agrawalankur
źródło
Jak to odpowiada na zadane pytanie?
komar
@gnat, pytanie brzmiało: Jaki jest najlepszy sposób na testowanie jednostkowe metod prywatnych w klasach Java? I mój pierwszy punkt odpowiada na pytanie.
agrawalankur
twój pierwszy punkt jedynie reklamuje narzędzie, ale nie wyjaśnia, dlaczego uważasz, że jest ono lepsze niż alternatywy, takie jak np. pośrednie testowanie prywatnych metod, jak to sugerowano i wyjaśniono w najwyższej odpowiedzi
gnat
Firma jmockit przeprowadziła się, a stara oficjalna strona wskazuje na artykuł o „syntetycznym moczu i sztucznym siusiu”. Nawiasem mówiąc, jest to wciąż związane z kpiną, ale nie dotyczy tutaj :) Nowa oficjalna strona to: jmockit.github.io
Guillaume
8

Po pierwsze, jak sugerowali inni autorzy: zastanów się dwa razy, jeśli naprawdę potrzebujesz przetestować metodę prywatną. A jeśli tak, ...

W .NET możesz przekonwertować go na metodę „ Wewnętrzną ” i ustawić pakiet „ InternalVisible ” na projekt testu jednostkowego.

W Javie możesz pisać testy w testowanej klasie, a twoje metody testowe powinny mieć możliwość wywoływania metod prywatnych. Tak naprawdę nie mam dużego doświadczenia z Javą, więc prawdopodobnie nie jest to najlepsza praktyka.

Dzięki.

Budda
źródło
7

Zazwyczaj chronię takie metody. Powiedzmy, że Twoja klasa jest w:

src/main/java/you/Class.java

Możesz utworzyć klasę testową jako:

src/test/java/you/TestClass.java

Teraz masz dostęp do chronionych metod i możesz je testować jednostkowo (JUnit lub TestNG nie ma tak naprawdę znaczenia), ale ukrywasz te metody przed dzwoniącymi, których nie chciałeś.

Zauważ, że oczekuje to drzewa źródłowego w stylu raju.

gyorgyabraham
źródło
3

Jeśli naprawdę potrzebujesz przetestować metodę prywatną, z Javą mam na myśli, możesz użyć fest asserti / lub fest reflect. Wykorzystuje odbicie.

Zaimportuj bibliotekę za pomocą maven (dane wersje nie są chyba ostatnimi) lub zaimportuj ją bezpośrednio do ścieżki klasy:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Na przykład, jeśli masz klasę o nazwie „MyClass” z prywatną metodą o nazwie „myPrivateMethod”, która przyjmuje parametr String jako parametr i aktualizuje jego wartość do „to jest fajne testowanie!”, Możesz wykonać następujący test junit:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Ta biblioteka pozwala również zastąpić dowolne właściwości komponentu bean (bez względu na to, czy są prywatne i nie są zapisywane żadne ustawiacze) przez próbkę, a używanie tego z Mockito lub jakimkolwiek innym frameworkiem jest naprawdę fajne. Jedyne, co musisz wiedzieć w tej chwili (nie wiem, czy będzie lepiej w następnych wersjach), to nazwa pola / metody docelowej, którą chcesz manipulować, i jej podpis.

kij
źródło
2
Chociaż refleksja pozwoli ci przetestować prywatne metody, nie jest dobra do wykrywania ich użycia. Jeśli zmienisz nazwę metody prywatnej, przerwie to test.
Richard
1
Test zakończy się tak, ale z mojego punktu widzenia jest to niewielki problem. Oczywiście w pełni zgadzam się z twoim wyjaśnieniem poniżej dotyczącym widoczności pakietu (z klasami testów w oddzielnych folderach, ale z tymi samymi pakietami), ale czasami nie naprawdę mam wybór. Na przykład, jeśli masz naprawdę krótką misję w przedsiębiorstwie, która nie stosuje „dobrych” metod (klasy testowe nie w tym samym pakiecie itp.), Jeśli nie masz czasu na refaktoryzację istniejącego kodu, nad którym pracujesz / testowanie, to wciąż dobra alternatywa.
kij
2

To, co zwykle robię w języku C #, sprawia, że ​​moje metody są chronione, a nie prywatne. Jest to nieco mniej prywatny modyfikator dostępu, ale ukrywa metodę przed wszystkimi klasami, które nie dziedziczą po testowanej klasie.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Każda klasa, która nie dziedziczy bezpośrednio z classUnderTest, nie ma pojęcia, że ​​methodToTest w ogóle istnieje. W moim kodzie testowym mogę utworzyć specjalną klasę testową, która rozszerza i zapewnia dostęp do tej metody ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Ta klasa istnieje tylko w moim projekcie testowym. Jego jedynym celem jest zapewnienie dostępu do tej jednej metody. Pozwala mi na dostęp do miejsc, których nie ma większość innych klas.

astronauta
źródło
4
protectedjest częścią publicznego interfejsu API i podlega tym samym ograniczeniom (nigdy nie można go zmienić, musi zostać udokumentowany ...). W C # możesz używać internalrazem z InternalsVisibleTo.
CodesInChaos
1

Możesz łatwo przetestować metody prywatne, jeśli umieścisz testy jednostkowe w klasie wewnętrznej w klasie, którą testujesz. Za pomocą TestNG testy jednostkowe muszą być publicznymi statycznymi klasami wewnętrznymi opatrzonymi adnotacją @Test, jak poniżej:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Ponieważ jest to klasa wewnętrzna, można wywołać metodę prywatną.

Moje testy są uruchamiane z maven i automatycznie wyszukuje te przypadki testowe. Jeśli chcesz tylko przetestować jedną klasę, możesz to zrobić

$ mvn test -Dtest=*UserEditorTest

Źródło: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
źródło
4
Sugerujesz włączenie kodu testowego (i dodatkowych klas wewnętrznych) do faktycznego kodu wdrażanego pakietu?
1
Klasa testowa to klasa wewnętrzna klasy, którą chcesz przetestować. Jeśli nie chcesz, aby te wewnętrzne klasy były wdrażane, możesz po prostu usunąć pliki * Test.class ze swojego pakietu.
Roger Keays
1
Nie podoba mi się ta opcja, ale dla wielu jest to prawdopodobnie prawidłowe rozwiązanie, nie warte pochwał.
Bill K
@RogerKeays: Technicznie, kiedy przeprowadzasz wdrożenie, jak usuwasz takie „testowe klasy wewnętrzne”? Proszę nie mówić, że przecinasz je ręcznie.
gyorgyabraham,
Nie usuwam ich. Tak. Wdrażam kod, który nigdy nie jest wykonywany w czasie wykonywania. To naprawdę takie złe.
Roger Keays,