devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
5 minut

[UT-3.1] O mockach jeszcze słów kilka


28.11.2011

[ten post jest częścią mojego minicyklu o testach, pełna lista postów: tutaj]

Po dość długiej przerwie wracam do tematu testów jednostkowych. Kombek zainicjuję krótkim zahaczeniem o mocki, które opisałem w poprzednim poście cyklu. Poruszyć chcę dzisiaj dwie kwestie.

Kwestia 1: terminologia

Niedawno na blogu Piotra Zielińskiego pojawił się post opisujący różnice pomiędzy terminami określającymi to co ja rozumiem przez “mock”. Przypomniało mi to czasy, gdy starałem się zgłębiać definicje skrywające się pod tymi nazwami. Ba, próbowałem je nawet opisywać (chociażby w tym poście)!

Korzystając z okazji chcę przedstawić swoją aktualną opinię na ten temat: nazwa nie ma znaczenia! Czy naprawdę mamy aż tyle rodzajów testowania interakcji pomiędzy dwoma obiektami, żeby chrzcić je z fantazją równą celebrytom wymyślającym imiona swoim celeb-pociechom? Znane mi są takie określenia: strict mock, dynamic mock, loose mock, stub, dummy, fake, substitute, test double, spy, mole. Czy to nie przesada?

Co więc spowodowało zmianę moich poglądów?

Po pierwsze – stałem się o wiele bardziej pragmatyczny. Po co zaprzątać sobie głowę takimi bzdurami, skoro liczy się efekt? W jednym teście tak samo skonstruowany obiekt może być wykorzystany jako mock, w innym jako stub. A framework go tworzący nazywa go “fake“. Jaka różnica, w którą szufladkę wpada on podczas jakichś teoretycznych rozważań? Żadna. Czy mój test będzie lepszy jeśli gdzieś z tyłu głowy będę marnował cykle procesora na dywagacje “mock vs stub“? Nie. Więc koniec z tym.

A po drugie – cały “światek” .NET idzie w podobną stronę, tzn. zwracania uwagi na to co najważniejsze i zaprzestania (tak charakterystycznych zresztą dla Microsoftu) tworzenia rozdmuchanych terminologii i klasyfikacji nadających się tylko do wydumanych artykułów w MSDN Magazine. Dlatego też na stronie projektu fakeiteasy przeczytamy “all fake objects are just that – fakes – the use of the fakes determines whether they’re mocks or stubs“. A na stronie projektu Moq: “no need to learn what’s the theoretical difference between a mock, a stub, a fake, a dynamic mock, etc.“. A na stronie projektu NSubstitute: “Mock, stub, fake, spy, test double? Strict or loose? Nah, just substitute for the type you need!“. I bardzo dobrze!

Nie żebym krytykował tutaj Piotra za jego post, bynajmniej. Miał on na celu ułatwienie przyswajania lektury dotyczącej testów jednostkowych osobom w miarę świeżym w temacie. Ja po prostu mam tutaj inną radę: przyjmijcie, że nazwa nie ma znaczenia – wszystko rozchodzi się wyłącznie o zasymulowanie zachowania obiektu w celu przetestowania interakcji z nim. I tyle.

Kończąc temat: jestem świadom tego że nawet Fowler pisał “Mocks Aren’t Stubs“. Ale zostaję przy swoim: nie ma to znaczenia. I kogo to obchodzi? Podobnie jak i w innych aspektach życia… na moim poziomie abstrakcji, w kontekście takiego na przykład kredytu hipotecznego, nie ma znaczenia czy zwiększył się euribor (nie wiem nawet co to), marża, kurs walut czy oprocentowanie kredytu i czy jest to spowodowane kryzysem w Grecji, kryzysem w USA czy lądowaniem Marsjan na księżycu. Dla mnie liczy się to, że wzrosła rata kredytu. Szczegóły tylko zajmowałyby mi mój prywatny RAM i zostawiam je panom z tefałensienbisibiznes.

Kwestia 2: FakeItEasy

Poprzednia notka (jak i zapewne inne przykłady na moim blogu) pokazywała jak skorzystać z Moq. Wspomniałem tam mrukliwie o fakeiteasy, ale kodem nie zaświeciłem.

