[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.