piątek, 2 grudnia 2011

TDD cz.1


Może na wstępie kilka słów o TDD. TDD to technika tworzenia oprogramowania zaliczana do metodyk zwinnych. Od tradycyjnego sposobu wytwarzania oprogramowania odróżnia ją przede wszystkim cykl programowania.


Tradycyjny cykl programowania

Taki sposób tworzenia oprogramowania obciążony jest kilkoma wadami:

  • Testowanie
    • Testerzy testują kod nie znając go zbyt dobrze
    • Brak zaangażowania testerów
    • ­­­­­Na czas testowania kod jest zamrożony
    • Naprawa błędu po zakończeniu implementacji jest zazwyczaj 2-krotnie wyższa niż w jej trakcie
  • Utrzymanie
    • Testy „po” sprawdzają wybiórczo kod
    • Testy nie przyczyniają się do zwiększenia jakości kodu, a jedynie sprawdzają, jak działa napisany kod
Ponieważ w podejściu TDD ważniejsze jest "co", a nie "jak" cykl programowania jest odwrócony.

Cykl programowania TDD

Przepis na TDD jest prosty:

  1. Najpierw napisz test, który nie przechodzi.
  2. Stwórz najprostsze możliwe rozwiązanie
  3. Refaktoryzuj
  4. Wróć do kroku 1

Najpierw napisz test

  • W TDD jest to ważne narzędzie – tutaj powstają pierwsze decyzje projektowe.
  • Programista musi przemyśleć jak kod zostanie użyty
  • Test wymusi odnalezienie tego, co rzeczywiście jest potrzebne w systemie
  • Testy stają się dokumentacją

Stwórz najprostsze możliwe rozwiązanie

  • Napisz dokładnie to, co jest wymagane przez test.
  • Nie dodawaj fajerwerków do kodu
  • Kod musi przejść stworzone testy – tyle wystarczy.

Refaktoryzuj

  • Przyjrzyj się swojemu rozwiązaniu
  • Zastanów się, co należy zrobić lepiej
  • Zmieniaj to, co rzeczywiście pomoże lepiej zrealizować wymagania
  • Popraw brzydkie fragmenty kodu

Ważnym pojęciem w TDD jest programowanie przyrostowe. Metoda najmniejszych kroków. Programowanie przyrostowe odnosi się zarówno do kodu programu jak i testów, które stają się coraz pełniejsze wraz z każdą iteracją.

W danym momencie dokonuj tylko jednej zmiany w systemie (możliwie najmniejszej)

Taka technika programowania posiada kilka zalet:

  • W każdym momencie masz w pełni działający kod
  • Małe zmiany powodują, że łatwiej nad mini zapanować
  • Małe zmiany powodują, że łatwiej znaleźć błędy, gdyż powodów błędów jest mniej.
  • Pracowanie na mniejszych fragmentach kodu daje większą kontrolę programiście
  • Projekt powstaje ewolucyjnie – programista może czerpać doświadczenie z tego, co napisał do tej pory.

W dalszej części tego dokumentu zauważycie, że metody przyrostowe wykorzystywane są na każdym kroku w TDD. Zaczynamy od rzeczy najprostszych i najprostszych rozwiązań

Przyczyną opracowania tej techniki programowania są ograniczenia pola świadomości:

  • Ludzki świadomy umysł jest w stanie przetwarzać 5-7 informacji w danym momencie
  • Dzieląc problem na mniejsze części ułatwiasz sobie pracę
  • Mniej szczegółów musisz zapamiętać
  • Jesteś bardziej świadomy konsekwencji wprowadzonych zmian

Test-Driven Development – jak to się robi

Pierwszą rzeczą, jaką należy zrobić przystępując od tworzenia projektu jest spis funkcjonalności. Możemy tutaj posłużyć się szablonem User Story, który pozwala na opisanie funkcjonalności w sposób najbardziej ogólny:

Jako <<osoba, rola>>,

chcę <<funkcjonalność, czynność>>,

aby <<uzasadnienie biznesowe&gt>;

As <<osoba, rola&gt>;,

I want <<funkcjonalność, czynność>>,

