W poprzedniej lekcji omówiliśmy techniki czarnoskrzynkowe, które koncentrują się na zewnętrznym zachowaniu systemu.
Teraz czas zajrzeć do środka – do kodu – i poznać techniki białoskrzynkowe (white-box techniques).
Jak sama nazwa wskazuje, techniki te wymagają wiedzy o wewnętrznej strukturze, projekcie i kodzie oprogramowania.
Ich celem jest analiza przepływu sterowania i danych w kodzie, aby zaprojektować testy, które odpowiednio go "przećwiczą".
Sylabus ISTQB Foundation Level v4.0 w sekcji 4.3 (FL-4.3.1 K2, FL-4.3.2 K2, FL-4.3.3 K2) koncentruje się na dwóch podstawowych technikach białoskrzynkowych związanych z kodem oraz zrozumieniu ich wartości:
- Testowanie instrukcji (Statement Testing) i pokrycie instrukcji (Statement Coverage)
- Testowanie gałęzi (Branch Testing) i pokrycie gałęzi (Branch Coverage)
- Korzyści wynikające z testowania białoskrzynkowego
W tej lekcji wyjaśnimy, na czym polegają te techniki, jak mierzy się związane z nimi pokrycie kodu oraz jakie korzyści przynosi stosowanie podejścia białoskrzynkowego w testowaniu.
1. Testowanie Instrukcji i Pokrycie Instrukcji Kodu (Statement Testing and Coverage)
Cel: Zaprojektowanie przypadków testowych, które wykonają każdą wykonywalną instrukcję w kodzie źródłowym co najmniej raz.
Element pokrycia: Wykonywalna instrukcja kodu (np. przypisanie wartości, wywołanie funkcji, operacja arytmetyczna; nie liczą się deklaracje zmiennych, komentarze).
Jak stosować?
- Analiza kodu: Zidentyfikuj wszystkie wykonywalne instrukcje w testowanym module lub fragmencie kodu. Często wykorzystuje się do tego narzędzia analizujące kod.
- Projektowanie testów: Stwórz zestaw przypadków testowych (danych wejściowych), które spowodują wykonanie każdej zidentyfikowanej instrukcji.
- Pomiar pokrycia: Uruchom testy i zmierz, ile instrukcji zostało wykonanych. Pokrycie instrukcji oblicza się według wzoru:
Pokrycie instrukcji = (Liczba wykonanych instrukcji / Całkowita liczba instrukcji wykonywalnych) * 100%
Przykład:
1. Czytaj A
2. Czytaj B
3. Jeżeli A > B THEN
4. Drukuj "A jest większe"
5. Inaczej
6. Drukuj "A nie jest większe"
7. Koniec Jeżeli
8. Koniec
W tym pseudokodzie mamy 6 instrukcji wykonywalnych (1, 2, 3, 4, 6, 8). Instrukcje 5 i 7 to elementy strukturalne, nie wykonywalne linie kodu.
- Przypadek testowy 1: A = 5, B = 3. Wykonane instrukcje: 1, 2, 3, 4, 8.
- Przypadek testowy 2: A = 2, B = 4. Wykonane instrukcje: 1, 2, 3, 6, 8.
Aby osiągnąć 100% pokrycia instrukcji, potrzebujemy obu tych przypadków testowych, ponieważ pierwszy wykonuje instrukcję 4, a drugi instrukcję 6.
Po wykonaniu obu testów, wszystkie instrukcje (1, 2, 3, 4, 6, 8) zostały wykonane co najmniej raz.
Wartość i ograniczenia:
- Wartość: Gwarantuje, że każda linia kodu została uruchomiona, co zwiększa szansę na wykrycie prostych błędów w instrukcjach. Jest to podstawowe, najłatwiejsze do osiągnięcia kryterium pokrycia kodu.
- Ograniczenia: 100% pokrycia instrukcji nie gwarantuje przetestowania wszystkich możliwych ścieżek decyzyjnych w kodzie. W powyższym przykładzie, nawet jeśli osiągnęliśmy 100% pokrycia instrukcji, nie mamy pewności, czy warunek `A > B` działa poprawnie dla przypadku `A = B`. Testowanie instrukcji nie wykryje też błędów w logice warunków (np. użycie `>` zamiast `>=`). Ponadto, wykonanie instrukcji nie zawsze ujawnia defekt, np. dzielenie przez zero spowoduje błąd tylko wtedy, gdy dzielnik faktycznie będzie zerem.
2. Testowanie Gałęzi i Pokrycie Gałęzi (Branch Testing and Coverage)
Cel: Zaprojektowanie przypadków testowych, które wykonają każdą możliwą gałąź (wynik decyzji) w kodzie co najmniej raz.
Element pokrycia: Gałąź w przepływie sterowania.
Gałąź reprezentuje przepływ sterowania między dwoma punktami w kodzie.
Decyzje (np. instrukcje `IF`, `CASE`, pętle `WHILE`, `FOR`) tworzą punkty rozgałęzień, z których wychodzą co najmniej dwie gałęzie (np. gałąź dla warunku prawdziwego i gałąź dla warunku fałszywego).
Jak stosować?
- Analiza kodu i przepływu sterowania: Zidentyfikuj wszystkie punkty decyzyjne w kodzie i wychodzące z nich gałęzie. Często rysuje się graf przepływu sterowania, aby to zwizualizować.
- Projektowanie testów: Stwórz zestaw przypadków testowych, które spowodują wykonanie każdej gałęzi (każdego możliwego wyniku każdej decyzji).
- Pomiar pokrycia: Uruchom testy i zmierz, ile gałęzi zostało wykonanych. Pokrycie gałęzi oblicza się według wzoru:
Pokrycie gałęzi = (Liczba wykonanych gałęzi / Całkowita liczba gałęzi) * 100%
Przykład (ten sam co poprzednio):
1. Czytaj A
2. Czytaj B
3. Jeżeli A > B THEN
4. Drukuj "A jest większe"
5. Inaczej
6. Drukuj "A nie jest większe"
7. Koniec Jeżeli
8. Koniec
Punkt decyzyjny jest w linii 3 (`Jeżeli A > B`). Mamy dwie gałęzie wychodzące z tej decyzji:
- Gałąź 1: Warunek `A > B` jest prawdziwy (wykonuje się linia 4).
- Gałąź 2: Warunek `A > B` jest fałszywy (wykonuje się linia 6).
Aby osiągnąć 100% pokrycia gałęzi, potrzebujemy przypadków testowych, które wykonają obie te gałęzie:
- Przypadek testowy 1: A = 5, B = 3 (wykonuje gałąź 1).
- Przypadek testowy 2: A = 2, B = 4 (wykonuje gałąź 2).
W tym prostym przykładzie te same dwa przypadki testowe, które dały 100% pokrycia instrukcji, dają również 100% pokrycia gałęzi.
Jednak rozważmy inny przykład:
1. Czytaj X
2. Jeżeli X > 0 THEN
3. Drukuj "Dodatnia"
4. Koniec Jeżeli
5. Koniec
Instrukcje wykonywalne: 1, 2, 3, 5.
Gałęzie: Prawda dla `X > 0` (wykonuje linię 3), Fałsz dla `X > 0` (pomija linię 3).
- Przypadek testowy: X = 5. Wykonane instrukcje: 1, 2, 3, 5. Pokrycie instrukcji: 100%. Wykonana gałąź: Prawda. Pokrycie gałęzi: 50%.
Aby osiągnąć 100% pokrycia gałęzi, potrzebujemy drugiego przypadku, np. X = -1, który wykona gałąź Fałsz.
Wartość i ograniczenia:
- Wartość: Pokrycie gałęzi jest silniejszym kryterium niż pokrycie instrukcji. 100% pokrycia gałęzi implikuje 100% pokrycia instrukcji (każda instrukcja leży na jakiejś gałęzi). Testowanie gałęzi wymusza sprawdzenie obu wyników każdej decyzji, co zwiększa szansę na wykrycie błędów w logice warunkowej.
- Ograniczenia: Nadal nie gwarantuje wykrycia wszystkich błędów. Może nie wykryć błędów w złożonych warunkach (np. `IF (A > 0 AND B < 10)` – pokrycie gałęzi wymaga tylko jednego testu dla prawdy i jednego dla fałszu, ale niekoniecznie testuje wszystkie kombinacje A i B). Nie testuje też wszystkich możliwych ścieżek w programie, zwłaszcza w obecności pętli.
3. Korzyści Wynikające z Testowania Białoskrzynkowego
Dlaczego warto „zaglądać do środka” kodu, skoro techniki czarnoskrzynkowe weryfikują wymagania?
Testowanie białoskrzynkowe oferuje kilka istotnych korzyści (FL-4.3.3 K2):
- Wczesne wykrywanie defektów: Techniki te mogą być stosowane już na etapie kodowania (np. przez programistów piszących testy jednostkowe), co pozwala wykryć i naprawić błędy znacznie wcześniej i taniej niż w późniejszych fazach testów.
- Maksymalizacja pokrycia kodu: Pozwalają na systematyczne mierzenie i dążenie do wysokiego poziomu pokrycia kodu (instrukcji, gałęzi, a nawet bardziej zaawansowanych kryteriów). Daje to większą pewność, że kluczowe fragmenty logiki zostały przetestowane.
- Wykrywanie defektów trudnych do znalezienia przez techniki czarnoskrzynkowe: Błędy w specyficznych ścieżkach wykonania, obsłudze błędów wewnętrznych czy złożonej logice warunkowej mogą być trudne do sprowokowania i wykrycia tylko na podstawie analizy zewnętrznego zachowania.
- Optymalizacja testów: Analiza pokrycia kodu może wskazać obszary, które są niedostatecznie przetestowane, lub wręcz przeciwnie – obszary, gdzie testy są redundantne. Pozwala to na bardziej efektywne alokowanie zasobów testowych.
- Poprawa jakości kodu: Samo myślenie o tym, jak przetestować kod (np. aby osiągnąć pokrycie gałęzi), często prowadzi programistów do pisania prostszego, bardziej modularnego i łatwiejszego do testowania kodu.
- Ujawnianie „martwego kodu”: Analiza pokrycia może ujawnić fragmenty kodu, które nigdy nie są wykonywane przez żaden test (a być może w ogóle w aplikacji). Taki „martwy kod” może być usunięty, co upraszcza utrzymanie systemu.
Testowanie białoskrzynkowe jest więc cennym uzupełnieniem testowania czarnoskrzynkowego, dostarczając informacji o wewnętrznej jakości kodu i pomagając zapewnić jego solidność.
Podsumowanie
Techniki białoskrzynkowe, takie jak testowanie instrukcji i gałęzi, koncentrują się na wewnętrznej strukturze kodu.
Ich głównym celem jest zapewnienie odpowiedniego poziomu pokrycia kodu, co zwiększa zaufanie do jego poprawności.
Pokrycie gałęzi jest silniejszym kryterium niż pokrycie instrukcji.
Stosowanie technik białoskrzynkowych, zwłaszcza na niższych poziomach testów (komponentowe, integracyjne), przynosi liczne korzyści, w tym wczesne wykrywanie defektów i poprawę jakości samego kodu.