Testy jednostkowe: Podstawy Moq – cz.1

Realny, czyli powstający nie jako ilustracja do przykładu, kod jest często uwikłany w bardzo wiele zależności. Jest to normalne i nie ma w tym nic dziwnego. W momencie gdy piszemy testy jednostkowe (co ma miejsce albo przed pisaniem kodu (TDD), albo po) stajemy w obliczu dylematu: jak poradzić sobie z daną zależnością. Po co sobie w ogóle z nią radzić? Bo chcemy przetestować działanie konkretnej funkcjonalności jako takiej, w taki sposób by mieć pewność że wynik naszego testu nie jest zależny, w danym momencie, od jakiegoś czynnika zewnętrznego. Załóżmy, że chcemy przetestować pilot do telewizora – naciskamy guzik, oczekując że telewizor się włączy, a tu bach – nic się nie dzieje. Czy możemy założyć że pilot jest popsuty? Oczywiście że możemy, ale byłby to błąd w naszym rozumowaniu :). Popsuty może być również telewizor. Należało by w takim wypadku pilot testować pod kątem  wysyłania sygnału, ale zamiast telewizora, powinniśmy użyć jak najprostszego urządzenia odbiorczego, które po prostu potrafiło by powiedzieć „sygnał do mnie dotarł”, lub „brak sygnału”. To urządzenie odbiorcze byłoby w tym wypadku mock‚iem.

Co to jest Moq? : Jest to popularny framework dla .NET, pozwalający w prosty sposób tworzyć mock’i. Gdybyśmy chcieli robić to ręcznie, musielibyśmy napisać wiele lini dodatkowego kodu, który przecież również musiałby być utrzymywany. Korzystając z Moq (lub podobnego frameworka) możemy tworzyć mock’i niewielkim nakładem pracy.

Na początek zobaczmy jak wykonuje się podstawowe operacje, dzięki którym będziemy w stanie szybciej pisać testy.


 

Tworzenie mock’a:

Żeby utworzyć imitację (jest to chyba jedno z najlepszych tłumaczeń na nasz język ojczysty 😉 – ewentualnie atrapa) zależności, musi ona implementować interfejs, lub mieć składowe wirtualne – jak możemy się domyślić Moq, na ich podstawie „zaimplementuje” swoją, w pełni konfigurowalną wersję. Załóżmy, że piszemy testy klasy Client, do której przez konstruktor wstrzykiwana jest instancja klasy implementującej interfejs IClientDependency. Wówczas kod odpowiedzialny za utworzenie naszego „system under tests” oraz atrapy dla IClientDependency mógłby wyglądać tak:

 
Mock<IClientDependency> mockClientDependency = new Mock<IClientDependency>();
Client systemUnderTests = new Client(mockClientDependency.Object); 

 


Teraz gdy mock jest już wstrzyknięty do testowanej klasy, pozostaje tylko wykorzystać możliwości jakie daje nam Moq, aby konfigurować działanie zależności (czyli np. co zwróci nam dana metoda), oraz weryfikować określone warunki (np. ile razy metoda została wywołana).

Weryfikacja wywołania danej metody zależności:

Załóżmy, że chcemy w teście zweryfikować, że w danej metodzie wywoływana jest metoda z zalezności. Niech IClientDependency posiada metodę DoSomething(int argument), która powinna być wywoływana jeden raz w metodzie Do() klasy Client . Wówczas nasz test mógłby wyglądać tak:

 
public void Do_CallDoSomething_FromClientDependency()
{
    //Act:
    this.systemUnderTests.Do();
    //Assert:
    this.mockClientDependency.Verify(c => c.DoSomething(It.IsAny<int>()), Times.Exacly(1));
}

Jak widać wszystko jest robione w sposób bardzo „kompaktowy”. Za pomocą wyrażenia Lambda określamy jaka metoda ma być wywołana. Jeżeli, tak jak w powyższym prościutkim przykładzie, owa metoda posiada jakieś parametry, to nie musimy nawet specjalnie martwić się o dostarczanie jej argumentów testowych. Drugi parametr metody Verify pozwala na przekazanie informacji na temat ilości oczekiwanych wywołań.


 

Kolejną istotną dla testów kwestią jest sprawdzenie czy dana metoda rzuca wyjątek. Moq również pozwala bardzo łatwo testować takie przypadki. Wystarczy że testowaną metodę oznaczymy atrybutem ExpectedExceptionAttribute, i wywołamy funkcję po której spodziewamy się rzucenia wyjątku.

Jak widać Moq jest bardzo prosty, i tak naprawdę znajomość trzech podanych wyżej zagadnień pozwala na testowanie wielu przypadków. W kolejnej części przedstawię kolejne zastosowania na mniej trywialnym przykładzie, ale tak jak mówiłem znajomość tworzenia atrap, weryfikacji wywołania (wraz z umiejętnością wykorzystania fluent syntax dla podawania argumentów) metod, oraz weryfikacji rzucania wyjątku, powinna mieć szerokie zastosowanie 🙂

 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *