[ten post jest częścią mojego minicyklu o testach, pełna lista postów: tutaj]
Programiści .NET nie mogą narzekać na brak narzędzi i bibliotek wspomagających pisanie testów jednostkowych. Zanim przejdziemy jednak do zerkania w ich kierunku, zobaczymy jak można samemu, bez zewnętrznych zależności, rozpocząć pisanie testów.
Testować będziemy taką banalną klaskę, której zadaniem jest obliczenie “ile złotych polskich dostanie polski hydraulik Waldek za przywiezione zza zachodniej granicy jełro“:
1: public class LocalEuroCalculator 2: { 3: public decimal Calculate(decimal srcEur, decimal euroRate) 4: { 5: return srcEur * euroRate; 6: } 7: }
Console application
Tak jest, nie pomyliłem się. Pierwszy przykładowy test jednostkowy zaimplementujemy w zwykłej aplikacji konsolowej, bez referencji do żadnych “testujących” bibliotek. Bo tak naprawdę co jest potrzebne do tego, aby mieć działający test jednostkowy? Po pierwsze – metodę testującą. I po drugie – coś, co tą metodę odpali i wyświetli rezultat jej działania.
No to proszę bardzo, oto metoda testująca, czyli nasz pierwszy test jednostkowy:
1: public class EuroCalculatorTests 2: { 3: public void Calculates_Pln() 4: { 5: decimal input = 50.0m; 6: decimal euroRate = 4.35m; 7: decimal correctResult = 217.5m; 8: 9: var calculator = new LocalEuroCalculator(); 10: var result = calculator.Calculate(input, euroRate); 11: 12: if (result != correctResult) 13: { 14: throw new Exception(string.Format("Expected {0} but actual result was {1}", correctResult, result)); 15: } 16: } 17: }
Jest git? Jest git.
(czytelników zastanawiających się nad tym, dlaczego w teście mam już wyliczoną wartość correctResult, a nie wyliczam jej na bieżąco, odsyłam do swojego posta sprzed półtora roku: “Jak nie pisać testów jednostkowych“)
No to teraz ten test trzeba uruchomić:
1: class Program 2: { 3: static void Main(string[] args) 4: { 5: var tests = new EuroCalculatorTests(); 6: 7: tests.Calculates_Pln(); 8: 9: Console.WriteLine("All tests passed"); 10: } 11: }
I już. Gdy test przechodzi, widzimy coś takiego:
A gdy nie – coś takiego:
Ale dobra, koniec zabawy, przecież nikt nie pisze testów w ten sposób.
MSTest
Jako pierwszą z bibliotek do testów wymieniam MSTest z jednego powodu… aby móc od razu napisać: odradzam. Jakieś pliki .testsettings, jakieś konfiguracje, jakieś wizardy… po to żeby uruchomić test? Pisanie testów powinno być proste, szybkie i przyjemne. Kiedyś dałem szansę temu rozwiązaniu i naprawdę chciałem z niego skorzystać w prawdziwym projekcie. Ale..
Zresztą, żeby nie kombinować, przekleję swoje wrażenia z posta “Programowanie przez eksplorację“:
“Przyznam, że do tej pory zawsze omijałem MS Test szerokim łukiem. Korzystałem albo z nUnit, albo z mbUnit. I w sumie nie wiedziałem dlaczego… przecież wbudowany w VS runner dla MSTest to naprawdę wielka zaleta, jeśli ktoś nie ma Resharpera. A czym takim niby może różnić się jedna biblioteka do testów jednostkowych od innej biblioteki do testów jednostkowych?
Ano… okazało się że może. Ludu mój ludu, nigdy więcej. Bardziej się tego chyba skomplikować nie da. Tu nie wystarczy dorzucić jednego i drugiego atrybutu żeby stworzyć test. Dodatkowo trzeba wypełnić jakąś chorą konfigurację, poczytać instrukcję… a ja chcę tylko odpalić test! Po TRZECH GODZINACH walki z atrybutem DeploymentItemAttribute, ustawianiem przeróżnych konfiguracji, męczenia się z tak banalnym zadaniem jak wykorzystanie zewnętrznego pliku w teście… powiedziałem sobie DOŚĆ. WIEM że to musi jakoś działać, WIEM że z pewnością wiele osób z tego korzysta, WIEM że dokumentacja na pewno jest poprawna… ale mimo to mi, zwykłemu kmiotowi, nie udało się zmusić tego szatańskiego pomiotu do wykonania odpowiednich zadań. Trzęsąc się z irytacji wróciłem do nUnit i po 5 minutach miałem wszystko działające tak jak chciałem, bez żadnego zbędnego wnikania w durne obejścia. Ale co się klawiaturze oberwało ciosów zadanych w furii, to się oberwało.”
Heh, self-quotation:).
Jedna uwaga – podkreślę jeszcze raz, że wielką, niekwestionowaną zaletą MSTest jest zintegrowany z Visual Studio runner. Pewnie gdybym nie miał Resharpera to patrzyłbym na to trochę inaczej… chociaż z drugiej strony, kiedyś bez niego żyłem, uruchamiałem testy nUnitowe nawet z VS Express i też wcale nie było źle.
nUnit, mbUnit
Skoro to co trzeba mieć za sobą mamy już za sobą, pora na właściwe narzędzia. Najpopularniejszą biblioteką do testów w światku .NET jest nUnit. Test z jej wykorzystaniem wygląda tak:
1: [TestFixture] 2: public class CalcTest_NUnit 3: { 4: private LocalEuroCalculator _calc; 5: 6: [SetUp] 7: public void CreateCalculator() 8: { 9: _calc = new LocalEuroCalculator(); 10: } 11: 12: [Test] 13: public void Calculates_Pln() 14: { 15: decimal input = 50.0m; 16: decimal euroRate = 4.35m; 17: decimal correctResult = 217.5m; 18: 19: decimal result = _calc.Calculate(input, euroRate); 20: 21: Assert.AreEqual(correctResult, result); 22: } 23: }
Dla mbUnit, alternatywnego rozwiązania, konstrukcja będzie identyczna.
Czyli jak widać – klasę z testami trzeba udekorować jednym atrybutem, metodę przygotowującą środowisko do testów – kolejnym atrybutem. Samą metodę testu – jeszcze innym atrybutem… Nie ma w tym tak naprawdę nic złego. Więc czy to koniec przedstawienia narzędzi?
Nie, bo…
xUnit
Sam od kilku miesięcy, gdy tylko mam wybór, sięgam po xUnit. Powód jest bardzo prosty: jeśli można uniknąć pisania nadmiarowego kodu to zawsze skorzystam z okazji. Popatrzmy:
1: public class CalcTest_XUnit 2: { 3: private LocalEuroCalculator _calc; 4: 5: public CalcTest_XUnit() 6: { 7: _calc = new LocalEuroCalculator(); 8: } 9: 10: [Fact] 11: public void Calculates_Pln() 12: { 13: decimal input = 50.0m; 14: decimal euroRate = 4.35m; 15: decimal correctResult = 217.5m; 16: 17: decimal result = _calc.Calculate(input, euroRate); 18: 19: Assert.Equal(correctResult, result); 20: } 21: }
Żadnego [TestFixture] – skoro klasa zawiera testy to znaczy że trzeba je uruchomić. Żadnego [SetUp] – w końcu od tego mamy konstruktory. Po prostu – oznaczamy test atrybutem [Fact] i tyle. Sweet. Bardzo polecam (nieprzekonanych zapraszam do lektury uzasadnienia powstania xUnit).
A najfajniejsze jest to, że możemy sobie dowolnie te biblioteki łączyć i mieszać, a kochany Resharper i tak będzie potrafił nam je uruchomić (po dodaniu odpowiednich wtyczek):
Hint: pod tym adresem można znaleźć projekt wykorzystywany przeze mnie podczas prezentacji o testach. Stamtąd pochodzi kod zawarty w tym poście.
Jak dla mnie głupie jest to, że ten tekst nie kierujesz do czytelników tylko lam, która nigdy nie widziały unita. Moja ocena w skali 1-10 to 2, bo dałeś link zwracający uwagę na to, by nie odtwarzać sposobu obliczeń po stronie testu.
Ale mam kilka pytań.
Zastanawia mnie jak to się dzieje, że xUnit wie, że ma przetestować akurat metodę: Calculates_Pln. Czy w C# da się pobrać wszystkie metody klasy nie znając wcześniej jej nazwy? Coś na wzór takiego foreach, w którym do filtrowania używa się adnotacji, aby wyselekcjonować metody test i kolejno je odpalić?
Bardzo dziwne jest to, że tak się zakręciłeś podczas użycia MSTest. W życiu nie widziałem żadnego mstestowego wizarda, konfiguracji ani dokumentacji. Klasę z testami dekorujesz atrybutem [TestClass], metodę dekorujesz [TestMethod] i odpalasz. Dokładnie jak w nUnit, a nawet prościej, bo zakładając nowy podprojekt do testów, referencje dodają Ci się automatycznie.
Galaktyczny_JOE,
Nie tyle xUnit wie, co test runnery wiedzą. Prawdopodobnie pobierają z AppDomain wszystkie typy, z nich wszystkie metody, a potem filtrują po atrybutach.
_ernie,
Jest przecież specjalny plik konfiguracyjny definiujący zachowanie testów. Domyślnie, dla większości testów, nie trzeba go ruszać, ale ja musiałem. I poległem. Prawdopodobnie po kolejnych kilku godzinach osiągnąłbym zamierzony efekt, ale szkoda było mi tracić więcej czasu.
A kontekst opisałem: chciałem wykorzystać zewnętrzne pliki w testach. Przy testach całkowicie oderwanych od środowiska domyślne ustawienia działają.
@Galaktyczny_JOE
Uzupełniając wypowiedź Macieja http://stackoverflow.com/questions/3467765/get-method-details-using-reflection-and-decorated-attribute/3467796#3467796
@Procent
Jak dla mnie dobrze robisz. Żeby dojść do szczegółów i poprawnych testów trzeba przebrnąć przez podstawy. Tym bardziej jak dorzucasz garść spostrzeżeń praktycznych.
A najfajniejsze jest to, że możemy sobie dowolnie te biblioteki łączyć i mieszać
A co z konsekwencjami takiego mieszania :) Jeżeli mamy build serwer, tam już nie będzie Resharper’a tylko będziemy musieli w jakiś karkołomny sposób skonfigurować np. nUnit’a z xUnit’em. To samo dotyczy ewentualnego obliczania pokrycia kodu.
Maks,
Mieszanie nUnita z xUnitem byłoby trochę bez sensu – tutaj bardziej chciałem pokazać "fajność" Resharpera:).
Ale już mix xUnit+MSpec (o którym kiedy indziej) to zupełnie inna para kaloszy. Wykonanie testów to kwestia wywołania pliku builda z odpowiednim parametrem (tutaj na samym dole moje eksperymenty z rake w tym temacie: https://github.com/maniserowicz/ProcentCqrs/blob/master/rakefile.rb ).
Co do obliczania pokrycia kodu – interesująca uwaga, nie polemizuję, nigdy tego nie robiłem:). W sumie ciekawe jak taki np. dotCover radzi sobie w takiej sytuacji.
No, ja akurat jestem taka lama ktora nie widziala unita na oczy i bardzo mmi sie ten post podoba :). Czekam na kolejne czesci…
Poleć czasem jakąś literaturę, bo programowanie to zabawa w domu ;) Czytałem ostatnio Art Of Unit Testing ale troche wybrakowana. Teraz widzę dużo o testach i TDD w Pro ASP.NET MVC 3 Framework, z uwzględnieniem NUnita i Dependency Injection – polecam, ale ciekawy jestem czy jest jakaś czarna księga testów?
Wojtek,
Ja czytałem jedną książkę na temat testów, na samym początku swojej przygody (2007r.): "Pragmatic Unit Testing in C# with NUnit" ( http://www.amazon.com/Pragmatic-Unit-Testing-NUnit-Programmers/dp/0974514020 ). Nie potrafię niestety powiedzieć czy coś mi ona dała, bo w międzyczasie pochłonąłem tysiące artykułów, postów na blogach, sampli…
Podobno to jest "biblia tdd": Kent Beck "Test Driven Development: By Example"
@procent Z MSpec nie mam żadnego doświadczenia, muszę doczytać co to właściwie jest :)
Natomiast wiem, że NCover pozwala generować raporty pokrycia z kilku różnych plików.
@procent, @wojtek
Test Driven Development by example jest gorsze od The Art Of Unit Testing. TDD by example pokazuje po prostu podejscie TDD a nie [i]unit testing[/i] jako tako. za pomoca przykladow Kent prowadzi czytelnika od napisania testu poprzez uzupelnienie kodu by test sie wykonywal, optymalizacje testu i w koncu poprawna implementacje metody. czyli pisze When_5_is_added_to_5_10_should_be_returned i nastepnie dopiero tworzy kod. nie ma tam mockow, stubow zbytnio z tego co pamietam. Zas Roy juz opisuje metody i sposoby testowania.
jezeli wiec ktos chce ksiazke wprowadzajaca z duza liczba przykladow polecam The Art Of Unit Testing
Gutek,
Dzięki za info, wnioskując z Twojego opisu obie książki są godne polecenia:)
To ja dodam, że na TechEdzie zapowiedzieli, że w następnej wersji Visual Studio ma się pojawić integracja dla uruchamiania testów NUnita i chyba xUnita.
Slawek,
O to by było bardzo fajne. Tylko wtedy równie dobrze mogą ubić MSTest:).
Co do pokrycia kodu testami: NCover i dotCover (brak doświadczenia z PartCover) udostępniają m.in. aplikację konsolową której jedną z funkcjonalności jest stworzenie pliku coverage, kolejna funkcjonalność to merge. Więc z kilku niezależnych źródeł można zrobić raport (a chyba o to chodzi).
@Procent Pisałeś kiedyś o TeamCity. TC Zawiera dotCover po stronie serwera, można pobrać (za pomocą wtyczki) raport i bezpośrednio w VS przeglądać pokrycie kodu testami. Wymaga klienta dotCover (niestety płatny). Jest wersja trial 30 dni do potestowania….
maczek,
Dzięki za info, dobrze wiedzieć.
Sprawdziłem NCover.
Pomaga w pisaniu testów, wskazuje pokrycie oraz podaje, które elementy nie są testowane (nie wywoływane w testach).
Wczoraj dzięki programikowi zrobiłem 6 testów sprawdzających wszystkie warianty pewnych request-handlerów, które do tej pory miały 0 pokrycie. Pokrycie 100% wykonanie kodu właściwie na poziomie 1. Dla innych kawałków kodu pokrycie dość wysokie, ale testów o wiele za dużo! A utrzymanie testów kosztuje. W sumie warto używać, szkoda, że nie darmowe :|
Pozdrawiam
@pawelek
Niekoniecznie trzeba płacić. NCover Explorer+NCover Community Edition i wszystko gra.
NCoverExplorer – http://www.kiwidude.com/dotnet/DownloadPage.html
NCover Community Edition – http://www.ncover.com/download/current
Używam w pracy – niech szef płaci, gościom co to piszą też się kasa należy :) A w domu pisze w Javie :D
Pozdrawiam