Mockowanie systemu #2 – DateTime

0
175
Źródło: https://sharpsnippets.wordpress.com

Dlaczego? To już było

Dlaczego chcemy i powinniśmy mockować DateTime ustaliliśmy w poprzednim odcinku. Dziś zajmiemy się odpowiedzią na pytanie:

Jak podejść do DateTime?

Pytanie bardzo krótkie, ale odpowiedź na nie mogłaby zająć kilka tego rodzaju artykułów. Jako, że ja nie mam tyle wiedzy, aby je wszystkie opisać, a Ty cierpliwości, aby o wszystkich czytać, pójdźmy na kompromis: opowiem tylko o tych, z których choć przez chwilę korzystałem (lub słyszałem :P). Zatem do rzeczy.

Jest wiele sposobów

Najprostszy – klasa statyczna

Przez dłuższy czas używałem rozwiązania najprostszego z prostych. Skonstruowałem sobie klasę o następującej strukturze:

A w kodzie produkcyjnym wykonałem prostą zmianę:

Jak widać, zamiast klasy DateTime, użyłem SystemTime. Dzięki temu kod jest praktycznie takie sam, a testy są możliwe i wyglądają następująco:

Dowolnie manipulujemy czasem, po prostu ustawiając statyczne pole Now na odpowiadającą nam datę. Plusem tego rozwiązania jest jego prostota, granicząca niestety z prostactwem. Bardzo łatwo też wykryć użycie tej klasy w kodzie – zwykłym wyszukiwaniem tekstowym. Minusami są użycie klasy statycznej – jeżeli to jest oczywiście minus w tym przypadku.

Troszkę bogatszy – klasa

Rozwinięciem powyższego sposobu jest utworzenie intefejsu wystawiającego niezbędne metody związane z czasem, jego implementacji oraz podpięcia tego do kontenera IoC. Teraz w każdej klasie pracującej z czasem wstrzykujemy implementację interfejsu. Kod Providera jest niezwykle prosty – tworzy go jedynie interfejs, w którym dowolnie definiujemy metody. W przypadku niżej mamy tylko Now, ale w kodzie rzeczywistym mam bardziej złożone właściwości – np. BeginOfTodayTranslatedToUtc.

Kod produkcyjny zmienia się znów nieznacznie – w konstruktorze wstrzyknąłem obiekt implementujący IAmDateTimeProvider. W kodzie produkcyjnym będzie to DateTimeProvider, w kodzie testowym TestDateTimeProvider:

 

Testy nie różnią się bardzo od poprzednich i wyglądają następująco:

To rozwiązanie podoba mi się najbardziej. Oczywiście zwiększa nam ono skomplikowanie kodu – mamy więcej kodu do utrzymania, ale takie podejście jest bardziej elastyczne i korzyści wg mnie wielokrotnie przewyższają ewentualne problemy. Dzięki takiemu podejściu możemy tworzyć testy pokrywające dowolnie skomplikowane scenariusze. Gdy po raz pierwszy użyłem tego kodu i tworzyłem testy do zgłoszonego błędu, byłem zachwycony w jak prosty sposób mogłem go odtworzyć i naprawić. Szybkość i łatwość pracy wynagrodziła mi wysiłek pamiętania o użyciu tego podejścia przy pisaniu kodu produkcyjnego.

Oczywiście z klasy TestDateTimeProvider możemy w łatwy sposób zrezygnować, używając biblioteki Moq:

Fancy – zewnętrzne biblioteki

Zamiast bawić się w swoje interfejsy, implementacje i inne takie, dlaczego by nie użyć doświadczenia i kodu osób, które zjadły na tym zęby? Znam co prawda odpowiedź na to pytanie (choćby sławetne „NIH – not invented here”), ale jeżeli nie ma żadnych przeszkód, można wtedy spróbować zewnętrznych bibliotek, których mnogość i bogata funkcjonalność potrafią czasami przyprawić o ból głowy (co też może być jednym z powodów nie stosowania ich – zbytnie skomplikowanie).

Problem pojawia się jednak przy próbach mockowania tego, nad czym skupiamy się w tym wpisie – czyli statycznych właściwości czy też metod. Wtedy nagle krąg dostępnych bibliotek zawęża się dramatycznie i zostają:

  1. TypeMock – bezpłatny piętnastodniowy okres próbny. Później 400 euro za rok. Nie korzystałem z tego i nie zamierzam chyba, że moja firma będzie tak bogata czy zdesperowana, że sama to zaproponuje.
  2. Microsoft Fakes – dawniej Moles. Dostępny tylko w najdroższej wersji Visual Studio – Enterprise. Ja „niestety” pracuję na Professional w pracy i Community w domu, więc także adios. Jeżeli ktoś jednak ma tę najlepszą wersję, to Piotr Zieliński bardzo ładnie opisał wykorzystanie tego frameworka na swoim blogu.
  3. Smock – jedyna bezpłatna biblioteka, którą udało mi się znaleźć, a która posiada niezbędne nam możliwości. Jest dostępna na NuGet’cie i stosunkowo prosta w użyciu. Możemy dzięki niej mockować statyczne właściwości, metody czy zdarzenia (pytanie, czy właściwe jest testowanie zdarzeń, czy też może taka konieczność sygnalizuje, że kod jest w złym stanie?). W każdym razie, jeżeli idzie o przelicznik cena/jakość to biblioteka jest nie do pobicia 🙂 Przykład użycia poniżej.

Ne pochylam się nad wszystkimi znanymi mi (ze słyszenia) bibliotekami, a tylko sygnalizuję ich obecność na rynku – sam nie korzystam z nich na co dzień i dlatego wolę tylko zasygnalizować, że coś takiego istnieje, a nie opisywać skomplikowane funkcjonalności, których nie używam.

Koniec z datami

Na koniec wszystko co naprawdę trzeba podkreślić: nieważne, które rozwiązanie wybierzesz. Ważne jest, byś je stosował i był konsekwentny w jego używaniu. Na początku może się to wydawać dziwne, by wstrzykiwać DateTime, który przecież oferuje Ci .Net, ale gdy tylko pojawią się jakiekolwiek problemy związane z czasem, będziesz z radością wspominał chwilę, gdy podjąłeś tę dobrą decyzję. Wiem, bo sam tak miałem.

Popatrz na przykład na taki test:

Na pierwszy rzut oka nie spełnia on standardów testów jednostkowych. Nie ma jednej asercji, nie ma struktury AAA, pewnie wielu rzeczy nie ma. Jednak to wszystko kompensują jego zalety – a raczej jedna z nich – wyjaśnia jak działa program. Pokazuje jak będzie się zachowywał w newralgicznych momentach czasowych, jakie intencje miał programista pisząc kod produkcyjny. Samo pisanie takiego testu i głębokie zastanowienie się – „aha, o północy nie powinno się wykonać, ale nad ranem już tak”, daje możliwość zrozumienia systemu a przez to dużo większą kontrolę nad nim. Same plusy – a nie byłoby ich, gdyby nie możliwość mockowania czasu.

Na dziś koniec. W kolejnym odcinku postaram się opisać sposoby radzenia sobie z mockowaniem interakcji z systemem plików. Jeżeli przykłady z tego odcinka wydają się interesujące, można zobaczyć je wszystkie na raz na githubie.

Do zobaczenia!

ZOSTAW ODPOWIEDŹ

Please enter your comment!
Please enter your name here