Nieodłącznym elementem TDD i unit testów jest mockowanie zależności. Powstało w tym celu sporo bibliotek dla różnych języków i różnych frameworków testowych. Mogłoby się więc wydawać, że wystarczy wybrać swoją ulubioną bibliotekę do mocków i po prostu jej używać. Okazuje się jednak, że pisanie prostych mocków samemu czasem może okazać się lepszym wyborem. Pokażę dzisiaj jak łatwo można napisać własne mocki do frameworków Unity i CppUTest.

Zalety własnych mocków

Największymi zaletami własnych mocków jest ich prostota prostota i brak zewnętrznych zależności. Dla porównania na przykład CMock – dedykowane mocki do Unity – wykorzystuje Ruby do parsowania headerów produkcyjnych i generowania na ich podstawie kodu mocków, który następnie wykorzystujemy w naszych testach. Rodzi to od razu kilka problemów. Po pierwsze każde środowisko, na którym budujemy i uruchamiamy testy musi mieć zainstalowane Ruby. Po drugie mocki są generowane automatyczne podczas builda, są więc za każdym razem tworzone od nowa. Z kolei przed zbudowaniem projektu w ogóle nie ma tych plików, więc nie możemy choćby podejrzeć ich implementacji.

Skoro jesteśmy przy implementacji, mocki z zewnętrznej biblioteki często są dla nas czarną skrzynką. Wywołujemy je w kodzie testów, ale nie mamy pojęcia, co dzieje się pod spodem. Zwykle nas to po prostu nie obchodzi – do momentu, kiedy coś przestaje działać. Poza tym składnia mocków bywa skomplikowana i nieczytelna.

Pisanie własnych mocków, szczególnie jeśli zaczynamy z TDD, ma również wartość edukacyjną. Pisząc je musimy rozwiązać kilka problemów programistycznych takich jak zaprojektowanie API, sposób przechowywania argumentów funkcji i zwracanych wartości. Poznajemy również ograniczenia mocków. Wiemy jakie funkcjonalności są trudne do osiągnięcia.

Mocki w C

Wystarczy tej teorii, pora na trochę kodu. Najpierw zastanowimy się, jak mogą wyglądać mocki napisane w C do wykorzystania na przykład w Unity.

Utworzymy mocka dla funkcji wypełniającej bufor danymi. Jako argumenty przyjmuje ona bufor do wypełnienia oraz jego rozmiar. Zwraca natomiast ilość wpisanych bajtów. Sygnatura takiej funkcji wygląda tak:

Aby powyższą funkcję zamockować, potrzebujemy interfejsu do wykorzystania przez unit testy. Musimy być w stanie skonfigurować wartość zwracaną przez funkcję oraz mieć dostęp do przekazanych argumentów. Przydatna jest również informacja o ilości wywołań funkcji. Poniżej deklaracje funkcji naszego mocka:

Implementacja mocka jest dosyć prosta. Musimy mieć zmienną przechowującą parametry mocka – ilość wywołań, argumenty i zwracaną wartość. Funkcja init ustawi je na wartości początkowe, funkcje get zwrócą poszczególne parametry, natomiast mock funkcji produkcyjnej zapisze przekazane argumenty i zwróci wartość ret.

Jak widać utworzenie własnego mocka w C jest proste, ale wymaga całkiem sporo pisania. Z pomocą może nam przyjść IDE. W Eclipse na przykład możemy zdefiniować szablon kodu, w którym musimy podmienić jedynie typy zmiennych i nazwy własne elementów. Oczywiście kodu mocków w dalszym ciągu będzie sporo, co doda nam pracy na przykład podczas refactoringu. Jednak dzięki temu rozwiązaniu mamy kontrolę nad kodem i nie musimy korzystać z automatycznego generowania kodu jak w przypadku CMock. Innym sposobem na zmniejszenie duplikacji jest użycie makr. Istnieje nawet projekt na GitHubie z makrami do C – Fake Function Framework.

Mocki w C++

Jeżeli korzystamy z frameworka testowego w C++, możemy wykorzystać dobrodziejstwa tego języka również do pisania mocków. Z pomocą przychodzą nam klasy oraz templaty.

Klasa mocka funkcji fill_buffer może wyglądać następująco:

Domyślne wartości zmiennych wewnętrznych są ustawiane w domyślnym konstruktorze. Mamy również drugi konstruktor inicjalizujący ret na przekazaną wartość.

Potrzebujemy jeszcze wywołać metodę call mocka w testowej wersji funkcji fill_buffer:

Na początku testu musi być zadeklarowana zmienna klasy Mock_fill_buffer i przekazana do funkcji mock_fill_buffer_set, aby mock został użyty. Po zakończeniu testu w funkcji teardown należy wywołać funkcję mock_fill_buffer_set z nullptr jako parametrem.

Przedstawione rozwiązanie w dalszym ciągu wymaga od nas wiele pisania. Dla mockowanych funkcji wykorzystujących inne typy i ilości argumentów potrzebujemy odrębne klasy. Tym razem jednak zamiast szablonów w IDE możemy użyć szablonów C++.

Wykorzystanie templatów C++

Wystarczy stworzyć template dla najczęściej używanych ilości argumentów w wariantach, gdy funkcja zwraca jakąś wartość lub nie. Każdy mock wykorzystuje zmienną cnt liczącą ilość wywołań. Możemy więc wykorzystać również dziedziczenie. Przykładowa implementacja templatów dla mocków od 0 do 2 argumentów może wyglądać następująco:

Mając takie templaty mock do funkcji fill_buffer możemy zdefiniować jako:

Obsługa funkcji mock_fill_buffer_set pozostaje taka sama.

Dzięki użyciu templatów możemy łatwo tworzyć mocki kolejnych funkcji nie dopisując zbyt dużo kodu.

Podsumowanie

Jak widać, jeżeli potrzebujemy podstawowych mocków zadających zwracaną wartość funkcji i zapamiętujących jej argumenty, nie musimy specjalnie wykorzystywać frameworków do mockowania. Jesteśmy w stanie bez problemu wykorzystać własne mocki. Dzięki temu unikamy dodatkowych zewnętrznych zależności oraz dziwnej składni często występującej w mockach. Pisał na ten temat nawet Uncle Bob.

Jednak kiedy testujemy bardziej skomplikowane zależności takie jak sekwencje wywołań, wielokrotne wywołanie jednej mockowanej funkcji w pojedynczym teście z różnymi argumentami i zwracanymi wartościami – wtedy lepiej użyć gotowej biblioteki, która posiada te mechanizmy.