poniedziałek, 5 grudnia 2011

TDD cz.2

Drugą część zaczniemy od kilku słów na temat testów integracyjnych. Później powiem coś na temat wzorców testowania jednostkowego. A na koniec opiszę pare zagadnień, które z różnych powodów nie zostały opisane wcześniej

I. Testy integracyjne

Ogólnie rzecz biorąc testowanie integracyjne wykonywane jest w celu wykrycia błędów w interfejsach i interakcjach pomiędzy modułami.Testy jednostkowe natomiast testują metody wybranej klasy, a klasy z nią współpracujące mockują. Testem jednostkowym nie jest więc test, który:
  • Komunikuje się z bazą danych
  • Komunikuje się po przez sieć
  • Pracuje z systemem plików
  • Nie może być uruchomiony równolegle z innymi testami
  • Musisz dokonać zmian w środowisku uruchomieniowym, aby zadziałał
Najczęściej spotykanym obszarem wykorzystania testów integracyjnych jest dostęp do danych. Testując integracyjnie dostęp do danych należy pamiętać o kilku ważnych rzeczach:
  • Testowana baza danych zazwyczaj nie jest idealnym odzwierciedleniem bazy produkcyjnej
  • Tworząc bazę testową należy wziąć pod uwagę następujące czynniki:
    • Jak bardzo ustawienia bazy testowej są zbliżone do bazy produkcyjnej
    • Na ile łatwo jest współdzielić konfigurację w zespole
    • Na ile łatwy jest dostęp do bazy
  • Zawsze czyść bazę danych przed testem, a nie po jego wykonaniu
  • Stwórz klasę wspierającą testowanie, która będzie zajmowała się czyszczeniem bazy danych
W celu odseparowania warstwy dostępu do danych używamy wzorca DAO. Możemy wtedy mockować dostęp do danych.

II. Wzorce testowania jednostkowego

1) Wzorce asercji

Wzorce te zostały zebrane i opisane przez Gerarda Meszaros http://xunitpatterns.com/

Wzorce asercji można podzielić na:

  • Asercja stanu końcowego – sprawdza stan końcowy
  • Asercja pomocnicza - programista upewnia się, że obiekt, który tworzy, ma odpowiedni stan
  • Asercja delta – badana jest różnica między stanem początkowym a końcowym
  • Własna asercja
  • Asercja interakcji

Programowa realizacja tych wzorców przejawia się w metodach klasy Assert. Metody te zostały opisane w podpunkcie IV.2

http://premium-hands.blogspot.com/2011/11/normal-0-21-false-false-false-pl-x-none.html

My skupimy się tylko na dwóch ostatnich typach.

Własna asercja (Custom Assertion) – jeśli używasz wielokrotnie tej samej asercji lub asercja jest złożona warto wyodrębnić metodę – własną asercję. Może się to wydawać oczywiste, ale warto o tym wspomnieć. Asercja własna jest najczęściej wynikiem refaktoryzacji kodu. Przypomnijmy, refaktoryzacji nie podlega tylko właściwy kod aplikacji, ale także kod testowy.

Asercja interakcji (Interaction Assertion) – zamiast badać stan bada się interakcję obiektów testowanych. Do tego celu głównie używa się Mock Objects. Tym sposobem możemy sprawdzić wywołanie konkretnej metody, parametry z jakimi została wywołana, a także kolejność wywołań. Testowanie interakcji stosuje się dla testów złożonych, gdzie ze względu na trudność realizacji testowanie stanu jest bardzo trudne.

2. Wzorce otoczenia testu

Otoczenie testu (test fixture) – otoczenie testu to warunki początkowe wspólne dla kilku testów. Otoczenie testu nadaje kontekst dla testów, które do niego przynależą. Jest realizowane po przez pola klasy testu i inicjację w metodzie oznaczonej atrybutem [SetUp] TODO: Poszukać atrybutu dla VS TeamTest. Otoczenie testu minimalizuje duplikację, wyodrębnia cechy wspólne wielu testów w formie pól i metody SetUp. Dobrze przemyślane rozwiązanie spowoduje, iż metody testowe będą się składać z prostych wywołań i asercji. Warto także wspomnieć, iż atrybut opisujący klasę testową w NUnit to [TestFixture].

