Blog Dla Młodszych Programistów C#/.NET

wtorek, 7 kwietnia 2020
Test Driven Development w skrócie TDD, czyli tworzenie oprogramowania sterowane testami. To znaczy, tak jakby testy sterują naszym kodem, który dopiero będziemy pisać. Myślę, że TDD obiło się o uszy każdemu programiście, lecz mało z nich próbowało pisać oprogramowanie, stosując to podejście. Aby zacząć pisać zgodnie z TDD, najpierw trzeba nauczyć się pisać testy jednostkowe, jeżeli nie pisałeś wcześniej żadnych testów, to spróbuj najpierw zapoznać się z artykułem na temat testów jednostkowych, a następnie wróć do tego tematu :) Są dwie techniki pisania testów jednostkowych. Możesz pisać najpierw kod, a następnie równolegle testy do tego kodu, a także możesz również pisać najpierw test, a dopiero później kod. Ta druga technika to właśnie Test Driven Development.

Test Driven Development: Korzyści ze stosowania TDD na przykładzie w .NET


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


Test Driven Development cykl red green refactor

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#.
Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
CTO & Founder - FindSolution.pl
Programista C#/.NET. Specjalizuje się w Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Komentarze (1)
Agile Coach
AGILE COACH, piątek, 10 grudnia 2021 19:18
Dziwi mnie jedynie fakt, że aż tak wielu developerów wciąż uparcie nie chce stosować tdd... :(
Dodaj komentarz

Wyszukiwarka

© Copyright 2024 modestprogrammer.pl. Wszelkie prawa zastrzeżone. Regulamin. Polityka prywatności. Design by Kazimierz Szpin