Przez ostatnie kilka miesięcy zaznajomiłem się z fakeiteasy dość dobrze i aktualnie to właśnie ta biblioteka jest moim wyborem, jeśli chodzi o generowanie mocków.

Poniżej kod analogiczny do klasy CalcTest_moq z ostatniego razu:

  1:  public class CalcTest_fakeiteasy
  2:  {
  3:      private RemoteEuroCalculator _calc;
  4:      private ICurrencyRateSource _currencyRateSource;
  5:  
  6:      public CalcTest_fakeiteasy()
  7:      {
  8:          _currencyRateSource = A.Fake<ICurrencyRateSource>();
  9:  
 10:          _calc = new RemoteEuroCalculator(_currencyRateSource);
 11:      }
 12:  
 13:      [Fact]
 14:      public void Calculates_Pln()
 15:      {
 16:          decimal input = 50.0m;
 17:          decimal euroRate = 4.35m;
 18:  
 19:          A.CallTo(() => _currencyRateSource.GetEuroRate())
 20:              .Returns(euroRate);
 21:  
 22:          decimal correctResult = 217.5m;
 23:  
 24:          decimal result = _calc.Calculate(input);
 25:  
 26:          Assert.Equal(correctResult, result);
 27:      }
 28:  }

Różnice widać na pierwszy rzut oka: tutaj cały czas operujemy bezpośrednio na implementacji interfejsu ICurrencyRateSource. Takie podejście odpowiada mi o wiele bardziej niż charakterystyczne dla Moq Mock<ICurrencyRateSource>. Definiowanie zachowań (ich weryfikacja zresztą także) z kolei odbywa się poprzez przekazanie odpowiedniej lambdy do metody A.CallTo() – więc unikamy znanych z Rhino Mocks metod rozszerzających zaśmiecających API wszystkich naszych obiektów.

Funkcjonalnie wszystkie te biblioteki są prawdopodobnie takie same… więc właściwie wybór jednej czy drugiej sprowadza się do osobistych preferencji.

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
BP
BP

Taka uwaga nie związana z tematem: miło by było jakby zaznaczanie w polu z kodem nie zaznaczało też numerów linii (może ktoś chciałby to skopiować? :)

pozdrawiam

xyz
xyz

Nie rozumiem tego przykładu. Co to jest "A"? Co dokładnie dzieje się w 19tej linijce? [ jeśli pytanie jest za głupie zrozumiem brak odp :) ]

Rayu
Rayu

Nie ma głupich pytań :)
A – to z pewnością jakaś klasa statyczna frameworka fakeiteasy (to tak nie zagłebijąc się co to jest w ogóle). Natomiast w 19 linii mówimy frameworkowi od testów, że: "A.CallTo(() =>" – gdy wywołana zostanie metoda "_currencyRateSource.GetEuroRate()" na zmockowanym obiekcie udaj że zwraca ona wynik ".Returns(" o wartości "euroRate)". Bo jak możesz zauważyć w konstruktorze testu tworzony jest mock interfaceu ICurrencyRateSource i przekazywany przez konstruktor do klasy RemoteEuroCalculator

Tomek
Tomek

Użyłeś słowo "celeb" i od razu przypomniał mi się Caleb z Blood 1. Ostatnio ciągle mi się przypomina ta gra, hmh, dziwne… .

xyz
xyz

Aha, czyli normalnie w programie metoda _calc.Calculate(input) gdzieś w środku korzysta z _currencyRateSource.GetEuroRate() , ale tutaj w teście zwrócimy podmienioną wartość euroRate. Ok, teraz wydaje się to oczywiste, dzięki :)

Piotr Zielinski

A ja nadal będę się upierał przy terminologii:) Oczywiście nie jest to ważna sprawa. Ale to jest jak ze słownictwem w języku polskim, które warto znać chociaż nie zawsze z niego korzystamy a w codziennych dyskusjach zastępujemy je prostszymi, bardziej popularnymi i przyswajalnymi zwrotami.

