Jak dobre są Twoje testy? Czyli kilka słów o testowaniu mutacyjnym.

Załóżmy, że mamy pewną aplikacje, o którą bardzo się troszczymy i robimy wszystko, żeby zapewnić jak najlepszą jakość kodu tejże aplikacji. Pokrycie kodu testami jednostkowymi jest jednym ze sposobów, którego możemy użyć, aby ‚zabezpieczyć’ nasz kod przed błędami. Dwoimy się i troimy, aż w końcu udaję nam się pokryć nasz kod w 100% i teraz mamy pewność, że żaden bug nie wkradł się do naszej aplikacji! Ale czy na pewno?

Stwórzmy aplikacje, o którą zadbamy!

Zacznijmy od ‚zewnętrznego’ serwisu, który będziemy wstrzykiwać do naszej klasy.

Teraz stwórzmy główną klasę naszej aplikacji:

Jak widzimy, nie ma tutaj żadnego ‚Rocket Science’ 🙂 Pobieramy z zewnętrznego serwisu dwie liczby, a następnie je porównujemy. Jeżeli pierwsza z nich jest większa to odejmujemy od niej drugą liczbę i otrzymaną liczbę zwracamy jako wynik. W przeciwnym przypadku jako rezultat zwracamy sumę dwóch pobranych liczb.

Pora przetestować naszą aplikacje!

Stwórzmy najpierw pomocniczą klasę, która będzie implementować interfejs naszego zewnętrznego serwisu.

I teraz pierwsza metoda testowa:

W powyższej metodzie testowej sprawdziliśmy pierwszy przypadek, czyli sytuacje gdzie pierwsza pobrana liczba z zewnętrznego serwisu jest większa od kolejnej.

Teraz pora na drugi przypadek:

Tutaj pokryliśmy drugi przypadek, czyli sytuacje gdy druga liczba jest większa od pierwszej.

Wygląda na to, że pokryliśmy wszystkie ‚branche’ i nasz kod jest w pełni przetestowany, ale skąd mamy wiedzieć jak dobre są nasze testy jednostkowe?

Testy mutacyjne

Tutaj w pomocą przychodzą nam testy mutacyjne. Czym zatem są owe testy? Jest to technika polegająca na wprowadzaniu małych i losowych zmian w kodzie naszej aplikacji. Zmiany te powinny zostać wykryte przez nasze testy jednostkowe. Jeżeli, któraś ze zmian nie została wykryta oznacza to, że nasze testy mogą nie być tak dobre jak nam się wydawało 😉

Jakie zmiany?

Poniżej znajduję się lista z przykładowymi zmianami, które mogą zostać wprowadzone w naszym kodzie.

  • Zmiana granicy w warunkach, np. > zostanie zmienione na >=, >= na >, itd.
  • Negacja warunków, np. == zostanie zmienione na !=, <= na >, itd.
  • Usunięcie warunków i zastąpienie ich stałą wartością, np. a > b zostanie zmienione na true
  • Zmiana operacji matematycznych, np. dodawanie zostanie zamienione na odejmowanie, a mnożenie na dzielenie
  • Zmiana wartości zmiennych na wartości defaultowe lub stałe, np. int zostanie ustawiony na 0 lub inną losową wartość
  • Zwrócenie null zamiast obiektu
  • Pominięcie wywołania metody typu void

Właśnie zapoznaliśmy się z przykładowymi modyfikacja, które mogą zostać wprowadzone do naszej aplikacji podczas testów mutacyjnych. Nasze testy jednostkowe powinny być napisane w taki sposób, aby zmiany te spowodowały to, że nasze testy nie przejdą.

Testy mutacyjne w praktyce

Wróćmy teraz do naszego kodu, który napisaliśmy na początku i spróbujmy przeprowadzić testy mutacyjne. Z pomocą przyjdzie nam biblioteka PIT!

Konfiguracja

Konfiguracja i uruchomienie PIT są banalnie proste! Pierwsze co musimy zrobić to dodać plugin do naszego poma:

Domyślnie wszystkie klasy z naszej aplikacji zostaną poddane testom mutacyjnym. Jeżeli chcemy to zmienić to możemy skonfigurować pakiety klas/testów, które będą wzięte pod uwagę.

Uruchomienie

