Rodzaje framerków do tworzenia atrap możemy podzielić na dwie kategorie:
Do pierwszej kategorii zaliczamy wszystkie do tej pory poznane frameworki do tworzenia atrap – Moq, FakeItEasy, NSubstite – a także Rhino Mocks, NMock oraz EasyMock. Ich cechą charakterystyczną jest ograniczona możliwość tworzenia atrap.
Biblioteki te generują kod dziedziczący atrapy w czasie rzeczywistym, w oparciu o kod pośredni (IL). Najczęściej atrapy są tworzone w oparciu o wzorzec projektowy dynamicznego proxy, który wymaga tego aby kod umożliwiał dziedziczenie. Oznacza to, że aby stworzyć atrapę potrzebujemy interfejsu do naszej klasy lub metody wirtualnej. Jako, że kluczem do stworzenia atrapy jest dziedziczenie, nasza klasa/metoda nie może być statyczna, niepubliczna, sealed oraz musi posiadać publiczny konstruktor. Oznacza to też, że kod zawarty w konstruktorze oraz polach klasy jest wykonywany przy tworzeniu atrapy.
Czym jest dynamiczne proxy?
Dynamiczne proxy to klasa, która implementuje interfejs lub klasę w trakcie wykonywania programu (run-time).
Biblioteki te są zwykle darmowe, a obiekty proxy są zwykle tworzone przy użyciu biblioteki Castle.Windsor.
Frameworki o “nieograniczonych możliwościach” to Typemock Isolator, JustMock i Microsoft Fakes. Są one napisane w oparciu o Common Language Runtime (CLR) Profiler, który udostępnia API pozwalające na większą kontrolę nad wykonywanym kodem. Możemy zatem wstrzykiwać nasz kod przed kompilacją kodu pośredniego oraz mamy dostęp do eventów wywoływanych w trakcie wykonywania naszego kodu. Większa kontrola nad generowanym kodem oznacza, że frameworki te pozwalają na tworzenie atrap dla kodu, który nie musi być dziedziczony, a więc klasy/metody statyczne, prywatne, biblioteki zewnętrzne, klasy systemowe (np. DateTime), itp. Ze względu na stopień skomplikowania pisania kodu w oparciu o API Profilera, frameworki te (poza Microsoftowym) są dostępne odpłatnie.
Przykłady bibliotek
constrained generują kod pośredni w trakcie wykonywania (run-time):
unconstrained bazujące na API Profilera:
Osobiście preferuję podejście przy użyciu “ograniczonych” frameworków, gdyż wymuszają one pisanie dobrego kodu w oparciu o programowanie zorientowane obiektowo. Podobnie jest z refaktoryzacją i poprawkami w nieprzetestowanym kodzie. Izolacja miejsca, które zmieniamy oraz tworzenie kodu który będzie w łatwy sposób testowalny również powoduje zwiększenie jakości kodu. Nie chcę przez to powiedzieć, że samo wykorzystanie bibliotek constrained gwarantuje nam poprawę kodu “gratis”. Dostajemy jednak informację zwrotną na temat designu naszych klas—nie możemy stworzyć atrapy jeśli nasza klasa nie jest testowalna z powodu np. statyczności lub braku interfejsu.
Użycie bibliotek unconstrained wiąże się z kilkoma pułapkami, m.in.
Frameworki constrained wymagają więcej wiedzy oraz doświadczenia na temat testowania oraz dobrego kodu, ale dzięki temu zyskujemy natychmiastową informację zwrotną na temat jakości naszego kodu. Ja stosuję z powodzeniem tę grupę bibliotek zarówno w przypadku greenfield (nowy kod), jak i brownfield (stary kod).
A wy jakie macie zdanie na temat tych dwóch grup frameworków?
Część I: Testy jednostkowe – wstęp
Część II: Atrapy obiektów
Część III: Teoria