fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
6 minut

Sposoby pisania testów z mockami: Expectations vs Record/Replay vs Arrange-Act-Assert


30.07.2009

Zgłębiając arkana tworzenia testów jednostkowych z wykorzystaniem mock objects możemy natknąć się na trzy szkoły/sposoby pisania kodu. Zwykle możliwe jest zastosowanie więcej niż jednego podejścia za pomocą danej biblioteki. Wszystkie jednak charakteryzują się podobnymi “etapami”: musimy stworzyć środowisko testowe, wykonać testowane operacje oraz sprawdzić ich poprawność. Różnice polegają na składni oferowanej przez framework oraz niejawnych założeniach sygnalizowanych przez daną metodę.

Pokrótce przedstawię wszystkie z nich wraz z przykładem napisanym z wykorzystaniem Rhino Mocks (które oferuje wszystkie drogi) oraz krótkim komentarzem.

Expectations

To jest chyba “najpierwsza” metoda pisania testów, która narodziła się wraz z samymi mockami. Polega na definiowaniu swoich oczekiwań względem tworzonego środowiska jeszcze przed wykonaniem jakiejkolwiek operacji. Przykład może wyglądać tak:

Co widzimy na pierwszy rzut oka… Dość rozwlekła składnia niepotrzebnie zwiększa liczbę linii kodu. Co gorsza, już na etapie definiowania oczekiwanego zachowania musimy przejmować się takimi szczegółami jak wartości zwracane przez metody, co zwykle nie będzie nas zupełnie interesowało – w końcu chcemy tylko zweryfikować fakt ich wywołania z odpowiednimi parametrami. Dodatkowo pisanie testów w taki sposób bardzo łatwo przeistacza się w tworzenie absolutnie nieczytelnych molochów, w których konfiguracja środowiska (jak na przykład definiowanie wartości otrzymywane z zależności wykorzystywane w testowanej metodzie, jednak nie będące przedmiotem testu) miesza się i plącze z tym co w teście najważniejsze – czyli bardzo klarownym przedstawieniem oczekiwanego rezultatu działania. Samo mockery.VerifyAll() nie mówi nam nic poza stwierdzeniem “koniec testu”.

Record/Replay

Ten model był rekomendowanym trybem pracy z Rhino Mocks do czasu wydania ostatniej wersji. Jego założenia są właściwie takie same jak modelu poprzedniego z tą różnicą, że składnia proponowana przez autora Rhino jest zdecydowanie milsza dla oka:

Wyraźne wydzielenie sekcji ustawiania oczekiwań oraz podejmowania testowanych akcji za pomocą sprytnego wykorzystania interfejsu IDisposable może się nawet podobać. Nadal mamy jednak problem z tym, że na takie testy powinno się (IMHO) patrzeć odwrotnie…

Arrange-Act-Assert

AAA dzieli test na trzy oderwane części, przyjemnie wyodrębniane do osobnych, łatwych do opisania i zrozumienia metod (które można z kolei komponować w twierdzenia definiujące zachowanie naszego systemu, do czego zachęca nas podejście Behavior Driven Development). Konfiguracja środowiska testowego nie jest ściśle powiązana ani z akcjami, które za chwilę wykonamy, ani z efektem, którego oczekujemy. Oczywiście stworzenie całej logicznej struktury testów jednostkowych możliwe jest również przy zastosowaniu poprzednich sposobów, jednak nie jest to tak naturalne, wygodne i czytelne jak w przypadku AAA. Przykład tego prezentowany był już kilkukrotnie:

Kluczową zaletą jest fakt, że możemy skupić się tylko i wyłącznie na tym, co nas bezpośrednio interesuje, pomijając nieistotne szczegóły. Nie bez znaczenia jest również zwięzła, a jednocześnie czytelna, konstrukcja, jasno definiująca zarówno stan początkowy systemu, jak i oczekiwany wynik końcowy. Na pierwszy rzut oka wydawać się może że “przecież to tylko zamieniona kolejność, o co tyle rabanu!”. Też tak na początku myślałem, jednak w bardziej skomplikowanych scenariuszach składających się z więcej niż jednej zależności i zawierających dość złożoną niejednokrotnie konfigurację szybko dostrzega się siłę takiego podejścia. Pamiętajmy przy tym, że jeden test jednostkowy powinien testować dokładnie jedno zachowanie. Podczas stosowania składni AAA naturalne wydaje się trzymanie dyscypliny w tym względzie, co owocuje bardziej granularnymi i mniejszymi testami (prawdziwie jednostkowymi).


Jak można się domyślić, dla rozpoczynających przygodę z mockami ogólnie, a Rhino Mocks w szczególności, owa mnogość opcji nie jest wcale na rękę. Dałem wyraźnie do zrozumienia, że według mnie jedynym “słusznym” podejściem jest AAA doczepione do Rhino w najnowszej wersji za pomocą extension methods. Mogę oczywiście nie mieć racji (jeśli tak uważasz, zostaw proszę komentarz z argumentacją potępiającą jego stosowanie). Tak czy siak dość dynamiczny rozwój biblioteki i absorpcja tego stylu spowodowały, że teraz API zaśmiecone jest masą nieużywanych konstrukcji. Co prawda nie przeszkadza to w wygodnym wykorzystaniu części odpowiedzialnej za AAA, jednak nie byłoby z mojej strony uczciwe gdybym ponownie nie wspomniał o konkurencyjnym Moq. Wygląda on pod tym względem bardziej obiecująco. Jego API również ma pewne zaszłości (jak metoda Expect oznaczona jako Obsolete), ale mimo to mniej jest nieużywanych obszarów zmylających programistę. Na szczęście każdy ma wolną wolę (wiem, niepoprawny ze mnie optymista) i może wybrać wedle własnego uznania.
Którego stylu używasz, jakie widzisz w nim zalety? A może inny framework, o którym nie wspomniałem, oferuje coś jeszcze lepszego?

0 0 votes
Article Rating
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
simon
14 years ago

Wstyd się przyznać, ale nie wiedziałem, że RM ma możliwość pracy "w trybie" AAA. Używaliśmy tego jakieś 2 miesiące temu i albo jeszcze nie było, albo przeoczyliśmy. Cóż, w ramach treningu przepisze dziś chyba te testy wszystkie:)

Dzięki za oświecenie:)

procent
14 years ago

@simon:
Przeoczyliście, wersja 3.5 wyszła w październiku 2008, więc minęło trochę więcej niż 2 miesiące:).

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również