Testowanie aplikacji nie jest oczywistą i prostą pracą. W praktyce bardzo często brakuje czasu na to, aby „optymalnie” przetestować aplikację, zanim trafi ona do klienta. Z tym problemem spotyka się sporo firm informatycznych oraz programistycznych. Ostatnimi czasy testowanie oprogramowania stało się bardzo gorącym tematem. Jest to problem, który próbują rozwiązać potężne umysły. Oczywisty jest fakt, że nikt nie może przetestować całej aplikacji od początku do końca, ponieważ czas testów wielokrotnie przekraczałby długość życia oprogramowania lub byłoby to nieopłacalne. Nawet testy automatyczne nie są w stanie zapewnić pełnego pokrycia wszystkich możliwych kombinacji funkcjonalności oraz danych w programie. Pojawia się pytanie: Jak zaprojektować testy i jak napisać przypadki testowe, aby najbardziej optymalnie sprawdzić, czy wszystkie funkcje w aplikacji działają prawidłowo? Jak najbardziej optymalnie sprawdzić przenaszalność danych pomiędzy stanami?
Można zoptymalizować testowanie, gdy ograniczymy się do takiego zaprojektowania systemu oraz napisania przypadków testowych, w którym wykorzystujemy cykl Hamiltona w grafie przepływu danych, w grafie stanów lub w grafie przenaszalności danych pomiędzy stanami zdefiniowanymi oraz zaprojektowanymi w danym systemie informatycznym.
Przedstawione podejście można stosować na każdym poziomie testów oraz na każdym poziomie projektowania systemu, np. poprzez wykorzystanie modelu V(fazy testów są powiązane z fazami projektowania oraz wytwarzania oprogramowania).
Grafy Hamiltona ich własności
Zaprezentujmy niezbędne wiadomości, które należy poznać w celu zrozumienia najważniejszego problemu opisywanego w następnym rozdziale, a mianowicie zastosowania teorii grafów Hamiltona podczas projektowania systemów informatycznych oraz przypadków testowych. Zaczniemy od cykli Hamiltona:
Definicja 1. Cykl Hamiltona to taki cykl w grafie, w którym każdy wierzchołek grafu przechodzony jest tylko jeden raz (oprócz pierwszego wierzchołka). Ścieżka Hamiltona (ang. Hamiltonian path) jest taką ścieżką w grafie, która odwiedza każdy jego wierzchołek dokładnie jeden raz.
Definicja 2. Grafem spójnym nazywamy graf spełniający warunek, że dla każdej pary wierzchołków istnieje ścieżka, która je łączy.
Przykład 1.
W celu łatwiejszego zrozumienia kolejnych rozdziałów należy znać dwie poniżej przedstawione definicje:
Definicja 3. Spójną składową grafu nieskierowanego G jest spójny podgraf grafu G nie zawarty w większym podgrafie spójnym grafu G. Innymi słowy spójna składowa grafu jest to taki podgraf, który można "wydzielić" z całego grafu bez usuwania krawędzi. Graf spójny ma jedną spójna składową.
Definicja 4. Grafem prostym nazywamy graf bez pętli własnych i krawędzi wielokrotnych. Często określenie graf (bez przymiotników) oznacza graf prosty.
Niech Goznacza graf, V(G) zbiór jego wierzchołków, E(G) zbiór krawędzi, |A| moc zbioru, u_ipojedynczy (w tym przypadku i-ty) wierzchołek grafu a deg(v) stopień wierzchołka (liczbę kończących się w nim krawędzi). Tradycyjnie oznacza się V(G) = n oraz E(G) = m, zapis {v,u} będący zbiorem dwuelementowym wierzchołków, używa się do oznaczenia krawędzi między viu.
Twierdzenie 1 (Ore). Jeżeli w grafie prostym G o n wierzchołkach, n > 2, zachodzi następująca nierówność:
deg(v) + deg(u) ≥ n – 1,
dla każdej pary niepołączonych bezpośrednio krawędzią wierzchołków u i v, to graf G posiada cykl Hamiltona.
Twierdzenie 2 (Diraca). Jeśli graf G nie ma pętli, ani krawędzi wielokrotnych (jest grafem prostym) i
|V(G)| ≥ 3 oraz jeśli deg(v)≥ (|V(G)|)/2 dla każdego wierzchołka w G, to jest on hamiltonowski.
Aby graf mógł posiadać cykl Hamiltona, musi być spójny – jest to oczywiste. W grafie niespójnym istnieją wierzchołki, pomiędzy którymi brak ścieżki, zatem nie można ich odwiedzić. Można podać prosty algorytm rekurencyjny znajdowania wszystkich cykli i ścieżek Hamiltona, jednakże posiada on złożoność O(n!), co czyni go zupełnie niepraktycznym dla większych grafów (zawierających powyżej 30 wierzchołków). Zasada działania naszego algorytmu jest następująca:
za pomocą odpowiednio zmodyfikowanej procedury przeglądamy graf począwszy od wierzchołka nr 0 (wybór wierzchołka nie ma znaczenia, ponieważ możliwa ścieżka lub cykl Hamiltona musi przechodzić przez wszystkie wierzchołki grafu). Przetwarzany wierzchołek umieszczamy na stosie. Następnie sprawdzamy, czy stos zawiera n wierzchołków. Jeśli tak, to otrzymaliśmy ścieżkę Hamiltona. W takim przypadku sprawdzamy, czy ostatni wierzchołek ścieżki jest połączony krawędzią z wierzchołkiem nr 0. Jeśli tak, to ścieżka tworzy cykl Hamiltona - wyprowadzamy jej zawartość ze stosu (dla cyklu dodatkowo dodajemy na końcu wierzchołek 0), usuwamy ostatni wierzchołek ze stosu i wycofujemy się na wyższy poziom rekurencji, gdzie będą rozważone inne ścieżki lub cykle Hamiltona.
Jeśli stos nie zawiera n wierzchołków, to rekurencyjnie wywołujemy procedurę szukającą ścieżki dla wszystkich nieodwiedzonych sąsiadów. Wierzchołek usuwamy ze stosu i kasujemy informację o jego odwiedzeniu, po czym wycofujemy się na wyższy poziom rekurencji.
Algorytm wyszukiwania ścieżki Hamiltona
Przykładowy algorytm znajdujący ścieżkę Hamiltona w grafie może mieć następującą postać:
1) Funkcja rekurencyjna Hamilton (n, graf, v, visited, S)
2) Wejście:
S – stos wierzchołków,
3) Wyjście: Wszystkie cykle i ścieżki Hamiltona w grafie,
4) Elementy pomocnicze:
u – wierzchołek grafu.
Liczba kroków w algorytmie:
K01: |
S.push(v) |
; umieszczamy v na stosie |
K02: |
JeśliSzawiera mniej niż n wierzchołków, to idź do K10 |
; mamy ścieżkę Hamiltona? |
K03: |
test← false |
; zakładamy brak cyklu Hamiltona |
K04: |
Dlakażdego sąsiada u wierzchołka v, wykonuj K05 |
; przeglądamy sąsiadów v |
K05: |
Jeśliu = 0, totest ← true i idź do K06 |
|
K06: |
Jeślitest= true, to pisz "Cykl Hamiltona:" |
|
K07: |
Piszzawartość S bez usuwania danych z S |
|
K08: |
Jeślitest= true, to pisz 0 |
; dla cyklu dopisujemy wierzchołek startowy |
K09: |
Idź doK14 |
|
K10: |
visited[v] ← true |
; oznaczamy wierzchołek jako odwiedzony |
K11: |
Dlakażdego sąsiada u wierzchołka v, wykonuj K12 |
; przeglądamy sąsiadów wierzchołka v |
K12: |
Jeślivisited[u] = false, toHamilton(n,graf,u,visited,S) |
; dla sąsiadów nieodwiedzonych wykonujemy wywołanie rekurencyjne |
K13: |
visited[v] ← false |
; wycofujemy się z wierzchołka v |
K14: |
S.pop() |
; wierzchołek v usuwamy ze stosu |
K15: |
Zakończ |
|
Zastosowanie teorii w praktyce
Wykorzystanie twierdzenia Ore oraz ścieżki Hamiltona w projektowaniu testów
Graf ten nie jest grafem Hamiltona. Statusy grafów możemy potraktować jak wierzchołki grafu. Aby przetestować przenaszalność danych pomiędzy poszczególnymi statusami, trzeba wielokrotnie przechodzić przez niektóre węzły. Możemy jednak zastanowić się, czy nie warto dodać jeszcze jednej ścieżki, tak aby nie było to niezgodne z logiką firm budowlanych. Możemy zaproponować takie podejście:
- założenie budowy oraz przetarg wygrany można potraktować jako jeden węzeł, ponieważ dokumenty związane już z samą budową są zupełnie innymi dokumentami niż projekt przetargu i nie są już statusem przetargu. W momencie założenia budowy, tworzymy nowy pusty dokument, który należy wypełnić i zapisać. Przy takim podejściu graf redukuje się do siedmiu wierzchołków,
- pomijamy przetarg zamknięty, ponieważ jest to przetarg, z którego firma zrezygnuje. Pozostaje on jedynie w bazie danych i nic się już z nim nie dzieje(dane z takiego przetargu nie są już przenoszone po zamknięciu).
Myśląc w ten sposób, możemy zredukować graf do sześciu wierzchołków. Graf przepływu danych przyjmie następującą postać:
Dodana została możliwość odrzucania projektu w przygotowaniu. Ścieżka Hamiltona dla tego grafu wygląda następująco:
Ważone grafy hamiltonowskie
Zastanówmy się, co byłoby w przypadku sytuacji, gdy w stanach w systemie lub pomiędzy przepływem danych istnieje więcej niż jeden graf Hamiltona. W praktyce, zawsze istnieje w aplikacji ścieżka, którą klient wykonuje częściej, niż inne czynności.
a) Jeżeli chcemy najmniejszą sumę wag, to wybierzemy ścieżkę:
1 -> 2 -> 4 -> 0 -> 3,
gdzie suma wag wynosi 7,
b) Jeśli chcemy największą sumę wag, to wybierzemy ścieżkę:
0 -> 1 -> 3 -> 4 -> 2,
gdzie suma wag jest równa 13.
Algorytm, który sprawdza sumy wag może być analogiczny do algorytmu, który przedstawiony jest w rozdziale 3, z tą różnicą, że musi mieć dodatkowy warunek, który sprawdza sumę ścieżek po znalezieniu ścieżki Hamiltona.
Zadaniem testera (zgodnie z ideologią ISTQB) jest powiedzenie, że "to działa" a nie pesymistyczne podchodzenie do aplikacji, w sposób, że nic nie działa dobrze. Więc, projektowanie testów oraz aplikacji pod kątem istnienia w niej cyklu Hamiltona pozwoli zaoszczędzić czas (którego często brakuje) podczas testowania przenaszalności danych między stanami w aplikacji. Jeśli wynik testu okaże się pozytywny, to znaczy, że dane przenoszą się poprawnie i nie ma luki ani defektu pomiędzy funkcjami przejścia przez stany.
Istnienie cyklu Hamiltona (tak jak w schemacie przetargów) pozwoli również zaprojektować test automatyczny tak, że raz wprowadzamy dane i na końcu tworzymy warunki sprawdzające.
O autorze
Marek Żukowicz jest absolwentem matematyki na Uniwersytecie Rzeszowskim. Obecnie pracuje jako tester. Jego zainteresowania skupiają się wokół testowania, matematyki, zastosowania algorytmów ewolucyjnych oraz zastosowania matematyki w procesie testowania. Interesuje się również muzyką, grą na akordeonach oraz na perkusji. |
Od redakcji