so that <<uzasadnienie biznesowe>>

Tutaj zahaczamy trochę o Behaviour-Driven Development. Jest to popularny pomysł na sterowanie procesem pisania kodu. Skupia się na wymaganiach użytkownika i interesariuszy. Określa formę zapisu wymagań, które są przekształcane w testy.

W BDD osią rozwoju systemu jest zachowanie systemu. Testy powinny przede wszystkim opisywać (i testować) zachowanie systemu. Formułowanie zachowania staje się sposobem definiowania wymagań i zarazem ich dokumentacją.

Tutaj znajdziecie ogólny opis BDD http://www.araneo.pl/blog/behaviour-driven-development

Posiadając już taką listę User Story „Musisz odpowiedzieć sobie na jedno zajebiście, ale to zajebiście ważne pytanie: co lubisz w życiu robić? Jaka jest następna najważniejsza rzecz, którą system powinien robić? A potem zacznij to robić.”

Jednym słowem należy wybrać najważniejszą z punktu widzenia użytkownika funkcjonalność i stworzyć dla niej kryteria akceptacyjne. Kryteria akceptacyjne formułowane są jako scenariusze. Możemy skorzystać tutaj z szablonu:

Zakładając że <<opis kontekstu>>

Gdy <<opis zdarzenia w systemie>>

Wtedy <<spodziewany rezultat>>

Diven <<opis kontekstu>>

when <<opis zdarzenia w systemie>>

then <<spodziewany rezultat>>

Kryteria akceptacyjne powinny być wykonywalne. Aby kryteria akceptacyjne miały największą wartość użytkową powinny mieć odzwierciedlenie w kodzie. Istnieje wiele frameworków wspierających BDD

  • JBehave (Java)
  • NBehave (.NET)
  • Spec (Ruby)
  • i wiele innych …
BDD wspiera tworzenie specyfikacji poprzez przykład (specification by example). Wymagania funkcjonale mogą być definiowane w formie kryteriów akceptacyjnych, które stają się jednocześnie dokumentacją.

Teraz przyszedł czas na testy akceptacyjne.

Ogólny schemat testowania akceptacyjnego

Testy akceptacyjne testują funkcjonalność z punktu widzenia użytkownika. Skupiają się na perspektywie użytkownika końcowego. Symulują one interakcję z systemem zewnętrznym (np. z użytkownikiem, usługą sieciową):

  • poprzez interakcję z użytkownikiem
  • wysyłając komunikaty symulujące system zewnętrzny
  • wywołując usługi sieciowe
  • parsując generowane raporty

Testy akceptacyjne odpowiadają za tzw. jakość zewnętrzną, czyli w jakim stopniu system zaspokaja potrzeby klienta i użytkowników. Pozwalają nam także sterować testami jednostkowymi, które to z kolei odpowiadają, za jakość wewnętrzną, czyli w jakim stopniu system zaspokaja potrzeby programistów i administratorów. (Testy integracyjne są gdzieś pośrodku J)

To jak już tyle wiemy to można już coś zrobić.

Od czego zacząć?

  • Zbuduj infrastrukturę, aby zautomatyzować proces
    build – deploy – test
  • Zwiększasz ilość uzyskiwanej informacji zwrotnej
  • Dzięki temu od razu będziesz mógł weryfikować budowanie i wdrażanie systemu
  • Szkielet – implementacja bardzo wąskiego wycinka systemu, który można zbudować, wdrożyć i przetestować
  • Zobacz jak system działa najszybciej jak to tylko możliwe
  • Jeśli istnieje system zewnętrzny, którego trudno użyć, zbuduj jego namiastkę

Przydatne jest także stworzenie zgrubnego projektu, czyli wysokopoziomowej struktury aplikacji lub części nad którą pracujesz. Przemyśl główne komponenty i ich powiązania. Jak komponenty będą się komunikować. Na stworzenie zgrubnego projektu przeznacz co najwyżej kilkanaście minut – przydatny jest flipcharta lub tablicy.