Aby przeprowadzić testy mutacyjne wystarczy wywołać następujące polecenie:

Gdy operacja zakończy się sukcesem zostanie wygenerowany raport z wynikami. Znajduje się on pod następującą ścieżką: target/pit-reports/yyyyMMddHHmm.

Zmutujmy naszą aplikacje!

Pora wrócić do naszej aplikacji i wykonać na niej testy mutacyjne 🙂

Po zakończeniu testów otrzymamy wygenerowany raport.

Możemy z niego wyczytać, że nasz kod jest w pełni pokryty przez nasze testy jednostkowe (Line Coverage). Możemy również zobaczyć trochę czerwonego koloru przy pokryciu mutacyjnych testów, a jak możemy się domyślać czerwony kolor nie oznacza nic dobrego 😉

Po wklikaniu się trochę głębiej będziemy mogli zobaczyć poniższy ekran.

Możemy na nim zobaczyć, która linia naszego programu nie jest wystarczająco dobrze przetestowana, a poniżej listę mutacji, które zostały przeprowadzone w poszczególnych liniach kodu. Na zielono są zaznaczone mutacje, które zostały wykryte przez testy, natomiast na czerwono mutacje, które przeżyły i nasze testy ich nie wychwyciły.

W naszym przypadku nie została wychwycona zmiana warunku w if’ie z > na >=. Czyli w tym przypadku został wykryty warunek brzegowy, który nie został sprawdzony w testach.

Poprawy w takim razie nasz drugi test tak, aby pokrył warunek brzegowy.

Po tej modyfikacji żadne mutacje nam nie straszne i nasze testy mutacyjne przejdą na zielono 🙂

Podsumowanie

Dzisiaj zapoznaliśmy się z podstawami testów mutacyjnych. Testy te mogą nam pomóc w sprawdzeniu jak dobre są nasze testy jednostkowe. Sama koncepcja testów mutacyjnych nie jest niczym nowym, ale dopiero stosunkowo od niedawna jest używana w praktyce, ponieważ testy mutacyjne są dosyć kosztowne i wymagają sporej czasu procesora, żeby przeprowadzić wszystkie kombinacje mutacji i dopiero od niedawna nasze komputery są na tyle szybkie, żeby robić to w rozsądnym czasie 🙂

Budowniczy w akcji!

Dzisiaj porozmawiamy trochę o jednym z bardziej popularnych wzorców projektowych, a mianowicie o Budowniczym (Builder). Jest to jeden z kreacyjnych wzorców projektowych.

A na co to komu?

Dzięki użyciu budowniczego możemy z pewnością osiągnąć przynajmniej dwie rzeczy:

  • oddzielimy tworzenie obiektu od jego reprezentacji,
  • nasz kod powinien stać się trochę bardziej przyjazny dla oka 😉

The good old way…

Na początku zobaczmy jak będzie wyglądał zwykły obiekt POJO. Jako przykład stwórzmy klasę, która będzie zawierała wszystkie informacje o mailu, który będzie mógł być później wysłany.

Jak widzimy, nasza klasa ma za zadanie przechowanie informacji o tym do kogo mail będzie wysłany (to), dodatkowych odbiorów (cc), temat maila (subject) oraz jego treść (content).

Stwórzmy teraz instancje naszej klasy:

Co prawda ten kod nie wygląda jeszcze zbyt strasznie, ale wyobraźmy sobie teraz, że do naszej klasy dodajemy jeszcze kilka pól jak bcc, mime type, załączniki i jeszcze kilka innych. Wtedy nasza klasa trochę się rozrośnie i stworzenie instancji tej klasy nie będzie wyglądało zbyt przyjemnie, szczególnie mając na uwadze, że dużo pól naszej klasy ma ten sam typ.

A co jeśli…

Możemy się okazać, że część pól w naszej klasie nie jest obowiązkowa przy tworzeniu naszego obiektu. W naszym przykładzie pole cc nie musi być obowiązkowe. Co w takim przypadku powinniśmy zrobić?

Możemy stworzyć dodatkowy konstruktor bez pola cc 🙂 Tylko co jeśli pól opcjonalnych będziemy mieć kilka? Tworzenie wszystkich kombinacji konstruktorów zdecydowanie odpada.

W takim przypadku możemy przecież stworzyć POJO z setterami/getterami 🙂

