w tym artykule porównano cztery różne podejścia do testowania prywatnych metod w klasach Java.
moim pierwszym użyciem JUnit było zbudowanie zestawu testów zgodności dla interfejsu API ServiceUI . Celem zestawu testów zgodności jest pomoc w upewnieniu się, że alternatywne implementacje tego samego API są zgodne ze specyfikacją API. Ponieważ specyfikacja API definiuje tylko publiczny interfejs API, a nie jego implementację, test zgodności wykonuje tylko publiczny interfejs. Innymi słowy, test zgodności jest testem „czarnej skrzynki”. Traktuje testowane API jako czarną skrzynkę, której zewnętrzny interfejs jest widoczny, ale której wewnętrzna implementacja nie jest możliwa. Dlatego test zgodności API Java wymaga dostępu tylko do publicznych członków testowanych pakietów i klas. Nie ma potrzeby uzyskiwania dostępu do członków na poziomie pakietów, chronionych lub prywatnych.
kiedy później zastosowałem JUnit do zadania pisania rzeczywistych testów jednostkowych, w przeciwieństwie do testów zgodności, chciałem napisać testy white box—testy, które wykorzystują wiedzę o wewnętrznej implementacji pakietów i klas testowanych. Podczas gdy chciałem testować tylko publiczne metody w moich testach zgodności, chciałem napisać testy jednostkowe dla dostępu do pakietów i czasami prywatne metody, a także publiczne metody.
Daniel Steinberg pokazał mi powszechną technikę JUnit polegającą na używaniu równoległych drzew kodu źródłowego, co pozwoliło mi umieścić klasy testowe w tym samym pakiecie co klasy testowane, ale trzymać je w innym katalogu. Zapewniło to czyste oddzielenie kodu testowego i produkcyjnego. Umieszczając oba drzewa źródłowe w ścieżce klas, moje klasy testowe mogły uzyskać dostęp do metod na poziomie pakietu i klas w testowanym pakiecie. To jednak pozostawiło mnie jeszcze z problemem testowania prywatnych metod.
kiedy zapytałem Daniela o testowanie prywatnych metod, delikatnie zasugerował, żebym testował prywatne metody pośrednio, testując dostęp do pakietów i publiczne metody, które wywołują prywatne. Ta odpowiedź nie do końca mnie zadowoliła, bo od czasu do czasu naprawdę odczuwałem potrzebę bezpośredniego przetestowania prywatnej metody. Moim początkowym rozwiązaniem było po prostu udostępnienie takich prywatnych metod pakietowych, co pozwoliło mi przetestować je bezpośrednio z Junitem z klas testowych w tym samym pakiecie w równoległym drzewie źródłowym. To działało dobrze, ale poczułem się trochę brudny. Chociaż ogólnie odkryłem, że myślenie o tym, jak zaprojektować interfejsy, aby można je było łatwo przetestować, pomogło mi zaprojektować lepsze interfejsy, w tym przypadku czułem, że robię projekt nieco gorzej, aby był testowalny.
kiedy później uczestniczyłem w tworzeniu tego, co Frank Sommers, Matt Gerrans i ostatecznie wydałem jako Artima SuiteRunner , przysięgłem, że sprawię, że testowanie prywatnych metod będzie łatwiejsze w SuiteRunner niż w JUnit. Ale po zbadaniu różnych podejść do testowania prywatnych metod, postanowiłem nie robić nic specjalnego w SuiteRunner, aby wspierać testowanie prywatnych metod. Niezależnie od tego, czy używasz JUnit, czy SuiteRunner, masz te same cztery podstawowe podejścia do testowania prywatnych metod:
- nie testuj prywatnych metod.
- podaj dostęp do pakietu metod.
- użyj zagnieżdżonej klasy testowej.
- użyj odbicia.
w tym artykule omówię te cztery podejścia do testowania prywatnych metod w Javie. Przyjrzę się zaletom i wadom każdego z nich i spróbuję rzucić trochę światła na to, kiedy warto korzystać z każdego podejścia.
jak wspomniałem we wstępie, po raz pierwszy usłyszałem od Daniela Steinberga radę, aby stłumić moje okazjonalne żądze testowania prywatnych metod. Ale Daniel nie jest jedynym źródłem tej Rady, z którą się spotkałem. Wydaje się, że jest to powszechne podejście w społeczności Java. Na przykład, JUnit FAQ stwierdza:
testowanie prywatnych metod może wskazywać, że metody te powinny zostać przeniesione do innej klasy w celu promowania możliwości ponownego użycia.
Charles Miller wyraził podobny punkt widzenia na swoim blogu:
jeśli masz dokładny zestaw testów dla odsłoniętego (nieprywatnego) interfejsu klasy, testy te powinny ze swojej natury zweryfikować, czy jakakolwiek prywatna metoda w klasie również działa. Jeśli tak nie jest, lub jeśli masz prywatną metodę tak złożoną, że trzeba ją przetestować poza kontekstem publicznych rozmówców, uznałbym to za zapach kodu.
a Dave Thomas i Andy Hunt w swojej książce Pragmatic Unit Testing piszą:
ogólnie rzecz biorąc, nie chcesz łamać żadnej enkapsulacji ze względu na testowanie (lub jak mawiała mama: „nie ujawniaj swoich prywatnych rzeczy!”). W większości przypadków powinieneś być w stanie przetestować klasę, korzystając z jej publicznych metod. Jeśli istnieje znacząca funkcjonalność ukryta za prywatnym lub chronionym dostępem, może to być znak ostrzegawczy, że istnieje inna klasa, która stara się wydostać.
wierzę w te wszystkie rady. W większości przypadków metody prywatne mogą być najskuteczniej testowane za pomocą podejścia 1, pośrednio poprzez testowanie metod na poziomie pakietów, chronionych i publicznych, które je wywołują. Ale nieuchronnie, niektórzy ludzie w pewnych sytuacjach poczują, że bezpośrednie testowanie prywatnej metody jest właściwą rzeczą do zrobienia.
w moim przypadku mam tendencję do tworzenia wielu prywatnych metod użyteczności. Te narzędzia często nie robią nic z danymi instancji, po prostu działają na przekazanych parametrach i zwracają wynik. Tworzę takie metody, aby metoda wywołania była łatwiejsza do zrozumienia. Jest to sposób na zarządzanie złożonością implementacji klasy. Teraz, jeśli wyodrębnię metodę prywatną z metody, która już działa i ma dobry zasięg testu jednostkowego, to te istniejące testy jednostkowe prawdopodobnie wystarczą. Nie muszę pisać więcej testów jednostkowych tylko dla metody prywatnej. Ale jeśli chcę napisać prywatną metodę przed jej wywołaniem i chcę napisać testy jednostkowe przed napisaniem prywatnej metody, wracam do bezpośredniego testowania prywatnej metody. W przypadku prywatnych metod użyteczności, nie czuję potrzeby bezpośredniego testowania tych metod, jak to ujął JUnit FAQ, ” wskazaniem, że metody te powinny zostać przeniesione do innej klasy w celu promowania możliwości ponownego użycia.”Metody te są tak naprawdę potrzebne tylko w klasie, w której przebywają, a w rzeczywistości są często wywoływane tylko jedną inną metodą.
innym powodem, dla którego czasami odczuwam potrzebę bezpośredniego testowania prywatnych metod, jest to, że mam tendencję do myślenia o testowaniu jednostkowym jako pomaganiu mi osiągnąć solidny system, budując go z solidnych części. Każda część jest „jednostką”, dla której mogę napisać ” testy jednostkowe.”Testy jednostkowe pomagają mi upewnić się, że każda jednostka działa poprawnie, co z kolei pomaga mi zbudować system, który działa poprawnie jako całość. Podstawową jednostką, którą myślę pod względem programowania w Javie, jest klasa. Buduję systemy z klas, a testy jednostkowe dają mi pewność, że moje klasy są solidne. Ale do pewnego stopnia czuję to samo w odniesieniu do prywatnych metod, z których komponuję Pakiety-dostęp, chronione i publiczne metody. Te prywatne metody to jednostki, które można testować indywidualnie. Takie testy jednostkowe dają mi pewność, że prywatne metody działają poprawnie, co pomaga mi zbudować solidne metody dostępu do pakietów, chronione i publiczne.
jak wspomniałem we wstępie, udostępnienie metod pakietowych było moim pierwszym podejściem do testowania prywatnych metod za pomocą JUnit. Takie podejście działa dobrze, ale wiąże się z niewielkim kosztem. Kiedy widzę specyfikator dostępu prywatnego w metodzie, mówi mi coś, co lubię wiedzieć-że jest to część implementacji klasy. Wiem, że mogę zignorować metodę, jeśli próbuję tylko użyć klasy z innej klasy w pakiecie. Mógłbym dowiedzieć się tego o metodzie dostępu do pakietów, przyglądając się dokładniej nazwie, dokumentacji i kodzie tej metody, ale słowo prywatne komunikuje się o wiele wydajniej. Co więcej, główny problem, jaki mam z tym podejściem, ma charakter filozoficzny. Chociaż nie mam nic przeciwko „łamaniu enkapsulacji w celu testowania”, jak to Ujęliby Dave i Andy, po prostu nie czuję się dobrze z łamaniem enkapsulacji w sposób, który zmienia API na poziomie pakietów. Innymi słowy, chociaż jestem entuzjastycznie nastawiony do testowania niepublicznych metod klas, tj. tworzenia testów jednostkowych „white-box”, wolałbym, aby API testowanych klas, w tym API na poziomie pakietów, nie było zmieniane, aby ułatwić te testy.
podejście 3: Użyj zagnieżdżonej klasy testowej
trzecim podejściem do testowania prywatnych metod jest zagnieżdżenie klasy testowej statycznej wewnątrz testowanej klasy produkcyjnej. Biorąc pod uwagę, że zagnieżdżona klasa ma dostęp do prywatnych członków swojej klasy, byłaby w stanie wywołać metody prywatne bezpośrednio. Sama klasa statyczna może być dostępem do pakietu, umożliwiając jej załadowanie w ramach testu white box.
minusem tego podejścia jest to, że jeśli nie chcesz, aby zagnieżdżona Klasa testowa była dostępna w Twoim pliku JAR deployment, musisz wykonać trochę dodatkowej pracy, aby ją wyodrębnić. Co więcej, niektórzy ludzie mogą nie lubić mieszania kodu testowego w tym samym pliku Co kod produkcyjny, chociaż inni mogą preferować takie podejście.
podejście 4: Użyj refleksji
czwarte podejście do testowania metod prywatnych zasugerował mi Vladimir R. Bossicard, który napisał JUnit Addons . Pewnego dnia podczas lunchu, Vladimir oświecił mnie, że java.lang.reflect
API zawiera metody, które pozwoliły kodowi klienta obejść mechanizm ochrony dostępu maszyny wirtualnej Java. Powiedział mi również, że jego projekt JUnit Addons zawierał klasę junitx.util.PrivateAccessor
, która pomagała używać API reflection do tego właśnie celu: pisania testów jednostkowych , które manipulują prywatnymi członkami testowanych klas. JUnit FAQ wskazuje na podobną klasę o nazwie PrivilegedAccessor
, napisaną przez Charliego Hubbarda i Prashanta Dhotke.
jedną z zalet stosowania podejścia refleksyjnego do testowania prywatnych metod jest to, że zapewnia czyste oddzielenie kodu testowego i kodu produkcyjnego. Testy nie muszą być zagnieżdżane wewnątrz testowanej klasy, jak w podejściu 3. Zamiast tego można je umieszczać obok innych testów, które korzystają z metod klasy na poziomie pakietu i publicznych. Ponadto nie musisz zmieniać API testowanej klasy. W przeciwieństwie do podejścia 2, metody prywatne mogą pozostać prywatne. W przeciwieństwie do podejścia 3, nie musisz dodawać żadnych dodatkowych zagnieżdżonych klas na poziomie dostępu do pakietu. Główną wadą tego podejścia jest to, że kod testowy jest znacznie bardziej gadatliwy, ponieważ używa API reflection. Ponadto IDE refaktoryzacyjne, takie jak Eclipse i IntelliJ, zwykle nie są tak biegłe w zmianie nazw metod, gdzie są one określane jako String
s przekazywane do metod API refaktoryzacji. Jeśli więc zmienisz nazwę metody prywatnej za pomocą refaktoryzacji IDE, być może będziesz musiał ręcznie wprowadzić pewne zmiany w kodzie testowym.
przykład
aby podać jeden przykład prywatnej metody, która moim zdaniem zasługuje na bezpośrednie testowanie jednostkowe, wyodrębniłem pewną funkcjonalność z metodymain
klasyorg.suiterunner.Runner
.Runner.main
parsuje argumenty wiersza poleceń i uruchamia zestaw testów, opcjonalnie uruchamiając GUI. Metoda, którą rozpakowałem,parseArgsIntoLists
, wykonuje część pracy parsowania argumentów wiersza poleceń do aplikacji SuiteRunner. Teraz, aby przetestować publiczną metodę, która wywołuje tę prywatną metodę, musiałbym przetestowaćmain
. Głównym oczywiście jest cała aplikacja, co sprawia, że metoda jest raczej trudna do przetestowania. W rzeczywistości nie mam żadnego testu namain
.
w tym momencie możesz się zastanawiać, czy gdybym najpierw pisał testy w stylu test-driven development, jak mogłem w ogóle napisać parsujący kod, który nie miał testów jednostkowych? Głównym powodem jest to, że moja infekcja testowa przebiegała etapami. Złapałem grypę testową na długo przed tym, jak usłyszałem o JUnit lub przeczytałem Test Infected . Kiedy na przykład tworzyłem Aplikacje Windows w C++, pisałem trochę kodu, aby przetestować nowo zaimplementowaną metodę, a następnie wykonywałem ten kod i obserwowałem go, przechodząc przez testowaną metodę za pomocą debuggera. Tego rodzaju testy jednostkowe pomogły mi osiągnąć solidność, ale same testy nie sprawdzały właściwego zachowania. Sam sprawdziłem poprawne zachowanie, obserwując za pomocą debuggera. Testy nie były zautomatyzowane, dlatego nie zapisałem ich, aby można je było uruchomić ponownie później. Kiedy czytałem Test Infected, od razu zobaczyłem wartość automatyzacji testów i trzymania ich w pobliżu jako rodzaj testu regresyjnego po refaktoryzacji, ale przez długi czas nie miało dla mnie sensu pisanie testów jako pierwszego. Chciałem napisać testy po zaimplementowaniu funkcjonalności, ponieważ wtedy uruchomiłem testy z debuggerem. Drugim powodem, dla którego nie pisałem testów najpierw podczas opracowywania dużej części SuiteRunner, jest to, że chciałem napisać testy SuiteRunner z samym Suiterunnerem, aby zjeść własną karmę dla psów. Dopóki podstawowe API Suiterunnera nie zostało ustalone, nie miałem zestawu narzędzi do testowania, którego chciałem użyć do pisania testów.
od tego czasu jednak testujący wirus nabrał silniejszego wpływu na mnie, a teraz wolę pisać najpierw testy jednostkowe przez większość czasu. Wolę najpierw pisać testy nie tak bardzo, ponieważ uważam, że kończę z czystszymi projektami, które zwykle są promowane jako główna zaleta rozwoju opartego na testach. Wolę raczej najpierw pisać testy, ponieważ często odkrywam, że jeśli zanurzę się w kodzie pod presją, z zamiarem, że napiszę test później, test w rzeczywistości nigdy nie zostanie napisany. Sam SuiteRunner ma bardzo niewiele testów jednostkowych w tym momencie z tego właśnie powodu. Oto metoda parseArgsIntoLists
:
private static void parseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) { if (args == null || runpathList == null || reportersList == null || suitesList == null) { throw new NullPointerException(); } for (int i = 0; i < args.length; i++) { if (args.startsWith("-p")) { runpathList.add(args); runpathList.add(args); ++i; } else if (args.startsWith("-g")) { reportersList.add(args); } else if (args.startsWith("-o")) { reportersList.add(args); } else if (args.startsWith("-e")) { reportersList.add(args); } else if (args.startsWith("-f")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-r")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-s")) { suitesList.add(args); do { ++i; suitesList.add(args); } while (i + 1 < args.length); } else { throw new IllegalArgumentException("Unrecognized argument: " + args); } } }
wiersz poleceń dla SuiteRunner zawiera trzy rodzaje informacji używanych przez SuiteRunner do uruchamiania testów: runpath, reporters i suites. Metoda parseArgsIntoLists
po prostu przechodzi przez argumenty przekazane jako tablica String
s i umieszcza każdy argument na jednej z list, runpathList
, reportersList
i suitesList
.
zanim napiszę test dla tej prywatnej metody, chciałbym zapytać, czy moja chęć napisania tego testu jednostkowego reprezentuje zapach kodu, jak to Charles Miller umieścił na swoim blogu? Czy oznacza to, że parseArgsIntoLists
należy przenieść do innej klasy, aby promować możliwość ponownego użycia,jak sugeruje JUnit FAQ? Czy Dave i Andy powiedzieliby, że to znak ostrzegawczy, że jest tam inna klasa, która próbuje się wydostać? Może. Mógłbym stworzyć klasę ArgumentsParser
, która posiada tylko kilka statycznych metod, które wykonują pracę parsowania. Zarówno Klasa ArgumentsParser
, jak i metody, które zawiera, mogą być dostępem do pakietów, co ułatwiłoby ich testowanie. Ale dla mnie to nie jest w porządku. Metody te są wywoływane tylko przez Runner.main
. Najwyraźniej czują się dla mnie prywatnymi metodami. Jedynym powodem, dla którego przeniosłbym je do klasy ArgumentsParser
, jest możliwość ich przetestowania. W rzeczywistości używałbym podejścia numer 2: Udostępnij pakiet metod prywatnych.
zamiast tego, w tym przykładzie postanowiłem wziąć podejście numer 4 i użyć refleksji. Spojrzałem zarówno na Vladimira Bossicarda junitx.utils.PrivateAccessor
, jak i Charliego Hubbarda i Prashanta Dhotke PrivilegedAccessor
, ale stwierdziłem, że żaden z nich nie pomógł mi tak, jak chciałem. Po pierwsze, obie te klasy mają możliwość testowania pól, aby upewnić się, że są poprawnie ustawione. Jak dotąd nigdy nie czułem potrzeby bezpośredniego dostępu do prywatnych pól z testów jednostkowych. Chcę tylko móc testować prywatne metody użytkowe. Głównym problemem, jaki miałem z tymi dwiema klasami, jest jednak to, jak radziły sobie z wyjątkami, które mogą zostać rzucone podczas próby wywołania metody prywatnej poprzez refleksję. Każda klasa ma jedną lub więcej metod, których zadaniem jest wywołanie metody z odbiciem. PrivilegedAccessor
’ s dwie metody invokeMethod
przekazuje dowolny wyjątek z powrotem do wywołującego, w tym trzy sprawdzone wyjątki zadeklarowane w klauzuli throws: NoSuchMethodException
, IllegalAccessException
i InvocationTargetException
. Natomiast dwie metody PrivateAccessor
wychwytują InvocationTargetException
, a następnie wyodrębniają i wyrzucają docelowy wyjątek, rzeczywisty wyjątek wyrzucony przez wywołaną metodę. Następnie łapie każdy inny wyjątek i wyrzuca NoSuchMethodException
. Nie podobało mi się, że wywołujący PrivilegedAccessor.invokeMethod
zawsze musi obsługiwać trzy sprawdzone wyjątki, ponieważ pomyślałem, że ogólnym sposobem obsługi każdego wyjątku będzie pozwolić testowi na niepowodzenie. Obawiałem się również, że PrivateAccessor.invoke
wyrzucał potencjalnie przydatne informacje o śledzeniu stosu w swojej polityce obsługi wyjątków. To, czego naprawdę chciałem, to metoda, która próbowała wywołać prywatną metodę z odbiciem, która zawijała każdy wyrzucony wyjątek poza InvocationTargetException
w niezaznaczoną TestFailedException
. W większości przypadków ten wyjątek spowodowałby niepowodzenie testu. W testach, które oczekiwały zgłoszenia wyjątku, wyjątek zawarty w InvocationTargetException
mógł zostać wyodrębniony i przetestowany pod kątem poprawności.
dlatego napisałem invokeStaticMethod
. Wywołanie setAccessible(true)
umożliwia wywołanie metody prywatnej spoza klasy. Odpowiednia implementacja invokeStaticMethod
do użytku z JUnit rzuciłaby AssertionFailedError
zamiast TestFailedException
. Oto kod:
private static void invokeStaticMethod(Class targetClass, String methodName, Class argClasses, Object argObjects) throws InvocationTargetException { try { Method method = targetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); method.invoke(null, argObjects); } catch (NoSuchMethodException e) { // Should happen only rarely, because most times the // specified method should exist. If it does happen, just let // the test fail so the programmer can fix the problem. throw new TestFailedException(e); } catch (SecurityException e) { // Should happen only rarely, because the setAccessible(true) // should be allowed in when running unit tests. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } catch (IllegalAccessException e) { // Should never happen, because setting accessible flag to // true. If setting accessible fails, should throw a security // exception at that point and never get to the invoke. But // just in case, wrap it in a TestFailedException and let a // human figure it out. throw new TestFailedException(e); } catch (IllegalArgumentException e) { // Should happen only rarely, because usually the right // number and types of arguments will be passed. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } }
następnie stworzyłem wygodną metodę, która wywołuje konkretną prywatną metodę, którą chciałem przetestować:
private static void invokeParseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) throws InvocationTargetException { // Purposely pass null values to the method, to make sure it throws // NullPointerException Class argClasses = {String.class, List.class, List.class, List.class }; Object argObjects = {args, runpathList, reportersList, suitesList }; invokeStaticMethod(Runner.class, "parseArgsIntoLists", argClasses, argObjects); }
w końcu mógłbym napisać testy z metodą prywatną bez zbytniego bałaganu, jak to:
public void testParseArgsIntoLists() throws InvocationTargetException { String args = new String; List runpathList = new ArrayList(); List reportersList = new ArrayList(); List suitesList = new ArrayList(); try { invokeParseArgsIntoLists(null, runpathList, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, null, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, null, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, reportersList, null); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } args = new String; args = "-p"; args = "\"mydir\""; args = "-g"; args = "-f"; args = "test.out"; args = "-s"; args = "MySuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 3); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(suitesList.size() == 2); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); args = new String; args = "-p"; args = "\"mydir\""; args = "-e"; args = "-o"; args = "-r"; args = "MyCustomReporter"; args = "-s"; args = "MySuite"; args = "MyOtherSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 4); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(reportersList.get(3).equals(args)); verify(suitesList.size() == 3); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); args = new String; args = "-p"; args = "\"serviceuitest-1.1beta4.jar myjini http://myhost:9998/myfile.jar\""; args = "-g"; args = "-s"; args = "MySuite"; args = "MySecondSuite"; args = "MyThirdSuite"; args = "MyFourthSuite"; args = "MyFifthSuite"; args = "MySixthSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 1); verify(reportersList.get(0).equals(args)); verify(suitesList.size() == 7); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); verify(suitesList.get(3).equals(args)); verify(suitesList.get(4).equals(args)); verify(suitesList.get(5).equals(args)); verify(suitesList.get(6).equals(args)); }
wniosek
podejście 1, testowanie metod prywatnych pośrednio poprzez testowanie metod na poziomie pakietów, chronionych i publicznych, które je wywołują, często będzie najlepszym podejściem. W przypadkach, w których naprawdę chcesz testować prywatne metody bezpośrednio, użycie refleksji do testowania prywatnych metod, choć dość uciążliwe, zapewnia najczystsze oddzielenie kodu testowego od kodu produkcyjnego i najmniejszy wpływ na kod produkcyjny. Jednakże, jeśli nie masz nic przeciwko wprowadzeniu tych konkretnych prywatnych metod, które chcesz przetestować dostęp do pakietów, możesz użyć podejścia 2. Jeśli nie masz nic przeciwko umieszczeniu zagnieżdżonej klasy testowej w testowanej klasie produkcyjnej, metoda approach 3 pozwoli Ci przynajmniej zachować prywatność metod.
nie ma idealnej odpowiedzi. Ale jeśli przyjmiesz podejście 4, ostatecznie otrzymasz kilka metod, takich jak invokeStaticMethod
, które możesz ponownie wykorzystać. Gdy napiszesz wygodną metodę, taką jak invokeParseArgsIntoLists
, dla metody prywatnej, możesz bez większych trudności napisać testy przeciwko metodzie prywatnej.
1. Interfejs API ServiceUI definiuje standardowy sposób dołączania interfejsów użytkownika do usług Jini:
http://www.artima.com/jini/serviceui/index.html
2. Daniel Steinberg jest obecnie redaktorem naczelnym Java.NET:
http://www.java.net/
3. Artima SuiteRunner to darmowy zestaw narzędzi do testowania open source i JUnit runner:
http://www.artima.com/suiterunner/index.html
4.JUnit FAQ pytanie o testowanie prywatnych metod:
http://junit.sourceforge.net/doc/faq/faq.htm#tests_10
5. Testowanie prywatnych metod (Don ’ t Do it), a weblog post by Charles Miller:
http://fishbowl.pastiche.org/2003/03/28/testing_private_methods_dont_do_it
6. Andy Hunt i Dave Thomas są autorami Pragmatic Unit Testing, który jest dostępny w Pragmatic Store.
7. JUnit Addons to zbiór klas pomocniczych dla JUnit stworzony przez Vladimara R. Bossicarda:
http://sourceforge.net/projects/junit-addons
PrivateAccessor
jest klasą z JUnit Addons, która ułatwia testowanie członków prywatnych:
http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html
9.PrivilegedAccessor class, której możesz użyć, aby uzyskać dostęp do członków prywatnych:
http://groups.yahoo.com/group/junit/files/src/PrivilegedAccessor.java
10. Test Driven Development by Example, by Kent Beck, opisuje technikę test-first:
http://www.amazon.com/exec/obidos/ASIN/0321146530/
11. Test Infected, autorstwa Kenta Becka i Ericha Gamma, wprowadził JUnit na świat:
http://junit.sourceforge.net/doc/testinfected/testing.htm
Unit Testing Private Methods, a weblog post about NUnit by Ted Graham:
http://weblogs.asp.net/tgraham/archive/2003/12/31/46984.aspx
podważanie ochrony dostępu Java do testów jednostkowych, O ’ Reilly OnJava.com artykuł Ross Burton:
http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html
Klasa RunnerSuite
, z której pochodzą fragmenty kodu w tym artykule, pojawia się w całości tutaj:
http://www.artima.com/suiterunner/privateExample.html
dlaczego Zrefakturowaliśmy JUnit
http://www.artima.com/suiterunner/why.html
Artima SuiteRunner Tutorial, budowanie zgodności i testy jednostkowe z Artima SuiteRunner:
http://www.artima.com/suiterunner/tutorial.html
pierwsze kroki z Artima SuiteRunner, jak uruchomić prosty przykład zawarty w dystrybucji:
http://www.artima.com/suiterunner/start.html
Uruchamianie testów JUnit z Artima SuiteRunner, jak używać Artima SuiteRunner jako JUnit runner do uruchamiania istniejących pakietów testowych JUnit:
http://www.artima.com/suiterunner/junit.html
Strona główna Artima SuiteRunner:
http://www.artima.com/suiterunner/index.html
Strona pobierania Artima SuiteRunner (musisz się zalogować Artima.com aby pobrać wydanie):
http://www.artima.com/suiterunner/download.jsp
Forum SuiteRunner:
http://www.artima.com/forums/forum.jsp?forum=61
gadaj!
masz opinię? Czytelnicy napisali już 26 komentarzy na temat tego artykułu. Dlaczego nie dodać swojego?
o autorze
Bill Venners jest prezesem Artima Software, Inc. i redaktor naczelny Artima.com. jest autorem książki Inside The Java Virtual Machine, a programmer-oriented survey of the Java platform ’ s architecture and internals. Jego popularne felietony w JavaWorld magazine dotyczyły Java internals, object-oriented design i Jini. Bill jest aktywny w społeczności Jini od momentu jej powstania. Kierował projektem ServiceUI społeczności Jini, który wyprodukował ServiceUI API. ServiceUI stał się de facto standardowym sposobem kojarzenia interfejsów użytkownika z usługami Jini i był pierwszym standardem społeczności Jini zatwierdzonym przez proces decyzyjny Jini. Bill służy również jako wybrany członek wstępnego Komitetu Nadzoru Technicznego Wspólnoty Jini (TOC), a w tej roli pomógł zdefiniować proces zarządzania dla wspólnoty. Obecnie większość swojej energii poświęca na budowanie Artima.com w coraz bardziej użyteczny zasób dla programistów.