Dobre praktyki

  • Rozpocznij implementację funkcjonalności od testu akceptacyjnego
    • Wyrażone w języku użytkownika
    • Pozwala zrozumieć system przed przystąpieniem do implementacji
  • Oddziel testy, które wskazują postęp od testów regresyjnych
    • Testy regresyjne zawsze muszą przechodzić
    • Testy wskazujące postęp mogą nie działać w czasie rozwoju
  • Rozpocznij od najprostszego przypadku
    • Podstawowy scenariusz pozytywny
    • Implementacja powinna być prosta, ale nie zdegenerowana (zbyt prosta)
  • Pisz testy, które miałbyś ochotę przeczytać
    • Czytelny test wspiera nas w odnajdywaniu błędów i oszczędza nasz czas
  • Obserwuj, w jaki sposób test nie przechodzi
    • Zmodyfikuj komunikaty o błędach, aby były proste i jednoznaczne
  • Testuj zachowanie a nie metody
    • Przykładowo test testMyMethod nie mówi, jaki jest cel testu
    • Maksymalne pokrycie kodu nie jest dobrym celem
    • Skup się na funkcjach, które metoda realizuje
  • Jeśli trudno napisać test
    • Nie zastanawiaj się jak go napisać
    • A raczej co sprawia, że trudno jest przetestować
    • Najczęściej kiepski kod trudno jest przetestować
      • Przeanalizuj jakość kodu i go zrefaktoryzuj
  • Praktykuj ciągłe doskonalenie
    • Bezustannie analizuj swoje techniki testowania
    • Odnajduj słabe punkty swoich strategii i zmieniaj je
    • Szukaj balansu pomiędzy testami jednostkowymi a testami integracyjnymi

Na początku tego dokumentu wspominałem, że podejście przyrostowe można napotkać na każdym kroku TDD. Implementując daną funkcjonalność zaczynamy od najprostszego przypadku (testu akceptacyjnego), a następnie go rozwijamy. Przykładowo nasza wspaniała aplikacja karciana będzie umożliwiać użytkownikowi wyświetlenie listy gier, jakie rozegrał.

Jako Użytkownik

Chcę zobaczyć listę gier,

Aby móc przeglądać historię rozegranych partii.

To by było nasze User Story J. Dodatkowymi wymaganiami byłoby, że listę można sortować, a wszystkie przegrane oznaczone są na czerwono.

Przykładowa lista testów w inkrementacjach mogłaby wyglądać tak:

  • Pusta lista wyników (dla użytkownika bez rozegranych partii)
  • Lista wyników z kilkoma grami
  • Lista wyników z wyróżnionymi porażkami
  • Lista wyników z sortowaniem

Cały proces testowania zaczyna się i kończy na teście akceptacyjnym. Jakby to powiedzieli mądrzy ludzie, test akceptacyjny jest klamrą dopinającą cały proces implementacji funkcjonalności. Pisząc test akceptacyjny, zauważamy braki w naszym projekcie. (Brak metod, klas itd. Przypomnijmy, że na początku posiadamy jedynie szkielet aplikacji.) Co robimy, jeżeli zauważymy brak danej metody? Piszemy dla niej test (oczywiście nie zapominając o specyfikacji i podejściu przyrostowym). Potem implementacja, znowu może nam czegoś brakować i znowu testy i znowu implementacja i tak w kółko, aż dojdziemy do końca.

Wspominałem wcześniej, że testy akceptacyjne sterują testami jednostkowymi. Zaczynami od testów najwyższego poziomu. W trakcie ich powstawania powstają testy niżej poziomowe. Po ich implementacji wracamy z powrotem do testu wysokopoziomowego. Może to trochę słabo opisałem, ale mam nadzieję, że wszyscy wiedzą, co chciałem powiedzieć.

3 komentarze:

Tomek pisze...
Ten komentarz został usunięty przez autora.
Tomek pisze...

Spoko wpis. Jedyne do czego mogę się przyczepić to literówka - "zgubnego projektu"- powinno raczej być "zgrubnego projektu". Poniżej zdanie w którym występuje błąd
"Przydatne jest także stworzenie zgubnego projektu, czyli wysokopoziomowej struktury aplikacji lub części nad którą pracujesz."

Massa pisze...

Tak :)

Prześlij komentarz

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Online Project management