sobota, 10 lutego 2007

Generyczne przypadki testowe

Każda firma produkująca oprogramowanie dąży do stworzenia zasobów generycznych pozwalających na szybkie i elastyczne generowanie nowych produktów - przynajmniej ich dużą część. W ten sposób powstają przeróżne biblioteki, repozytoria, snippety, interfejsy i inne takie. Po krótkim czasie okazuje się, że tak naprawdę jest to osobny projekt wymagający osobnego kierownika, planu, zdefiniowanych celów itd. Czy automatycznie rzutuje to na dział testów?

Zdecydowanie tak! I to na wielu płaszczyznach które chyba najlepiej wyrazić pytaniami.
  • co to znaczy generyczność?
  • jak przetestować projekt generyczny?
  • czy do generycznego projektu powinny powstać generyczne przypadki testowe?
  • co to znaczy, że przypadek testowy jest generyczny?
  • do kiedy jest generyczny a od kiedy już nie?
Roztrząsając pewne techniczne zagadnienia związane z integracją TestDirectora z CQ, wraz z kilkoma osobami z działu zajmującego się zarządzaniem konfiguracją i integracją środowiska, doszliśmy do zaskakującego wniosku:
"Istnieje na rynku wiele narzędzi do zarządzania artefaktami projektu takimi jak defekty, przypadki testowe, kod źródłowy, wymagania, specyfikacja czy dokumentacja projektowa. Większość z nich - biorąc pod uwagę przydatność biznesową szeroko rozumianą - kosztuje bardzo drogo. Każdy producent wychwala jak to jest elastyczne i integrowalne ale w rzeczywistości brakuje tym systemom wsparcia generyczności co prawdopodobnie spowodowane jest architekturą opartą o projekt."
Większość architektur takich rozwiązań przewiduje projekt jako kontekst projektu*. Z kolei projekt zdefiniowany jest jako jednotorowy - na ogólnym poziomie - proces wytwarzania oprogramowania. I tutaj pojawia się pierwszy problem! W środowiskach wieloprojektowych rozumienie generyczności jest inne niż w środowisku dużego projektu. W pierwszym przypadku oznacza to "zakres" kodu, możliwy do użycia przy założeniu prac konfiguracyjnych do różnych projektów. Natomiast w środowisku dużego projektu lub - dla jasności nazwijmy go jednoprojektowym - generyczność oznacza reużywalność ale nie względem projektów tylko modułów (stąd chyba bliżej do czystej obiektowości!?)

Oto prosty przykład: programiści pracują w kontekście projektu specyficznego i generycznego równocześnie, testerzy natomiast pracują tylko w kontekście konkretnego projektu (z przyczyn oczywistych). Tak więc defekty zgłoszone przez testerów do projektu "S" są potem przekierowywane przez programistów do projektu "G", kiedy natura defektu okazuje się generyczna. Następnie naprawione defekty wracają do testerów w celu weryfikacji i weryfikacja wszystkich defektów - nawet tych z projektu "G" - odbywa się na projekcie "S". Wystarczy teraz dodać, że testerzy pracują w swoim środowisku a programiści w swoim. Z systemu zarządzania testami raportowane są defekty do zintegrowanego systemu rejestracji defektów. Problem pojawia się w momencie przepisania defektu z projektu "S" do projektu "G". Po prostu w systemie do zarządzania przypadkami widoczne są defekty tylko dla konkretnego projektu. Więc analityk do spraw jakości nie ma dostępu do pełnej informacji tylko do jej części.

Chciałbym podkreślić w tym miejscu, że opisywany przeze mnie tutaj problem chyba w mniejszym zakresie dotyczy środowisk jednoprojektowych. A przynajmniej w środowisku wieloprojektowym objawia się znacznie intensywniej.

Skoro istnieje już projekt generyczny z generycznym kodem, który prawdopodobnie nie da się skompilować w łatwy sposób, to przed testerem powstaje poważne zagadnienie: "jak przetestować tą generyczność zawartą w generycznym projekcie?"