I stwórzmy teraz instancje naszej klasy, tylko tym razem bez inicjowania pola cc:

Podejście to pozwala nam stworzyć instancje naszego obiektu tylko z wybranymi przez nas polami. Dzięki setterom jest również trudniej o pomyłkę związaną z kolejnością przekazywanych parametrów do konstruktora.

Czy można zrobić to ładniej?

Wydaję mi się, że można 😉 I właśnie budowniczy przyjdzie nam z pomocą 🙂

Budowniczy pomaga nam w tworzeniu skomplikowanych obiektów poprzez danie możliwości tworzenia (budowania) ich kawałek po kawałku. Stwórzmy zatem budowniczego do naszej klasy 🙂

Zmiany, których dokonaliśmy w naszej klasie to:

  • stworzyliśmy prywatny konstruktor do klasy Mail, aby nikt nie stworzył nam naszego obiektu na lewo ;),
  • dodaliśmy statyczną metodę builder(), która zwraca nową instancje budowniczego,
  • dodaliśmy samego budowniczego.

Klasa budowniczego zawiera w sobie pole z instancją budowanego obiektu, metody odpowiadające ustawianym polom oraz metodę build(), która wieńczy dzieło i zwraca nam naszą zbudowaną instancje klasy Mail. Zwróćmy uwagę, że metody budowniczego zwracają instancje budowniczego. Dzięki czemu możemy zbudować nasz obiekt w sposób ‚płynny’ (fluent interface) 😉

I w ten oto sposób udało nam się stworzyć prostego budowniczego 🙂

A co jeśli… (cz. 2)

Wcześniej zauważyliśmy, że część pól z naszej klasy może być opcjonalna. Warto, żebyśmy też popatrzyli w drugą stronę – część pól z może być konieczna do poprawnego zainicjowania naszego obiektu. W naszej klasie Mail możemy uznać, że pola to, subject i content są obowiązkowe. I co teraz?

The good old way jeszcze raz 😉

Wróćmy na sekundę do podejścia z POJO. Problem ten możemy tutaj rozwiązać poprzez stworzenie konstruktora, który będzie przyjmować obowiązkowe parametry oraz stworzeniem setterów dla parametrów opcjonalnych.

Tylko w tym podejściu znów powraca problem z wieloma parametrami w konstruktorze, które nie wyglądają zbyt ładnie oraz dodatkowo nie jest trudno o pomyłkę.

I wróćmy do budowniczego

A jak możemy ten problem rozwiązać w budowniczym?

Możemy obowiązkowe parametry przekazać do konstruktora budowniczego, ale oczywiście rezygnujemy z tej opcji 😉

Do rozwiązania tego problemu musimy trochę zmodyfikować naszego budowniczego, którego już stworzyliśmy.

Na początku stwórzmy sobie kilka prostych interfejsów.

Zauważmy, że dla każdego obowiązkowego pola stworzyliśmy interfejs, który zawiera dokładnie jedną metodę odpowiadającą za ustawienie tego pola. Zwróćmy uwagę też, że metoda ustawiające dane pole zwraca interfejs do ustawienia kolejnego obowiązkowego pola. Wyjątkiem jest interfejs ContentStep, który ustawia ostatnie obowiązkowe pole i zwraca interfejs OptionalSteps, który zawiera możliwość ustawienia wszystkich opcjonalnych pól oraz metodę build(), która zwraca nam gotowy obiekt.

Teraz musimy użyć tych interfejsów w naszym budowniczym.

Sama implementacja znacząco się nie zmieniła. Teraz nasz builder implementuje stworzone przez nas interfejsy. Musieliśmy też zmienić zwracane typy w metodach budowniczego.

I ostatnia zmiana, którą musieliśmy zrobić to zmiana typu zwracanego w metodzie tworzącej naszego budowniczego, żeby zwracała interfejs do pierwszego obowiązkowego pola.

 

Samo budowanie obiektu nie zmieniło się w porównaniu do tego, jak było w naszym pierwszym budowniczym, ale dzięki tym kilku interfejsom klient używający naszego budowniczego będzie musiał podać wszystkie obowiązkowe pola, żeby utworzyć obiekt.

I to jest właśnie to, co chcieliśmy osiągnąć 🙂

Ile on waży?!

