Kurs TDD cz. 4: Nasz pierwszy test jednostkowy

W tej części cyklu stworzymy nasz pierwszy test jednostkowy. Przedstawię krok po kroku jak napisać i przetestować prostą funkcjonalność wedle zasad TDD. Opiszę tutaj szczegółowo wszystkie kroki, począwszy od tego jak dodać referencję do NUnita, a skończywszy na tym jak uruchomić test.

Zachęcam też do odwiedzenia ostatniej części, która opisuje strukturę kodu jednostkowego Arrange-Act-Assert. Zachęcam tym bardziej, że rozszerzyłem tę część o garść istotnych informacji.

Za przykład posłuży nam klasa kalkulatora i metoda służąca do dodawania dwóch liczb. Przyjmijmy, że wymagania precyzują, że metoda ta przyjmuje dwa parametry wejściowe typu int i zwraca wartość też typu int. Nasza funkcjonalność wygląda w kodzie następująco:

public class Calculator
{
	public int Add(int a, int b)
	{
		throw new NotImplementedException();
	}
}

Pamiętamy, że w Test-Driven Development testy piszemy w kroku pierwszym; dopiero później implementujemy kod i refaktoryzujemy.

Ruszamy!

Dodajemy solucję i projekty

Pierwszą czynnością po uruchomieniu Visual Studio jest utworzenie nowej solucji, dodanie projektów i zalążków naszych klas:

  1. Tworzymy nową solucję z projektem: File > New > Project… > Class Library o nazwie: Calculator.
  2. Klasę Class1.cs zastępujemy klasą Calculator i wklepujemy metodę Add, dokładnie taką jak powyżej.
  3. Dodajemy bibliotekę z testami do projektu: File > Add > New Project… > Class Library o nazwie: Calculator.Tests.
  4. Dodajemy do projektu Calculator.Tests klasę o nazwie CalculatorTests.
  5. Dodajemy referencję projektu Calculator do projektu Calculator.Tests. (prawy klik na projekt Calculator.Tests > Add Reference… > Projects > Calculator)

Zwróć uwagę na nazewnictwo projektu i klas. Nieformalna zasada mówi, że projekt z testami powinien mieć przyrostek .Tests, natomiast klasa zawierająca testy przyrostek Tests.

Jeśli wszystko poszło dobrze, całość powinna wyglądać tak:

tdd-4-1

Dodajemy NUnit

Kolejnym krokiem jest dodanie biblioteki NUnit do naszego projektu. Możemy to zrobić na dwa sposoby. Jednym z nich jest ściągnięcie DLL-ki z oficjalnej strony. Ten krok jest niezbędny dla tych, którzy nie mają zainstalowanego ReSharpera*. Drugim sposobem jest dodanie referencji przez NuGet. Jeśli NuGet jest Ci obcy, polecam mimo wszystko ten krok, gdyż jest bajecznie łatwy i sporo szybszy. Obydwa sposoby opisane są poniżej.

Komentarz: Pamiętaj, że prócz NUnita są też inne frameworki do testowania, np. MSTest dostarczany z Visual Studio lub xUnit.NET. NUnit jest jednak najpopularniejszy w swojej dziedzinie.

* Nie do końca niezbędny. Są też oczywiście inne dodatki pozwalające na uruchomienie testów, np. darmowy VsTestAdapter.

Metoda 1: Dodajemy referencję ręcznie

  1. NUnita pobieramy z oficjalnej strony: http://www.nunit.org/index.php?p=download.
    Wybieramy najnowszą stabilną wersję, opcję win jeśli chcemy instalkę lub opcję bin jeśli interesuje nas archiwum zip.
  2. W Solution Explorer klikamy prawym przyciskiem myszy na projekt Calculator.Tests i wybieramy Add Reference…
  3. W oknie Reference Manager klikamy na Browse i wskazujemy na DLL-kę nunit.framework.dll (w katalogu NUnit: \bin\framework\).

Metoda 2: Dodajemy referencję przez NuGet

  1. Odpalamy NuGet w Visual Studio: Tools > Library Package Manager > Package Manager Console.
  2. W oknie Package Manager Console ustawiamy domyślny projekt (Default project) CalculatorDemo.Tests.
  3. W tym samym oknie wpisujemy Install-Package NUnit.
  4. Jeśli wszystko się powiedzie, to dostaniemy komunikat: Successfully added ‘NUnit 2.6.2’ to CalculatorDemo.Tests.

Etap red: Piszemy testy

Pierwszym etapem cyklu red-green-refactor jest faza red, czyli napisanie testów do jeszcze nieistniejącej funkcjonalności. Przed napisaniem testów jednostkowych musimy zastanowić się m.in. nad wszystkimi sytuacjami wyjątkowymi oraz wartościami brzegowymi. Nasz przypadek dodawania jest ekstremalnie łatwy. Nie oczekujemy wystąpienia wyjątku, nie mamy do czynienia z wartościami NULL ani pustymi stringami, nie mamy też zdarzeń, typów generycznych ani zależności do innych klas. Jakie przypadki możemy więc sprawdzić? Jako, że dodajemy do siebie dwie liczby typu int, możemy zrobić testy jednostkowe do następujących przypadków:

  • dodanie dwóch liczb dodatnich, np. 2 + 2 = 4,
  • dodanie liczby dodatniej do ujemnej, np. 2 + (-3) = -1,
  • dodanie liczby ujemnej do dodatniej, np. -2 + 3 = 1,
  • dodanie dwóch liczb ujemnych, np. -2 + (-2) = -4.

Taki zestaw testów zostanie przygotowany do naszej metody Add. Jeśli wszystkie testy przejdą pozytywnie, uznamy że nasza funkcjonalność napisana jest poprawnie.

Każdy test jednostkowy napisany w NUnit z punktu widzenia technicznego charakteryzuje się:

  1. Test jednostkowy to publiczna metoda void z atrybutem [Test].
  2. Klasa zawierająca testy powinna mieć atrybut [TestFixture] i musi być publiczna.

I tyle! Bierzemy się zatem do napisania pierwszego testu jednostkowego. Dodajemy atrybut [TestFixture] do klasy CalculatorTests i piszemy pierwszą metodę. Jaka jest konwencja nazewnictwa w świecie TDD? Jest wiele szkół nazewnictwa, najważniejsze jednak żeby nazwa testu zawierała w sobie informacje:

  • Co testujemy i jaki jest stan testu, oraz
  • Jaki jest oczekiwany rezultat.

Przykładowo, do parametrów metody CheckPassword, wprowadzamy poprawną nazwę użytkownika i hasło; oczekujemy, że metoda wróci w takim przypadku wartość true. Dla takiego przypadku nazwa testu jednostkowego może być następująca: CheckPassword_ValidUserAndPassword_ReturnTrue.

Spójrzmy na początek tego rozdziału i cztery przypadki do naszej metody. Pamiętając o tym, że jeden test jednostkowy testuje jedną funkcjonalność, musimy stworzyć cztery testy jednostkowe, a co za tym idzie cztery metody.

Reasumując całą dotychczasową wiedzę z naszego kursu, pierwszy test, test dwóch liczb dodatnich, będzie wyglądać następująco:

[Test]
public void Add_AddsTwoPositiveNumbers_Calculated()
{
	var calc = new Calculator();
	int sum = calc.Add(2, 2);
	Assert.AreEqual(4, sum);
}

Nazwa naszej klasy mówi nam o tym:

  • Co testujemy (Add) — testujemy metodę Add
  • Co robimy (AddsTwoPositiveNumbers) — dodajemy do siebie dwie liczby dodatnie
  • Czego oczekujemy (Calculated) — oczekujemy, że wartości zostaną wyliczone (prawidłowo)

W powyższym przykładzie inicjalizujemy obiekt klasy Calculator. Następnie dodajemy dwie liczby i przypisujemy rezultat do zmiennej o nazwie sum. Najważniejszym krokiem jest jednak ostatnia linijka, w której sprawdzamy czy wartość tej sumy odpowiada wartości oczekiwanej.

Kolejne trzy testy jednostkowe możemy “napisać” metodą copy-paste, zmieniając nazwę testu, parametry wejściowe metody oraz wartość oczekiwaną. Zaraz, ale co z czystym kodem (możemy przypisać wcześniej wartości zmiennych, np. int expected = 4), duplikacją kodu (możemy wyrzucić zmienną calc do składowych klasy), itd.?

Otóż świat TDD ma trochę inne prawa niż kod produkcyjny. Dozwolone jest nieprzypisywanie zmiennych (testujemy czy 2 + 2 = 4, nie potrzeba nam więc przypisywać te wartości do zmiennych). Testy jednostkowe powinny być atomiczne i zawierać jak najmniej elementów mogących spowodować bugi w… tak, tak — teście; dozwolona jest więc w naszym teście jednostkowym duplikacja kodu, unikać powinno się takich elementów jak instrukcje warunkowe, pętle, dziedziczenie. Oczywiście, możemy znaleźć następstwa od tej reguły, takie jak obszerny setup metody z wieloma zależnościami, niemniej jednak należy trzymać się ww. reguł w codziennym życiu z testami.

Mamy zatem cztery metody z niemal identycznym kodem, dlaczego więc nie użyć jednej metody Add_AddsTwoNumbers_Calculated() i zawrzeć w niej czterech asertów, jeden za drugim?:

Assert.AreEqual(4, calc.Add(2, 2));
Assert.AreEqual(-1, calc.Add(2, -3));
Assert.AreEqual(1, calc.Add(-2, 3));
Assert.AreEqual(-4, calc.Add(-2, -2));

Takie rozwiązanie ma bardzo dużą wadę: Jeśli którakolwiek z tych asercji nie zostanie spełniona, cały test jednostkowy będzie czerwony. O ile NUnit pozwala sprawdzić, która asercja nie została spełniona i dlaczego, nam nie zależy na informacji która asercja została niespełniona, ale czy cały test się powiódł lub nie. Innymi słowy, musimy na podstawie nazwy testu wywnioskować, co poszło nie tak w naszym teście. Testując wiele elementów w jednym teście, nie jesteśmy jednoznacznie stwierdzić co poszło nie tak. Stąd też kluczowa reguła TDD mówiąca o tym, że powinniśmy testować zawsze jedną rzecz. Bardzo ważne są też nazwy naszych testów, przybierające z tych powodów niekiedy kosmicznie długie rozmiary.

Jak uruchomić testy?

Jeśli mamy ReSharpera, możemy to zrobić na dwa różne sposoby:

  1. Klikamy prawym przyciskiem myszy na projekt Calculator.Tests i wybieramy Run Unit Tests, lub
  2. Klikamy w edytorze kodu na zielonożółte kółko przy teście i wybieramy Run.

Po uruchomieniu testów wszystkie testy będą czerwone:

tdd-4-2Jeśli nie mamy ReSharpera, budujemy naszą solucję, a następnie uruchamiamy nunit.exe z katalogu bin NUnita. Tam, w okienku wyboru projektu (File > Open Project) wybieramy skompilowaną dll-kę Calculator.Tests.dll. W wyniku czego dostaniemy takie okienko:

tdd-4-3

Mamy napisane wszystkie testy, po uruchomieniu świecą się na czerwono. Przechodzimy do kolejnego etapu—napisania naszej logiki biznesowej.

Etap green: Implementacja kodu

W etapie green piszemy wreszcie nasz kod. W naszym przypadku rocket science to nie jest, do naszej metody Add wrzucamy

return a + b

Po uruchomieniu testów, wszystkie testy zaświeciły się na zielono:

tdd-4-4Etap ostatni: Refaktoryzacja kodu

Nasz przykład jest tak prosty, że nie potrzebujemy nic ulepszać. Pamiętajmy jedynie, że ten etap jest tak samo ważny co dwa pozostałe. Po dokończonej refaktoryzacji, odpalamy na nowo wszystkie testy i sprawdzamy czy nasza refaktoryzacja nie wprowadziła błędu. Nie dotyczy to naszego przypadku, gdyż nic nie refaktoryzowaliśmy.

Zakończenie

W podanym przykładzie chciałem skupić się głównie na pierwszym zetknięciu z NUnitem. Całość opisałem w najprostszych krokach w taki sposób, aby osoba początkująca z Visual Studio czy C# mogła swobodnie napisać kod wg TDD i uruchomić testy. Z tego względu wybrałem bardzo prosty przypadek – dodawanie dwóch liczb całkowitych.

W następnej części przejdziemy do testowania innej funkcjonalności – dzielenia. Dzięki temu przyjrzymy się trochę innym aspektom TDD. Będziemy musieli przyjrzeć się przypadkowi dzielenia przez zero (oczekujemy na wystąpienie wyjątku), jak i operacjom zmiennoprzecinkowym. Zachęcam do zrobienia takiego przykładu w domu! :)

18 thoughts on “Kurs TDD cz. 4: Nasz pierwszy test jednostkowy

  1. Pingback: Kurs TDD część 5: Nasz drugi test jednostkowy | DariuszWozniak.NET
  2. Napisałeś: “Nieformalna zasada mówi, że projekt z testami powinien mieć przyrostek .Tests, natomiast klasa zawierająca testy przyrostek Test.”
    Na zrzucie ekranowym w projekcie Calculator.Tests widać klasę CalculatorTests.cs – jak rozumiem, to jest klasa zawierająca testy? Jeśli tak, to czy zgodnie z wymienioną zasadą nie powinna nazywać się CalculatorTest.cs (końcówka Test, a nie Tests) ?

  3. Najpierwsz piszesz 4 testy, a dopiero potem implementacja? Po każdym jednym teście powinna nastąpić implementacja kodu. Dodatkowo to można było zaimplementować jako jeden test, ale wykorzystując atrybut TestCase NUnit-a.

    • Najpierw definiuję zestaw warunków dla metody, później przechodzę do implementacji. Nie widzę sensu dlaczego miałbym pisać implementację po każdym przypadku testowym.
      Testy parametryzowane są w części 5.

      • Może w tym przypadku sensu nie ma, ale w bardziej skomplikowanych tak. Jeden test, kod który spełnia ten test. Drugi test, modyfikacja starego kodu/dodanie nowego/przepisanie. Idea jest taka, że powinniśmy inkrementalnie dojść do ostatecznej wersji kodu. Oczywiście w przypadku tego kalkulatora to jest zbędne.

  4. Nie wiem, czy nie powinno brakować nagłówka –> using NUnit.Framework;
    Bez tego wywalało mi masę błedów uzywając Visual Studio 2015. Pozdrawiam

  5. I moved to Salt Lake City with about 4000 paper books, but I’m now down to under 500 and most of them are specialized research not available on Kindle. But besides that, I have Pai8knson&#r217;s, and I tend to drop paper books when I am turning the pages. I don’t drop the Kindle (thank goodness!) so it’s much easier for me to read from. Meanwhile my husband, who can’t get the hang of Kindle except the one on his computer desktop, has added so many paper books that the garden shed is full of them (in plastic tubs) and the handyman had to build a second garden shed to hold the garden stuff.

  6. Mam pytanie do praktyków, jako nowa osoba w świecie testów C#, czemu nie używa się wbudowanego MSTest, łatwo się dodaje nowe testy i szybko uruchamia. Czy ktoś może wskazać czemu Nunit jest popularniejszy niż domyślne środowisko MS, ma jakieś ograniczenia których Nunit nie posiada?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s