czwartek, 24 kwietnia 2008

[tdd] TDD - Test-Driven Development / NUnit. Krótkie wprowadzenie

Gdzieś w internecie znaleźliśmy coś co nas zainteresowało. Jest to dziwny skrót TDD. Poszperajmy trochę w internecie, by dowiedzieć się co to tak na prawdę jest. Pierwsza pozycja: tdd, hmm chyba nie o to nam chodziło, druga pozycja też nie, co jest? nie ma polskich stron o tym? Przechodzimy na szukanie zagranicznych materiałów tdd od razu lepiej, aż za dobrze. Nie chce nam się czytać tego, za długie! Przelećmy wzrokiem po tym. Wiemy już co oznacza skrót - Test-Driven Development, hmm bardzo ciekawe, czyli jest to wytwarzanie oprogramowania sterowane testami, bełkot ;) Przejdźmy może do przykładu.
Acha tutaj najważniejsza rzecz: przed przystąpieniem do pisania właściwego kodu, musimy zacząć od napisania testu, czyli jak chcemy sprawdzić czy metoda Dodaj jest dobrze napisana:


class Matematyka
{
public static int Dodaj(int a, int b)
{
return a + b;
}
}


a + b = c, prawda? czyli 2 + 3 = 5.

Ok, teraz napiszmy odejmowanie wykorzystując TDD.

(Ściągamy sobie z internetu NUnita, podpinamy dll'ke: nunit.framework.dll do naszego projektu)

Tworzymy nowy plik/klasę o nazwie: TestMatematyka

Świetnie! teraz możemy przygotować naszą klasę do testów.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace AplikacjaMatematyka
{
[TestFixture] // Informuje nunita, że w tej klasie znajdują się przypadki testowe
class TestMatematyka
{
[Test] // Informujemy nunita, że to jest klasa testowa
public void TestOdejmij()
{
}
}
}

No to teraz sedno: przewidujemy co dana metoda zwróci, czyli wiemy, że Odejmij będzie (uwaga!) odejmować 2 liczby od siebie ;) czyli jak mamy 3 - 2 = 1, no spoko żadne odkrycie.

Teraz musimy zapisać 3 - 2 = 1 w formie testu, ponieważ to wyrażenie będzie sprawdzać czy nasza metoda działa dobrze.

W NUnitcie, porównujemy wartości poprzez użycie metod z klasy Assert. By porównać 2 wartości posłużymy się metodą: Assert.AreEqual(); Napiszmy zatem test sprawdzający poprawność działania metody Odejmij (pamiętajmy, że jeszcze jej nie mamy zaimplementowanej! tym zajmiemy się później, trochę to dziwne na 1st rzut oka)


[Test]
public void TestOdejmij()
{
Assert.AreEqual(1, Matematyka.Odejmij(3, 2));
}

Oznacza to: oczekujemy wartości 1, odejmij 3 od 2

Skompilujmy projekt. BUM! Metoda Odejmij nie istnieje :) zatem, napiszmy ja, wiemy już z góry, ze potrzebujemy 2óch parametrów/zmiennych a i b, na wejściu mamy 3 i 2, musimy tak zrobić by wyszło 1. Nic trudnego prawda?


public static int Odejmij(int a, int b)
{
return a - b;
}

Kompilujemy. Udało się. Otwieramy NUnita, tworzymy nowy projekt, zapisujemy go KONIECZNIE tam gdzie się znajdują nasze dll'ki, bądz exe. Dołączamy exe bądź dll, gdzie znajduje się nasz test do naszego projektu i odpalamy. Wszystko jest ok, testy przeszły. Zróbmy teraz mały błąd w naszej metodzie: return a - b - 1; Kompilacja i testowanie. Test nie przeszedł. Co to oznacza? Oczywiście, to że wprowadziliśmy błąd do wyliczania odejmowania. Błąd był świadomy i wiemy o nim, lecz co jeśli błąd zrobimy NIEŚWIADOMIE? W jakimś miejscu w projekcie? Nie będziemy wiedzieli na dodatek w którym miejscu. Tutaj testy nam przychodzą z pomocą. Jeśli mamy otestowane metody w projekcie wtedy możemy mieć pewność, że wydanie nowej wersji nie będzie zawierało błędów. Brzmi fantastycznie. Lecz przecież wykonanie testu wiąże się z CZASEM. Hehe, ok, ale nie prościej napisać raz test a potem wywoływać go już ile razy chcemy i kiedy chcemy? bez ingerencji "ręcznego" testowania przez użytkownika? "Chyba" testowanie ręczne nam więcej zajmie czasu niż napisanie testu. Zatem mamy zysk czasu a nie stratę.

Ale poco pisaliśmy test na samym początku? Przecież można było zrobić to po napisaniu metody. Otóż właśnie że nie, w tym jest sęk. Jeśli ustalimy co mamy na wejściu a co chcemy mieć na wyjściu, to już będziemy wiedzieć jak sie zabrać do napisania interesującej nas metody. Przypadek z odejmowaniem był banalny, lecz co jeśli będziemy wyliczać jakieś skomplikowane biznesowe wskaźniki czy kto wie co jeszcze. Mamy dzięki temu wszystko sprecyzowane. Do tego tworzymy przy okazji bardzo dobrą dokumentację.

Korzyści z TDD:
-jest duża szansa, że kod będzie poprawny, mimo iż będziemy wprowadzać modyfikacje do kodu
-nie pozwalamy na powstanie CODE LEGACY
-bardzo szybko jesteśmy w stanie zdiagnozować błąd

Koszty... czy w ogóle są?
Ja korzystam dość długo już z TDD i cóż mogę powiedzieć o kosztach. "Jedynie" tyle, że one również muszą być poddawane refaktoryzacji, zmianom wraz gdy zmieniają się wymagania co dana metoda ma robić, trzeba je również konserwować i dbać o nie jak o normalny kod. Tym bardziej, że rozmiar kodu testowego będzie tak długi jak "normalny" kod.

Pytanie: Czy wszystkie metody muszę testować?

i tak i nie, zależy jak leży ;) A tak na serio to ZALEŻY od pewnych czynników, ale to już temat na inny post. Wg statystyk, jakie gdzieś wyczytałem, to jeśli kod jest pokryty testami w 60-80% to jest dobrze, ale nie daje głowy, że dobrze pamiętam. Z praktyki mogę powiedzieć jedno: im więcej tym szczelniej ale myślę, że to żadne odkrycie.

Więcej na ten temat w tutaj.

Kod źródłowy

4 komentarze:

Anonimowy pisze...

Myślę, że [TestFixture] to jednak atrybut, a nie właściwość :)

Laziers pisze...

bardzo słuszna uwaga, moje niedopatrzenie

Anonimowy pisze...

Bardzo ciekawy blog, tylko trochę ostatnio za mało piszesz :( Z niecierpliwością czekam na ciąg dalszy. Do boju!

Laziers pisze...

niestety na razie jestem zajęty nauką do certyfikatu, ale jak tylko go zdobędę na pewno będą się tutaj pojawiać posty. Termin ustaliłem sobie na połowę sierpnia, więc już niedługo.