Czy zastanawialiście się kiedyś jak dużo pamięci zajmują obiekty w Javie? Dużo osób pewnie nigdy się nad tym nie głowiło, a przynajmniej nie przed pierwszym OutOfMemoryError 😉

W tym wpisie postaram się pokazać w jaki sposób oszacować wielkość obiektów, które tworzymy w Javie.

Typy prymitywne

Niewątpliwie najłatwiej jest określić ilość pamięci zajmowanej przez typy prymitywne. Poniżej przedstawiam tabelkę, która pokazuje ile każdy z typów potrzebuję bajtów w pamięci.

Typbajty
boolean1
byte1
char2
int4
float4
long8
double8

Ilość pamięci potrzebnej do przechowywania booleana może trochę dziwić. W końcu może on przyjąć tylko dwa wartości i w teorii powinien zajmować 1 bit. Specyfikacja JVM nie definiuje ile pamięci jest potrzebne na przechowanie booleana i zależy to od danej implementacji. Dla uproszczenia możemy śmiało przyjąć, że jest potrzebny 1 bajt.

Reszta wartości wydaję się być w miarę oczywista 🙂

Tablice

Każda tablica zajmuje wielokrotność danego typu (co wydaję się oczywiste ;)) oraz dodatkowe 24 bajty, które są narzutem tablicy. Poniżej przedstawiam tabelkę z przykładami, które powinny wszystko wyjaśnić.

Typbajty
byte[]N + 24
char[]2N+24
int[]4N+24
double[]8N+24

Obiekty

I teraz przechodzimy do mięsa tematu  – czyli jak oszacować wielkość obiektu naszej klasy.

Aby to zrobić potrzebujemy kilku dodatkowych informacji.

Narzut obiektu

Każdy obiekt, podobnie jak tablica, powoduje dodatkowy narzut pamięci. W tym przypadku jest to 16 bajtów.

Referencja

Każda przetrzymywana referencja w naszym obiekcie to dodatkowe 8 bajtów.

Padding

Wielkość każdego obiektu jest uzupełniana do wielokrotności 8 bajtów.

Praktyka 🙂

Jako przykład weźmy klasę String. Poniżej przedstawiam wycinek implementacji tej klasy, który jest potrzebny do oszacowania wielkości obiektu.

Do obliczenia potrzebujemy zsumować kilka rzeczy:

  1. Narzut obiektu – 16 bajtów
  2. Referencja do tablicy – 8 bajtów
  3. Tablica – 2N + narzut tablicy 24 bajty
  4. Pole offset – 4 bajty
  5. Pole count – 4 bajty
  6. Pole hash – 4 bajty

Zsumowując wszystko wychodzi nam 2N + 60 bajtów. W takim przypadku musimy jeszcze pamiętać o paddingu, czyli dodajemy 4 bajty, aby całkowita wielkość obiektu była wielokrotnością 8.

Podsumowanie

Powyżej przedstawiłem ogólne zasady, których możemy użyć, aby oszacować wielkość naszych obiektów w Javie. Dzięki temu możemy lepiej zrozumieć co jest alokowane w pamięci i jaki rozmiar zajmuje.

Adnotacje w Javie

W Javie 1.5 zostały wprowadzone adnotacje. Możemy je często spotkać przy pisaniu testów jednostkowych, ORMach lub różnych frameworkach. Czym są właściwie adnotacje i co nam dają?

Adnotacje są to metadate, które dostarczają nam informacji na temat programu, ale same nie są częścią kodu, ani bezpośrednio nie wpływają na kod.

Najprostszą adnotacje możemy stworzyć w następujący sposób:

Teraz możemy użyć jej w naszym kodzie:

Pola

Do naszych adnotacji możemy dodawać pola. Aby to zrobić musimy je zdefiniować w ciele naszej adnotacji.

I samo użycie:

Jeżeli nasze pole będzie miał nazwę value, wtedy możemy pominąć nazwę pola w użyciu adnotacji.

Domyślne wartości pól

W przykładach powyżej podanie wartości zdefiniowanych pól jest obowiązkowe. Jeżeli chcemy, aby podanie pola nie było wymagane musimy zdefiniować jego domyślną wartość.

Wtedy przy użyciu adnotacji możemy, ale nie musimy, podawać wartość pola.

