Techniki projektowania testów — biała skrzynka
Testowanie białoskrzynkowe wykorzystuje strukturę kodu do projektowania testów. Poznaj pokrycie instrukcji, decyzji i ścieżek — i dlaczego 100% pokrycia nie oznacza braku bugów.
📚 To jest Część 7 serii ISTQB Poziom Podstawowy. W poprzednim artykule omawialiśmy techniki projektowania testów czarnoskrzynkowych. Teraz zajrzymy do środka skrzynki.
1. Wprowadzenie — Zajrzenie do środka skrzynki
W poprzednim artykule traktowaliśmy system jak czarną skrzynkę: dane wchodzą, dane wychodzą, a my nie patrzymy na wnętrze. Techniki czarnoskrzynkowe są potężne właśnie dlatego, że działają na podstawie specyfikacji — mówią, co system powinien robić.
Ale testowanie czarnoskrzynkowe ma słaby punkt. Może testować tylko to, co specyfikacja opisuje. Jeśli w implementacji jest kod bez odpowiadającego wymagania — zapomniana wartość brzegowa, martwy kod, nieudokumentowane zachowanie domyślne — testy czarnoskrzynkowe nigdy do niego nie dotrą.
Tu wkracza testowanie białoskrzynkowe. Zamiast wyprowadzać testy z wymagań, wyprowadzasz je ze struktury samego kodu. Skrzynka jest teraz przezroczysta — widać każdą instrukcję, każdą gałąź, każdą ścieżkę.
Kluczowe różnice:
| Wymiar | Czarna skrzynka | Biała skrzynka |
|---|---|---|
| Podstawa | Wymagania / specyfikacje | Struktura kodu |
| Wymaga dostępu do kodu | Nie | Tak |
| Typowy poziom testów | Systemowe, akceptacyjne | Jednostkowe, integracyjne |
| Wykrywa | Defekty behawioralne | Defekty strukturalne |
| Miara pokrycia | Pokryte scenariusze | Wykonane elementy kodu |
Testowanie białoskrzynkowe jest stosowane głównie na poziomie testów jednostkowych i testów integracyjnych, gdzie programiści piszą testy do własnego kodu. Jest rzadziej stosowane na wyższych poziomach testów, gdzie implementacja może obejmować wiele usług.
ISTQB Poziom Podstawowy definiuje trzy kryteria pokrycia białoskrzynkowego:
- Pokrycie instrukcji
- Pokrycie gałęzi (decyzji)
- Pokrycie ścieżek
Przez cały artykuł będziemy używać jednego przykładu kodu, aby porównanie było konkretne.
Przykład przewodni
Przez cały artykuł będziemy używać tej pseudokodowej funkcji rabatu:
function discount(price, age):
if age >= 65:
return price * 0.8 // 20% rabat dla seniora
if price > 100:
return price * 0.9 // 10% rabat za wysoką wartość
return price // brak rabatu
Ta funkcja ma:
- 5 wykonywalnych instrukcji (oba warunki
ifliczą się jako instrukcje, plus trzy instrukcjereturn) - 3 gałęzie (Prawda/Fałsz dla każdego
iforaz przejście do ostatniegoreturn) - Wiele różnych ścieżek w zależności od tego, które warunki są prawdziwe
2. Pokrycie instrukcji (ISTQB 4.3.1)
Koncepcja
Pokrycie instrukcji mierzy, czy każda wykonywalna instrukcja w kodzie została wykonana przynajmniej raz.
Wzór: Pokrycie instrukcji = (Wykonane instrukcje ÷ Łączna liczba instrukcji) × 100%
Zestaw testów osiągający 100% pokrycia instrukcji uruchomił każdą linię kodu przynajmniej raz.
Zastosowanie do naszego przykładu
Funkcja discount ma 5 wykonywalnych instrukcji. Ile przypadków testowych potrzebujemy, aby wykonać je wszystkie?
Przypadek testowy 1: price = 120, age = 70
if age >= 65→ Prawdareturn price * 0.8✅ wykonano- Wynik: 96
Przypadek testowy 2: price = 120, age = 30
if age >= 65→ Fałszif price > 100→ Prawdareturn price * 0.9✅ wykonano- Wynik: 108
Przypadek testowy 3: price = 50, age = 30
if age >= 65→ Fałszif price > 100→ Fałszreturn price✅ wykonano- Wynik: 50
Wszystkie 5 instrukcji wykonanych. Pokrycie instrukcji: 100%.
Ograniczenia
Pokrycie instrukcji jest najsłabszym kryterium białoskrzynkowym. Rozważ co się dzieje przy uruchomieniu tylko Przypadków testowych 1 i 2: pokrycie instrukcji nadal wynosi 100%, ale nigdy nie przetestowaliśmy przypadku gdy age < 65 AND price <= 100.
Co gorzej: możesz wykonać każdą instrukcję return, całkowicie pomijając gałąź Fałsz sprawdzenia price > 100. Pokrycie instrukcji nie daje żadnej informacji o nieprzetestowanych wynikach decyzji.
:::tip[Wskazówka egzaminacyjna ISTQB] Pokrycie instrukcji to minimum, nie cel jakościowy. ISTQB traktuje je jako najsłabsze kryterium białoskrzynkowe. 100% pokrycia instrukcji można osiągnąć, pomijając wiele ważnych ścieżek decyzyjnych. :::
3. Pokrycie gałęzi / decyzji (ISTQB 4.3.2)
Koncepcja
Pokrycie gałęzi (zwane też pokryciem decyzji) wymaga, aby każda gałąź każdego punktu decyzji została wykonana — zarówno wynik Prawda, jak i wynik Fałsz każdego if, while, switch itp.
Pokrycie gałęzi obejmuje pokrycie instrukcji: osiągnięcie 100% pokrycia gałęzi automatycznie daje 100% pokrycia instrukcji. Odwrotność nie jest prawdą.
Zastosowanie do naszego przykładu
Funkcja discount ma dwie decyzje if, każda wymagająca przypadku testowego dla Prawdy i dla Fałszu:
Przypadek testowy 1: price = 120, age = 70
if age >= 65→ Prawda ✅
Przypadek testowy 2: price = 120, age = 30
if age >= 65→ Fałsz ✅if price > 100→ Prawda ✅
Przypadek testowy 3: price = 50, age = 30
if age >= 65→ Fałsz (już pokryte)if price > 100→ Fałsz ✅
Trzy przypadki testowe osiągają 100% pokrycia gałęzi. Co kluczowe, pokrycie gałęzi gwarantuje przetestowanie co się dzieje gdy każdy warunek jest fałszywy — czego samo pokrycie instrukcji nie zapewnia.
Praktyka branżowa
Pokrycie gałęzi jest de facto standardem dla testów jednostkowych. Narzędzia pomiarowe są dostępne dla każdej głównej platformy:
- C#/.NET: Coverlet — uruchamiany przez
dotnet test --collect:"XPlat Code Coverage" - JavaScript/TypeScript: Istanbul / NYC — zintegrowany z Jest i Vitest
- Java: JaCoCo
- Python: coverage.py
Większość zespołów egzekwuje minimum pokrycia gałęzi (zazwyczaj 70–80%) jako bramkę jakości w CI/CD.
:::tip[Wskazówka egzaminacyjna ISTQB] ISTQB używa terminu “pokrycie decyzji” dla tego, co programiści zazwyczaj nazywają “pokryciem gałęzi” — oznaczają to samo. Kluczowy fakt egzaminacyjny: pokrycie gałęzi/decyzji obejmuje pokrycie instrukcji. Przy 100% pokrycia gałęzi automatycznie masz 100% pokrycia instrukcji. :::
4. Pokrycie ścieżek (ISTQB 4.3.3)
Koncepcja
Pokrycie ścieżek wymaga, aby każda możliwa ścieżka wykonania przez kod została przetestowana. “Ścieżka” to unikalna sekwencja gałęzi od wejścia do wyjścia funkcji.
Dla funkcji discount istnieją trzy różne ścieżki:
age >= 65jest Prawdą →return price * 0.8age >= 65jest Fałszem →price > 100jest Prawdą →return price * 0.9age >= 65jest Fałszem →price > 100jest Fałszem →return price
Trzy przypadki testowe pokrywają wszystkie ścieżki w tej małej funkcji. Ale rozważ co się dzieje przy bardziej złożonym kodzie.
Dlaczego pokrycie ścieżek jest zazwyczaj niepraktyczne
Liczba ścieżek rośnie wykładniczo wraz z liczbą punktów decyzji:
| Punkty decyzji | Maksymalna liczba ścieżek |
|---|---|
| 5 | 32 |
| 10 | 1 024 |
| 20 | 1 048 576 |
| Dowolna pętla | Potencjalnie nieskończona |
Dla każdej nietrywialnej funkcji 100% pokrycia ścieżek jest kombinatorycznie niewykonalne. To teoretyczny ideał, nie praktyczny cel.
MC/DC — Praktyczna alternatywa
W systemach o krytycznym znaczeniu dla bezpieczeństwa (lotnictwo wg DO-178C, motoryzacja wg ISO 26262) kryterium zwane Modified Condition/Decision Coverage (MC/DC) zapewnia znaczną część rygorystyczności pokrycia ścieżek przy ułamku liczby testów.
MC/DC wymaga, aby każdy indywidualny warunek w decyzji niezależnie wpływał na wynik. ISTQB Poziom Podstawowy wymaga jedynie świadomości istnienia MC/DC; jest ono szczegółowo omawiane na Poziomie Zaawansowanym.
:::tip[Wskazówka egzaminacyjna ISTQB] Na egzaminie Poziomu Podstawowego zapamiętaj hierarchię: pokrycie ścieżek jest najsilniejszym, ale najmniej praktycznym kryterium. ISTQB oczekuje znajomości zależności: Instrukcje ⊂ Gałęzie ⊂ Ścieżki — każde kryterium obejmuje poprzednie. :::
5. Mit pokrycia kodu
Oto zdanie, które często usłyszysz w zespołach programistycznych:
“Mamy 80% pokrycia kodu. Jesteśmy bezpieczni.”
Sprawdźmy, dlaczego może to być mniej uspokajające niż brzmi.
:::danger[Krytyczne nieporozumienie] 100% pokrycia kodu nie oznacza, że kod jest poprawny. Pokrycie mierzy które linie zostały wykonane, a nie czy zachowanie zostało zweryfikowane. Można osiągnąć 100% pokrycia instrukcji bez żadnych asercji i nie wykryć żadnych błędów. :::
Rozważ ten zestaw testów:
// Osiąga 100% pokrycia instrukcji. Nie wykrywa żadnych błędów.
test("funkcja discount uruchamia się bez błędów") {
discount(120, 70); // brak asercji — wynik ignorowany
discount(120, 30); // brak asercji — wynik ignorowany
discount(50, 30); // brak asercji — wynik ignorowany
}
Każda instrukcja jest wykonana. Żadne zachowanie nie jest weryfikowane. Funkcja mogłaby zwracać nieprawidłowe wartości, a ten zestaw testów przeszłby bez problemu.
Co naprawdę mówi pokrycie
Pokrycie to narzędzie diagnostyczne, nie miara jakości:
- Wysokie pokrycie informuje: kod był wykonywany podczas testowania
- Wysokie pokrycie NIE informuje: czy zachowanie było poprawne, czy przetestowano wszystkie scenariusze, czy asercje były znaczące
Pokrycie najlepiej służy do znajdowania luk — obszarów kodu, które nigdy nie były wykonywane, co sugeruje brakujące testy. Jest użyteczną podłogą (“powinniśmy przynajmniej wykonać każdą gałąź”), ale mylącym sufitem (“gdy wykonamy każdą gałąź, testowanie jest skończone”).
Używanie pokrycia jako KPI tworzy przewrotne motywacje: zespoły piszą testy wykonujące kod bez weryfikowania zachowania, wyłącznie po to, by osiągnąć docelowy wskaźnik.
Lepsze ujęcie: Pytaj “jakie scenariusze pokrywa nasz zestaw testów?” obok pytania “jaki procent gałęzi wykonuje?”. Oba pytania są ważne; żadne z nich nie jest samodzielnie wystarczające.
6. Łączenie testowania białoskrzynkowego i czarnoskrzynkowego
Najskuteczniejsza strategia testowania łączy oba podejścia, stosowane na właściwych poziomach.
Zalecany przepływ pracy:
-
Napisz najpierw testy czarnoskrzynkowe (na podstawie wymagań): Wyprowadź przypadki testowe ze specyfikacji używając EP, BVA, tablic decyzyjnych i przejść stanów. Testy te są niezależne od implementacji — pozostają ważne, nawet jeśli kod zostanie całkowicie przepisany.
-
Uruchom analizę pokrycia (obiektyw białoskrzynkowy): Po napisaniu testów czarnoskrzynkowych zmierz pokrycie gałęzi. Każdy kod niepokryty przez testy czarnoskrzynkowe sygnalizuje lukę: brakujące wymaganie, nieprzetestowany przypadek brzegowy lub martwy kod.
-
Dodaj ukierunkowane testy białoskrzynkowe dla luk strukturalnych: Napisz dodatkowe testy celujące w niepokryte gałęzie i ścieżki. Testy te są specyficzne dla implementacji, ale zapewniają faktyczne przetestowanie kodu.
Ten przepływ pracy tworzy zestaw testów, który jest napędzany wymaganiami (każdy test ma jasny powód istnienia) i strukturalnie kompletny (implementacja jest faktycznie testowana).
Typowe podejście według poziomów testów:
| Poziom testów | Główne podejście | Cel pokrycia |
|---|---|---|
| Testy jednostkowe | Biała + Czarna skrzynka | Pokrycie gałęzi ≥ 70–80% |
| Testy integracyjne | Czarna skrzynka (głównie) | Pokrycie instrukcji |
| Testy systemowe | Czarna skrzynka (wyłącznie) | Pokrycie scenariuszy |
| Testy akceptacyjne | Czarna skrzynka (wyłącznie) | Pokrycie scenariuszy biznesowych |
7. Podsumowanie
Techniki białoskrzynkowe zapewniają uzupełniającą perspektywę względem testowania czarnoskrzynkowego — gwarantują testowanie implementacji, a nie tylko specyfikacji.
Hierarchia pokrycia:
Pokrycie ścieżek (najsilniejsze — niepraktyczne dla prawdziwego kodu)
↑ obejmuje
Pokrycie gałęzi / decyzji (standard branżowy dla testów jednostkowych)
↑ obejmuje
Pokrycie instrukcji (minimalna linia bazowa)
Kluczowy wniosek: Pokrycie to narzędzie diagnostyczne, nie gwarancja. 80% pokrycia gałęzi ze znaczącymi asercjami jest dużo wartościowsze niż 100% pokrycia instrukcji bez żadnych.
Działanie: Uruchom raport pokrycia dla bieżącego projektu (dotnet test --collect:"XPlat Code Coverage" lub jest --coverage). Znajdź pięć funkcji z najniższym pokryciem gałęzi. Dla każdej z nich zapytaj: czy ta luka jest zamierzona, czy jest scenariusz wart przetestowania?
To jest Część 7 serii ISTQB Poziom Podstawowy.