2023.11.16 aktualizacja: dotNet 8 wprowadza abstrakcję TimeProvider umożliwiającą wszystko to co opisałem niżej oraz dużo więcej. Szczegółowe informacje na blogu Andrew Locka. Nadal jednak możesz przeczytać to co napisałem, będzie Ci łatwiej użyć nowych klas.
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ą:
- 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.
- 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.
- 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!