Zakres adnotacji

Adnotacje domyślnie mogą być używane na:

  • innych adnotacjach
  • konstruktorach
  • metodach
  • polach
  • lokalnych zmiennych
  • pakietach
  • parametrach metod
  • klasach, interfejsach, enumach
  • typach generycznych (od Java 8)

Domyślnie nowo stworzona adnotacja może być używana w każdym z  wymienionych wcześniej miejsc. Możemy jednak ograniczyć jej zasięg poprzez użycie adnotacji @Target z parametrami definiującymi zasięg.

Wszystkie dostępne wartości ElementType to:

  • ANNOTATION_TYPE
  • CONSTRUCTOR
  • METHOD
  • FIELD
  • LOCAL_VARIABLE
  • PACKAGE
  • PARAMETER
  • TYPE
  • TYPE_PARAMETER

Kolejne wymienione wartości ElementType odpowiadają wcześniej wymienionym miejscom opisującym gdzie można użyć adnotacji.

Dodatkowo w Javie 8 wprowadzono ElementType.TYPE_USE, który może być użyty na wszystkich typach.

Retencja adnotacji

Dla każdej adnotacji możemy ustalić jej zakres widoczności. Definiujemy to przez dodanie adnotacji @Retention.

Dostępne są trzy wartości dla adnotacji @Retention:

  • RetentionPolicy.SOURCE – informacja o adnotacji jest dostępna jedynie podczas kompilacji; przykładami takich adnotacji są @Override i @SuppressWarnings
  • RetentionPolicy.CLASS – adnotacja jest dostępna podczas ładowania klas; jest to domyślny poziom retencji
  • RetentionPolicy.RUNTIME – adnotacja jest dostępna podczas działania aplikacji, dzięki czemu mamy do niej dostęp poprzez refleksje; przykładem takiej adnotacji jest @Deprecated

Krótkie podsumowanie

W wpisie przedstawiłem najważniejsze informacje na temat adnotacji w Javie. Mam nadzieję, że okażą się one przydatne i choć trochę ułatwią poruszanie się po frameworkach, które pełne są najróżniejszych adnotacji 🙂

Kontrakt hashCode i equals

Jedną z pierwszych rzeczy, o których się dowiadujemy ucząc się Javy są informacje na temat metod equals oraz hashCode. Informacje na temat kontraktu tych dwóch metod są bardzo istotne w programowaniu w Javie, szczególnie w przypadku kolekcji, ale o tym będzie kiedy indziej.

Kontrakt

Metody equals oraz hashCode powinny spełniać poniższe warunki:

  • Jeżeli x == y to x.equals(y) == true
  • Jeżeli x.equals(y) == true to x.hashCode() == y.hashCode()
  • Jeżeli x.hashCode() == y.hashCode() to x.equals(y) może zarówno zwrócić true, jak i może zwrócić false

Wszystkie wartości zwracane przez powyższe funkcje muszą być determistyczne – zawsze zwracać te same wartości dla tych samym parametrów.

Domyślna implementacja

Metody hashCode i equals są zdefiniowane w Object, czyli w klasie nadrzędnej dla wszystkich klas w Java. Domyślne implementacje wyglądają następująco:

Domyślna implementacja metody equals opiera się na sprawdzeniu czy przekazany obiekt jest tą samą instancją, co obiekt, na którym wywołano metodę equals. Jak widać domyślna implementacja w żaden sposób nie opiera się na atrybutach klasy.

Klasa Object nie ma natomiast zdefiniowanego ciała metody hashCode. Każda implementacja JVM dostarcza swoją wersje tej metody. Zazwyczaj jest to adres pamięci, pod którym znajduje się obiekt, zamieniony na typ integer. Podejście takie byłoby prawidłowe, gdyby każdy obiekt był unikalny. W przypadku kiedy dwa obiekty są identyczne (bazując na atrybutach klasy) implementacja taka nie spełnia kontraktu tej metody.

Przykładowe implementacje

Oczywiście powyższe metody możemy (a często wręcz musimy) nadpisać i dostosować do naszych potrzeb. Załóżmy, że mamy poniższą klasę, do której chcemy dopisać nasze dwie metody, o których rozmawiamy.

Przykładowe implementacja metody hashCode mogą wyglądać następująco:

Jak widać wartość hashCode jest wyliczana przy użyciu mnożenia i dodawanie na podstawie wszystkich pól klasy.

Implementacja equals dla naszej klasy może wyglądać tak:

Tutaj już się dzieję trochę więcej… albo przynajmniej wygląda jakby się działo więcej.
W liniach 3-11 sprawdzamy identyczność obiektów na poziomie samego obiektu, a nie jego atrybutów. Sprawdzamy czy:

  • przekazany obiekt jest tym samym obiektem, na którym została wywołana metoda – jeżeli jest to oczywiście zwracamy true
  • przekazany obiekt nie jest nullem – jeżeli jest to zwracamy false – obiekt, na ktorym wywołaliśmy metodę z oczywistych powodów nie może być nullem 🙂
  • oba obiekty są tego samego typu – jeżeli nie są to oczywiście zwracamy false

Jeżeli obiekt przejdzie te 3 warunki to jest rzutowany na nasz typ klasy, a następnie każde pole klasy jest porównywane. Jeżeli, któreś z porównywanych pól jest inne to oczywiście zwracamy false, a w przeciwnym przypadku przechodzimy dalej. Kiedy już wszystkie pola zostały sprawdzony to zwracamy true.

Bardziej praktyczna implementacja

Implementacje powyższych metod nie są trudne, ale zajmują trochę linii kodu (szczególnie equals). Dodatkowo implementując ręcznie te metody dla klas z większą ilością pół łatwo o pomyłke. Co prawda każde IDE potrafi wygenerować implementacje powyższych metod na podstawie pól klasy, ale wciąż zostaje problem pierwszy – dużo linii i brzydko to wygląda 😉

Bardzo polecam używanie bibliotek, które zawierają wbudowany builder’y do tych metod. Jedną z takich bibliotek jest common-lang od Apache.
Implementacja hashCode przy użyciu tej metody wygląda następująco:

Jak widać kod dla tej metody został zdecydowanie uproszczony i jest bardziej przejrzysty.

Implementacja metody equals może wyglądać na przykład tak:

Jak widać kod w tym przypadku również się trochę uprościł. Trzy pierwsze if’y co prawda zostały, ale reszta kodu została zdecydowanie skrócona i jest bardziej czytelna.

Jak widać dzięki użyciu kodu z biblioteki mogliśmy stworzyć nasze metody pisząc mniej kodu, który jest jednak bardziej czytelny i przejrzysty.

A może by tak jeszcze coś ulepszyć?

Patrząc na implementacje metody equals można by się zastanowić czy użycie instanceof zamiast drugiego i trzeciego if’a nie byłoby dobrym pomysłem. Wtedy nasza klasa mogłaby wyglądać w następujący sposób:

Dzięki takiemu podejściu ‚oszczędzamy’ jednego if’a, a wszystko działa jak działało. Chociaż…

Spójrzmy na poniższą klase:

Stworzyliśmy klasę Student, która dziedziczy po Person. Nasza nowa klasa zawiera dodatkowe pole ‚school’.

Zróbmy teraz coś takiego:

Jak widać dochodzimy do sytuacji, gdzie tworzymy dwa różne obiekty i w zależności od tego, który do którego porównamy to otrzymujemy różne wyniki. Równość powinna być relacją symetryczną, a taka sytuacja zdecydowanie ją narusza.

Czy to oznacza, że to podejście jest błędne? Odpowiedź jest bardzo prosta i brzmi: to zależy 🙂

Wersja equals z getClass jest, powiedzmy, ‚restrykcyjna’, ale za to prosta i intuicyjna. Porównywany obiekt musi być dokładnie tego samego typu co obiekt, do którego jest porównywany.

Druga wersje może narobić nam trochę ambarasu, lecz zostawia trochę więcej swobody. Istotnym przypadkiem, kiedy takie podejście może być konieczne jest porównywanie obiektów, które są zarządzane przez jakiś framework np. część implementacji JPA opakowuje encje. W takiej sytuacji, dzięki użyciu instanceof w metodzie equals jesteśmy w stanie w poprawny sposób porównywać takie obiekty.

Jak widać obydwa sposoby implementacji metody equals mają swoje zastosowania. Najważniejsze, żeby świadomie wybierać podejście, którego potrzebujemy 🙂