Odpowiedzi oczywiście jest kilka i pewnie poniższe nie pokrywają całości:
  1. Testować niejawnie poprzez implementację fragmentów generycznych w modułach/projektach.
  2. Zbudować sztuczne projekty - najlepiej na początku fazy stabilizacji kiedy kod jest już gotowy - wygenerowane w głównych mutacjach użycia i przetestować je. Następnie przeprowadzić analizę porównawczą wyników dla poszczególnych projektów. Tam gdzie pokrywają się i wynik jest pozytywny, generyczność jest zaimplementowana.
  3. Określić wszystkie możliwe konfiguracje modułu/projektu, wygenerować je w postaci binarki i poddać moduły testom integracyjnym a projekty testom systemowym (oczywiście w ograniczonym zakresie) . Oczywiście procedura jest jednakowa dla wszystkich mutacji.
Innym podejściem do testowania generyczności może buć przygotowanie generycznych przypadków testowych. Tutaj jednak powstaje nasze czwarte pytanie (na trzecie odpowiedziałem w poprzednim zdaniu) "co to znaczy, że przypadek testowy jest generyczny?":
  1. da się go uruchomić na każdym projekcie z rodziny "G"?
  2. da się go uruchomić bez zmiany parametrów?
  3. jest reużywalny jako zamknięta część - bez konieczności zmian?
  4. wskazuje jedynie co powinno być przetestowane?
Pierwsze pytanie sugeruje szansę sukcesu ale tak naprawdę poważnie ogranicza zakres pokrycia testami. Oznacza to, sukces ale bardzo maleńki.

Drugie pytanie dotyczy tak naprawdę zagadnienia; co określa przypadek testowy jako generyczny? Ja jestem gorącym zwolennikiem oddzielenia danych testowych od procedury. Pozwoliło by to na bardzo dużą elastyczność:
  • dynamiczne generowanie przypadków testowych w oparciu o jedną procedurę i wiele zestawów danych, co pokryłoby w łatwy sposób:
    • klasy równoważności
    • dziedziny
    • wartość graniczne
    • przepływ danych
    • przepływ sterowania
  • kontrolę potrzebną głównie na poziomie danych
  • dużą przenaszalność samych procedur.
Pytania 3 i 4 wskazują chyba na zasadniczy konflikt. Mowa jest o tym, iż albo mamy bardzo szczegółową procedurę, która powinna mieć wynik pozytywny na każdym projekcie z rodziny albo nie mamy właściwie nic oprócz listy kontrolnej, którą następnie uzupełniamy jako implementację instancji przypadku testowego na konkretnym projekcie. Pierwsze wyjście powoduje, ze nasze zasoby generycznych przypadków testowych są bardzo ograniczone i tak naprawdę testują tylko standardowe funkcjonalności lub przypadki użycia. Ale za to uzyskujemy krótki czas przygotowania testów.

Drugie rozwiązanie pozwala na znacznie szerszy zakres testów ale czas do uruchomienia jest znacznie dłuższy.

Odpowiedź na ostanie pytanie: "do kiedy przypadek jest generyczny a od kiedy już nie" jest mocno uzależniona od tego jak zdefiniowana zostanie sama generyczność. Ja po prostu polecałbym zdrowy rozsądek i metodę drobnych kroczków. Najpierw należy zbudować zbiór przypadków, które udowodnią swoją generyczność (przy naprawdę minimalnych zmianach wykorzystywane będą na szeroką skalę). Każdy w miarę doświadczony tester ma podręczny zestaw takich przypadków (obsługujących takie obszary jak logowanie, wylogowywanie, walidowanie danych, itd). Następnie na tej podstawie należy spróbować określić co oznacza w danym wypadku "generyczność" i skorzystać z tego jako z kryterium przy wybieraniu następnych przypadków testowych. Jednak definicja generyczności powinna być nieustannie weryfikowana.

* Właściwie to nie znam systemu przeznaczonego do opisanych zastosowań, który nie używał by projektu jako kontekstu opisującego całość.

Brak komentarzy: