Zalety stosowania TDD
Skupiamy się na kodzie lepszej jakości. Dzięki temu, że najpierw został napisany test, mamy pewność, że kod będzie testowalny :) Co za tym idzie, będzie stosował się do dobrych praktyk programowania. Dzięki wymaganej w każdym cyklu refaktoryzacji będziemy mogli na bieżąco go poprawiać.
Jeżeli najpierw piszemy kod, a następnie do tego kodu implementujemy testy, to zazwyczaj pomijamy fazę red, to znaczy nie mamy testów, które nie przechodzą. Dobrą praktyką jest wtedy po dodaniu testu, zakomentować chwilowo linię kodu, którą testujemy. Jeżeli po zakomentowaniu tego fragmentu, test nie przechodzi, to oznacza, że testujemy prawidłową rzecz. Następnie trzeba odkomentować ten fragment kodu. Stosując TDD, ten etap możesz pominąć, pisząc według cyklu TDD, zawsze masz pewność, że testujesz dobrą rzecz.
Dzięki TDD, mamy szersze spojrzenie na kod, zanim przejdziemy do faktycznej implementacji. Skupiamy się na dokładnym zrozumieniu wszystkich wymagań. Dzięki temu jesteśmy, w stanie przewidzieć więcej przypadków testowych, które trzeba sprawdzić.
Niestety nie unikniemy wszystkich błędów, ale na pewno będzie ich mniej w naszym kodzie produkcyjnym, dodatkowo skrócimy całkowity czas tworzenie oprogramowania.
Cykl TDD
Podejście TDD składa się z trzech etapów, które nazywa się Red-Green-Refactor.
Implementacje zaczynamy od fazy red, jest to faza, w której piszemy pierwszy test i jego wynik powinien być czerwony, to znaczy test nie powinien przechodzić. Dodatkowo często nawet po tej fazie, kod nie będzie się kompilował, ponieważ możesz używać w teście klas, których jeszcze nie ma.
Drugi etap, czyli green, to implementowanie kodu, do naszego testu. Tutaj trzeba zwrócić uwagę, na to, że implementujemy najprostszy kod, który spełnia wymagania naszego obecnego testu. Po implementacji kodu test powinien być pozytywny, czyli zielony.
Ostatnim etapem jest refaktoryzacja kodu, ewentualnie testów, tak żeby kod dalej pozostał zielony. Musisz pamiętać o tym etapie, tak aby kod był, jak najwyższej jakości.
Następnie te cykle są powtarzane, w celu implementowania kolejnych funkcji w naszej aplikacji.
Przykład
Wróćmy do popularnego algorytmu FizzBuzz, do którego w jednym z poprzednich artykułów pisaliśmy testy jednostkowe. Tym razem, spróbujmy napisać testy jednostkowe metodą TDD, czyli zaczniemy od testu. Dla przypomnienia taki algorytm FizzBuzz musi spełniać następujące zasady:
-Jeżeli argument jest podzielny przez 3, zwraca Fizz.
-Jeżeli argument jest podzielny przez 5, zwraca Buzz.
-Jeżeli argument jest podzielny przez 3 i przez 5, zwraca FizzBuzz.
-Jeżeli argument nie jest podzielny przez 3 ani przez 5, zwraca wartość argumentu.
Implementacja zgodnie z TDD
Zaczynamy od fazy red, to znaczy od napisania najpierw testu jednostkowego, może on wyglądać tak:
[Test]
public void GetOutput_WhenInputIsDivisibleOnlyBy3_ShouldReturnFizz()
{
var fizzbuzz = new FizzBuzz();
var result = fizzbuzz.GetOutput(3);
Assert.That(result, Is.EqualTo("Fizz"));
}
W tym momencie otrzymujemy błąd kompilacji, ponieważ klasa FizzBuzz jeszcze nie istnieje. Przechodzimy do kolejnej fazy green, po której test powinien być zielony. Tworzę klasę FizzBuzz. Następnie metodę GetOutput, która zwraca stringa, a przyjmuje argument typu int. Dodaje najprostszą możliwą implementację, aby test był zielony. Tak wygląda moja metoda:
public string GetOutput(int number)
{
return "Fizz";
}
Czyli w obecnym momencie jest to najprostsza implementacja, która spełnia moje testy, przechodzimy do kolejnego etapu refactor. Akurat w tym momencie kod jest dość prosty, nie musimy tutaj nic refaktoryzować. Zakończyliśmy nasz pierwszy cykl TDD. Przechodzimy do kolejnego cyklu. Dodajemy nowy test, który będzie najpierw czerwony. Tak wygląda test, w którym sprawdzamy nasze drugie założenie, to znaczy argument podzielny przez 5 ma zwracać Buzz.
[Test]
public void GetOutput_WhenInputIsDivisibleOnlyBy5_ShouldReturnBuzz()
{
var fizzbuzz = new FizzBuzz();
var result = fizzbuzz.GetOutput(5);
Assert.That(result, Is.EqualTo("Buzz"));
}
Kolejny etap green, dodajemy najprostszy kod, które będzie spełniał te wymagania.
public string GetOutput(int number)
{
if (number % 5 == 0)
return "Buzz";
else
return "Fizz";
}
Kod spełnia wymagania, przechodzimy do kolejnego etapu refaktoryzacji. Możemy w tym etapie trochę poprawić ten kod. Możemy pozbyć się else z tego warunku.
public string GetOutput(int number)
{
if (number % 5 == 0)
return "Buzz";
return "Fizz";
}
Rozpoczynamy kolejny cykl TDD, dodajemy kolejny test, zaczynamy znowu od red.
[Test]
public void GetOutput_WhenInputIsDivisibleBy3And5_ShouldReturnFizzBuzz()
{
var fizzbuzz = new FizzBuzz();
var result = fizzbuzz.GetOutput(15);
Assert.That(result, Is.EqualTo("FizzBuzz"));
}
Następnie dodajemy najprostszą implementację.
public string GetOutput(int number)
{
if ((number % 3 == 0) && (number % 5 == 0))
return "FizzBuzz";
if (number % 5 == 0)
return "Buzz";
return "Fizz";
}
Nie mamy nic do refaktoryzacji, dodajemy kolejny ostatni już test z naszych wymagań, to znaczy, jeżeli argument nie jest podzielny przez 3, ani przez 5 to zwracamy wartość argumentu. Najpierw test.
[Test]
public void GetOutput_WhenInputIsNotDivisibleBy3Or5_ShouldReturnInput()
{
var fizzbuzz = new FizzBuzz();
var result = fizzbuzz.GetOutput(1);
Assert.That(result, Is.EqualTo("1"));
}
Następnie kod, który będzie spełniał wszystkie testy.
public string GetOutput(int number)
{
if ((number % 3 == 0) && (number % 5 == 0))
return "FizzBuzz";
if (number % 3 == 0)
return "Fizz";
if (number % 5 == 0)
return "Buzz";
return number.ToString();
}
Tym sposobem, udało nam się spełnić wszystkie wymagania. Zaimplementowaliśmy algorytm FizzBuzz, stosując TDD.
PODSUMOWANIE:
Test Driven Development to ciekawa technika tworzenia oprogramowania, która dobrze stosowana polepszy jakość Twojego kodu. Jeżeli jesteś osobą początkującą, która nie pisze, lub nie umie jeszcze pisać testów jednostkowych, to najpierw zacznij od nauki testów, a dopiero później polecam Ci spróbować pisać zgodnie z TDD. Jeżeli już opanowałeś testy jednostkowe, to polecam Ci spróbować sobie, może na początek na jakimś mniejszym, pobocznym projekcie zastosować tę technika i zobaczysz czy to jest dla Ciebie odpowiednie :)
Poprzedni artykuł - Testujemy Operacje na Bazie Danych - Wprowadzenie do Testów Integracyjnych w .NET.
Następny artykuł - Czy Testować Jednostkowo Metody Prywatne - Przykłady w C#.