Testy jednostkowe prywatnych metod

Bartosz Borowik
dodane przez: Bartosz Borowik | Marzec 16, 2016

Wydawało mi się, że pytanie, czy pisać testy jednostkowe do prywatnych metod, doczekało się jednoznacznej odpowiedzi już dawno i jest ona powszechnie znana. Tymczasem okazuje się, że nadal można spotkać się z rozbieżnymi opiniami na ten temat.

Zatem, na pytanie, czy powinniśmy testować jednostkowo metody prywatne, należy jasno i krótko odpowiedzieć: TAK!

Jednakże, kiedy ktoś zadaje takie pytanie, to często nie zdaje sobie sprawy, że jest ono bardziej zawiłe, niż się wydaje na pierwszy rzut oka. Przyjrzyjmy się tematowi bliżej.

Istnienie prywatnej metody ma sens tylko wtedy, kiedy wykonuje ona zadanie zlecane jej przez metody publiczne. Może być wywoływana bezpośrednio przez jedną z metod publicznych, albo pośrednio przez łańcuch wywołań innych metod publicznych i prywatnych. Ale zawsze na samym początku pierwsza wywoływana jest metoda publiczna, jako punkt wejścia do funkcjonalności oferowanej przez daną klasę.

Jeśli zatem otestujemy dokładnie i kompletnie funkcjonalność oferowaną przez metody publiczne, to jednocześnie będziemy mieli otestowane metody prywatne, tylko że nie bezpośrednio. Jeśli nasze metody prywatne są małe i proste, to testowanie ich pośrednio przez metody publiczne w zupełności wystarczy. Należy wtedy się tylko upewnić, że zestaw testów do metody publicznej w całości pokrywa też metody prywatne.

Problem pojawia się, kiedy metody prywatne nie są ani małe, ani proste. Kiedy zawierają trochę więcej logiki, jakieś instrukcje warunkowe, pętle, odwołania do innych serwisów. Wtedy testy przeprowadzane pośrednio przez metody publiczne mogą stać się zbyt zawiłe i trudne do zinterpretowania.

Testy zaczynają się robić za bardzo skomplikowane, kiedy w implementacji nawet pojedynczego przypadku testowego potrzebne jest zamockowanie zbyt wielu zależności, przygotowanie zbyt wielu różnego typu danych wejściowych, sprawdzenie wystąpienia (lub nie) wielu efektów ubocznych, zinterpretowania zbyt złożonych danych wyjściowych.

Z kolei interpretacja błędu zgłaszanego przez taki test może być trudna, kiedy nie jesteśmy w stanie bezzwłocznie jednoznacznie określić przyczyny tego błędu. Jest nią publiczna metoda wywołana bezpośrednio w teście? Czy może któraś z metod prywatnych wywołanych pośrednio? I która? Kiedy test sprawdza kilka metod, w tym kilka wywoływanych pośrednio, tracimy jednoznaczny odnośnik z testu do błędnego kodu.

Są to powody, dla których pojawia się pokusa, aby bezpośrednio otestować metodę prywatną (lub kilka metod prywatnych współuczestniczących w jakiejś funkcjonalności), zamiast trudzić się w rozpisywanie przypadków testowych pośrednio przez metody publiczne. Nasze narzędzia testowe pozwalają przecież na zrealizowanie tego przy pomocy kilku tricków.

Takie podejście pozwala nam wprawdzie łudzić się, że dobrze wykonaliśmy zadanie, bo przecież funkcjonalność jest otestowana, ale niestety nie usuwa ono właściwego problemu, który spowodował, że zaistniała w nas pokusa sięgnięcia do skrzynki z trickami umożliwiającymi wywoływanie prywatnych metod.

Problemem nie jest to, że nie mamy dostępu do prywatnych metod, tylko to, że te metody stały się zbyt liczne, lub zbyt złożone i prawdopodobnie zaciemniają obraz testowanej klasy i rozmywają jej odpowiedzialność. A, jak wiemy, dobrze jest ograniczać odpowiedzialność klasy do pojedynczego zadania.

Kiedy w jakiejś klasie zaczyna się pojawiać zbyt duża liczba, zbyt złożonych metod prywatnych (czego objawem jest m.in. właśnie trudność w testowaniu takiej klasy przez jej publiczny interfejs), powinniśmy przenieść te metody (lub ich część) do nowej klasy, pamiętając oczywiście o jej właściwej nazwie, oddającej jej ograniczone zadanie. W nowej klasie te metody staną się wtedy publicznymi tak, żeby mogła z nich korzystać klasa, z której zostały przeniesione. A skoro będą już publiczne, to dadzą się łatwo otestować bezpośrednio.

Nie podejmując się takich zmian i testując prywatne metody bezpośrednio, wiążemy nasze testy na sztywno z obecną implementacją testowanej klasy. Testujemy wtedy to, JAK dana klasa realizuje zleconą funkcjonalność, a nie to, CO dana klasa realizuje. Odwołując się bezpośrednio do prywatnych metod betonujemy implementację testowanej klasy i blokujemy sobie możliwość przyszłej jej zmiany. W takich przypadkach testy zgłoszą nam błędy nawet wtedy, kiedy funkcjonalność klasy się nie zmieni, a modyfikacji/refaktoryzacji ulegnie jedynie sposób realizacji tej funkcjonalności. Takie testy są zbyt wrażliwe i nie mają racji bytu.

Zatem jeszcze raz: na pytanie, czy testować jednostkowo metody prywatne, należy odpowiedzieć – TAK! Jeśli są proste i nieliczne, testujmy je pośrednio. W przeciwnym wypadku przenieśmy je jako publiczne do nowej klasy, gdzie będziemy mogli przetestować je bezpośrednio.

Nie usprawiedliwiajmy zaniechania refaktoryzacji/upraszczania/czyszczenia kodu tym, że to niby za dużo zachodu, zbyt trudne, czasochłonne, a przecież w danej chwili chcemy przetestować tylko jedną, malutką prywatną metodę.

Jesteśmy inżynierami oprogramowania i raczej wiemy, jak bezpiecznie dokonać takich prostych refaktoryzacji. W razie czego możemy poszukać pomocy u bardziej doświadczonych kolegów.

Jesteśmy odpowiedzialnymi programistami, świadomymi tego, jakie korzyści w dłuższym okresie daje utrzymywanie zdrowego, czystego i efektywnie otestowanego kodu. Nie odrzucajmy takich okazji na jego ulepszenie.

 

Wideo na dziś: The Magic Tricks of Testing by Sandi Metz

Bartosz Borowik

Bartosz Borowik

Software developer with years of experience desiging/implementing/enhancing/supporting/refactoring/streamlining desktop/web/client/server applications. Keen on delivering value on-time while not sacrificing best practices. Always willing to help others, to spread knowledge, to promote pair programming and test driven development. C++ lover... but still happy to write good code using other technologies.
Informacja dotycząca plików cookies

Informujemy, iż w celu optymalizacji treści dostępnych w naszym serwisie, dostosowania ich do Państwa indywidualnych potrzeb korzystamy z informacji zapisanych za pomocą plików cookies na urządzeniach końcowych użytkowników. Pliki cookies użytkownik może kontrolować za pomocą ustawień swojej przeglądarki internetowej. Dalsze korzystanie z naszego serwisu internetowego, bez zmiany ustawień przeglądarki internetowej oznacza, iż użytkownik akceptuje stosowanie plików cookies.

Akceptuję