Metoda fabryki (Parameterized Creation Method) – zamiast budować stan obiektów za każdym razem wypełniając wiele pól, warto wyodrębnić metodę pomocniczą, która pomoże stworzyć wymagane obiekty

  • Często podczas testowania należy tworzyć wiele pomocniczych obiektów
  • Stwórz metodę pomocniczą – metodę fabryki, która zajmie się szczegółami przygotowania obiektów do testu, które nie mają większego znaczenia
  • Czasami wystarczy wykorzystać odpowiedni konstruktor, jeśli istnieje lub stworzyć nowy

Klasy pomocnicze

  • Dodatkowo przydają się również pomocnicze klasy, które upraszczają tworzenie obiektów, np. generator unikalnych identyfikatorów (np. generator imion i nazwisk)

Matka obiektów

  • Klasa fabryki stworzona na potrzeby testów
  • Zawiera metody kreacyjne
  • Zawiera metody zmieniające stan obiektów (np. zamówione – złożone, zaakceptowane oczekujące zrealizowane)
  • Zbiera metody pomocnicze ułatwiające tworzenie obiektów na potrzeby testów
  • Może być to jedna klasa lub kilka klas
  • Klasy te mogą powstawać w efekcie refaktoryzacji testów

Automatyczne porządki (Automated teardown)

  • Sprzątanie po teście w przypadku złożonych danych jest nietrywialnie – łatwo zapomnieć o stworzonych obiektach
  • Tworzy się rejestr obiektów, do którego trafiają wszystkie obiekty tworzone w [SetUp], które należy usunąć po zakończeniu testu [TearDown]

3. Wzorce klas testowych:

Autopodstawienie (Self-Shunt)

  • Klasa testowa implementuje interfejs, który ma stanowić imitację (Test double)
  • Pozwala uniknąć tworzenia dodatkowych klas
  • Zaciemnia konstrukcję
  • Przydatne w prostych przypadkach

Uprzywilejowany dostęp (Privileged Access)

  • Bezpośrednie testowanie metod prywatnych jest w NUnit niemożliwe
  • Można skorzystać z refleksji do wywołania metod prywatnych
  • Nie powinno się nadużywać tej techniki
  • Zbyt wiele metod prywatnych, które należy przetestować, może sugerować refaktoryzację Wydzielenie klasy.

Dodatkowy konstruktor (Extra Constructor)

  • Jeśli kod, który testujesz ma zależności zaszyte wewnątrz klasy, trudno będzie go testować w izolacji
  • Stwórz dodatkowy konstruktor, który pozwoli dostarczać dane z zewnątrz
  • W teście użyj nowego konstruktora

III. Elementy testowanego kodu

1. Kompozycja zamiast dziedziczenia

Dziedziczenie jest jednym ze sposobów rozdzielenia odpowiedzialności między klasami. Umożliwia dodanie lub zmianę zachowania w klasie. Wadą jest to, że przeciążane metody mają tendencję do dużej zależności od klasy, w której się znajdują. Oznacza to, że zazwyczaj obciążone są pewnymi założeniami (np. metoda intensywnie korzysta z wewnętrznego stanu obiektu). Klasy dziedziczące mają tendencję także do rozrastania się. Często okazuje się że pewne elementy z klas nadrzędnych nie są potrzebne w klasach podrzędnych. Klasy są ze sobą mocno powiązane dziedziczeniem co utrudnia używanie test double.

Dzięki kompozycji klasa deleguje założone zadania do innych komponentów. Następuje lepsze rozłożenie odpowiedzialności. W testach możemy używać test doubles w miejscu klas zależnych. Poniważ klasy są mniejsze łatwiej testować je jednostkowo.

2. Unikanie elementów statycznych