Podobnie jest w programowaniu. Czy np. template method pattern albo memento dostarczają coś bardzo nowego? Prawdopodobnie wiele programistów z tego korzysta a nie zna "fachowej" nazwy na to bo nie ma to znaczenia. Bardziej chodzi tu o słownictwo po prostu…

Dobrze wiedzieć, ale oczywiście nie jest to jakoś super ważne – to jest mój tok myślenia :)

procent

BP,
Prawda… ale ten "koloryzator składni" pisałem kiedyś sam i wątpię, żebym znalazł czas i chęci na powrót do niego i zmianę działania:). Podobnie wątpię żebym zmienił sposób wstawiania kodu na blog.
Przyznam że czasami sam stąd coś kopiuję, wtedy bardzo pomocne jest "pionowe" zaznaczanie w visualu – shift + alt + selection. Zaznaczam tak numerki, usuwam i gra gitara.

procent

Rayu,
Dokładnie, dzięki za wyjaśnienie:)

xyz,
Tak, z tym że test nigdzie nie sprawdza, że dana metoda jest wywołana. On ją po prostu konfiguruje i testuje, że dla source zwracającego X otrzymamy wynik Y. Różnica jest nieznaczna i bardzo subtelna (w porównaniu z weryfikacją że wywołanie metody faktycznie nastąpiło), ale moim zdaniem warto na to zwrócić uwagę.

procent

Piotr Zielinski,
Spoko:). Chociaż moim zdaniem porównanie do wzorców projektowych nie jest do końca trafione. Wzorzec opisuje scenariusz, w którym można zastosować proponowane rozwiązanie. Wszystkie przytoczone przeze mnie terminy również… z tym że scenariusz wykorzystania tych terminów jest prawie taki sam – "symulacja zachowania zależności".
Jestem skłonny się zgodzić, że ta wiedza nie jest szkodliwa, ale polemizowałbym czy "warta posiadania".

Piotr Zielinski

percent:
Ale różnica Mock vs Stub również definiuje scenariusz użycia – to właśnie w zależności od konkretnego scenariusza i sposobu testowania, nazwa się zmienia. Ponadto dwie metodyki state verification i behaviour testing są bardzo różne…

procent

Piotr Zielinski,
Napisałbym odwrotnie – to scenariusz użycia definiuje czy mock czy stub. I jest to szczegół, którym moim zdaniem nie ma sensu się zajmować.
Co do różnych metodyk testowania… w sumie mój stosunek jest podobny, czyli: liczy się efekt. Przygotowuję środowisko, wykonuję testowaną instrukcję i badam wynik. A czy to podpadnie pod ‘state verification’, ‘behaviour testing’ czy jeszcze coś innego – nic mi taka wiedza nie daje.
Moje programistyczne życie stało się o wiele prostsze, a praca o wiele efektywniejsza, od kiedy przestałem wnikać w subtelne różnice w definicjach/terminologiach a zacząłem skupiać wyłącznie na rezultatach jakie chcę osiągnąć.

procent

Piotr,
Jeszcze jedna myśl przyszła mi do głowy… Warto interesować się dokładną terminologią i ją stosować wtedy, gdy może wystąpić sytuacja, w której podczas dyskusji z innym programistą rozróżnienie ma znaczenie. O ile w przypadku wzorców projektowych jak najbardziej potrafię sobie taką sytuację wyobrazić, to już w kontekście mocków ani razu nie spotkałem się ze scenariuszem, gdzie stwierdzenia "zastosowałem mock" zamiast "zastosowałem stub" (lub na odwrót) w jakikolwiek sposób zmieniłoby kierunek rozmowy.

Piotr Zielinski

procent:
W programowaniu mówię, użyłem momento gdy mam na myśli metodę typu LoadState. W unit testing mówię mock lub stub aby jednoznacznie zidentyfikować czy użyliśmy weryfikacji stanu czy po prostu sprawdzaliśmy jakie metody zostały wywołane. Identycznie jak z wzorcami projektowymi – opisują pewną konstrukcję programistyczną, która dla końcowego usera nie ma znaczenia – rozumie ją tylko programista.
Myślę, że powoli wchodzimy już w filozofię i różną interpretację, więc let’s agree to disagree :)

procent

Piotr,
deal :)

Moja książka

Facebook

Zobacz również