Elementy statyczne mają szereg wad, które mogą utrudniać testowanie:

  • Elementy statyczne mają charakter globalny (są związane z klasą a nie z obiektem)
  • Elementy statyczne nie podlegają dziedziczeniu
  • Nie można zastosować do nich test double
  • Testowanie w pełni jednostkowe jest niemożliwe
  • Zależność jest zaszyta wewnątrz metody
3. Architektura Warstwowa

  • Architektura warstwowa sprzyja testowaniu
  • Powinny być wydzielone przynajmniej 3 warstwy
    • Interfejs użytkownika
    • Logika biznesowa
    • Dostęp do danych
  • Klasy wizualne interfejsu nie powinny zawierać żadnej logiki przetwarzania. Logikę UI należy przenieść do obiektów pomocniczych
IV. Różne różności

W ostatniej części tego wpisu chciałem wspomnieć o kilku rzeczach, na które nie znalazło się miejsce w poprzednich rozdziałach.

1. Złote zasady testów automatycznych

  • Automatyczne – wykonują się bez udziału człowieka
  • Zupełne – obejmują wszystkie istotne przypadki testowe
  • Powtarzalne – mogą być uruchamiane wielokrotnie
  • Niezależne – nie powinny zależeć od zmian w środowisku zewnętrznym testu
  • Profesjonalnie – kod testu powinien być napisany równie starannie, jak kod produkcyjny
2. Strategie wyboru testów

a) Ogół – szczegół
b) Znane – nieznane
c) Ścieżka pozytywna – ścieżka negatywna

Ad. a

- Ogół

Najpierw testy wysokiego poziomu – tworzonych jest wiele testów bez początkowego wchodzenia w szczegóły.

Zalety:

  • Większe zrozumienie całościowe systemu

- Szczegół

Dany test jest rozwijany tak długo, aż nie osiągnie się ostatecznego, pełnego rozwiązania.

Zalety:

  • Szybka eliminacja ryzyka, gdyż rozpoznawane są szczegóły problemu


Ad.b Znane - nieznane

- Znane

Najpierw wybierane są te testy, które sprawdzają typowe elementy funkcjonalności, które są łatwo przewidywalne,

Zalety:

  • Jednoznaczne ścieżka prowadząca dająca szybkie efekty

- Nieznane

Najpierw wybierane są te testy, które obejmują mniej typowe przypadki

Zalety:

  • Wczesna analiza i potencjalna eliminacja ryzyka – zostaną szybko wykryte te elementy których nie da się wykonać lub będzie to nieopłacalne

Note:

Nie warto stosować kiedy nietypowe przypadki są bardzo mało prawdopodobne lub nie mają wpływu na system.

Ad.c Ścieżka pozytywna ścieżka negatywna

- Ścieżka pozytywna

Testowane są scenariusze typowego, zakończonego powodzeniem działania metody lub funkcji.

Zalety:

  • Testowane jest to, co przydarza się najczęściej

- Ścieżka negatywna

Testowane są scenariusze sytuacji wyjątkowych, na które system powinien być przygotowany

Zalety:

  • Minimalizowane jest ryzyko wynikające z błędów użytkownika lub programisty

Note:

Zazwyczaj najpierw warto wybrać testowanie z użyciem ścieżki pozytywnej, a następnie ścieżki negatywnej

Jak to połączyć w praktyce?

  1. Zacznij od prostego i oczywistego testu (sunny day scenario)
    - proste podstawienia, typowy scenariusz
  2. Testuj każdą funkcję z uwagą.
    - Zastanów się czy dana funkcja jest istotna z punktu widzenia systemu i jeśli tak, to, dlaczego
  3. Najpierw testuj ogólnie, a następnie szczegółowo
    - W ten sposób odnajdziesz najbardziej newralgiczne punkty systemu
  4. Po przetestowaniu podstawowego scenariusza, wybierz trudne ścieżki (nieznane lub negatywne)




0 komentarze:

Prześlij komentarz

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