Kosztowne i nie lubiane

Testowanie jest integralnym składnikiem procesu kontroli jakości. Zabiera ono nawet ponad 40% kosztu oprogramowania, a w systemach o dużych wymaganiach niezawodnościowych sięga wartości pięciokrotnego kosztu innych faz projektu. Wydawać by się mogło, że naturalne jest, aby tak ważny i kosztowny etap realizacji projektu był przeprowadzony według sprawdzonych reguł sztuki inżynierskiej. W praktyce sprowadza się to, niestety, do zespołowego: "No to panowie - testujemy?"

Testowanie jest integralnym składnikiem procesu kontroli jakości. Zabiera ono nawet ponad 40% kosztu oprogramowania, a w systemach o dużych wymaganiach niezawodnościowych sięga wartości pięciokrotnego kosztu innych faz projektu. Wydawać by się mogło, że naturalne jest, aby tak ważny i kosztowny etap realizacji projektu był przeprowadzony według sprawdzonych reguł sztuki inżynierskiej. W praktyce sprowadza się to, niestety, do zespołowego: "No to panowie - testujemy?"

Dlaczego testujemy?

Wady produktów mogą wynikać z trzech głównych przyczyn:

- wady spowodowane procesem wytwarzania produktu, tzw. wady wrodzone

- uszkodzenia losowe na skutek obciążeń produktu podczas użytkowania

- uszkodzenia wskutek zużycia, spowodowane zmianami fizycznych właściwości produktu.

Dzięki niematerialnej naturze oprogramowania może ono być niewrażliwe na dwa ostatnie źródła uszkodzeń, gdyż nie ma fizycznej postaci. Głównym czynnikiem kształtującym poziom niezawodności oprogramowania jest więc proces produkcji, który wbudowuje w oprogramowanie nie tylko zamawianą przez klienta funkcjonalność, ale także oddziałuje na wszelkie problemy, niezgodności, niespójności i błędy w zrozumieniu wymagań.

Błędy oprogramowania w odróżnieniu od uszkodzeń sprzętu, które uniemożliwiają jego uruchomienie lub powodują, że przestaje działać, rzadko stają się natychmiast widoczne. Mogą być skutkiem błędów programistów albo wynikiem przyczyny zewnętrznej (np. zamazania obszaru kodu przez inny program). Wada w programie nie musi powodować błędu lub uszkodzenia. Może upłynąć wiele czasu, zanim wadliwa część programu zostanie użyta w warunkach sprzyjających powstaniu uszkodzenia. Często zdarza się także, że pewien błąd wprowadza program w stan, z którego nie można go wyprowadzić, inaczej jak przez równoczesne wciśnięcie "trzech klawiszy". Jeśli nawet program może funkcjonować w stanie "niepewności", to najczęściej tylko po to, aby systematycznie rozszerzać zakres "choroby", doprowadzając do trwałego uszkodzenia programu i stopniowej lub "gwałtownej" degradacji jego funkcjonalności bądź danych.

Trudności z diagnozowaniem błędów polegają także na tym, że błędy oprogramowania nie są zależne od czasu, gdyż ujawniają się wskutek uruchomienia błędnych ścieżek programu lub pojawienia się powodującej błąd kombinacji danych.

Każde wykonanie programu jest właściwie tylko małą próbką pełnego grafu wywołań modułów, procedur, ścieżek i instrukcji. Nawet względnie proste systemy mają praktycznie nieskończoną liczbę kombinacji możliwych wejść i wyjść. Zautomatyzowane metody testów mogą przebadać tylko niewiele większą liczbę ścieżek programu. Liczba błędów pozostających w naszych systemach jest więc (statystycznie rzecz biorąc) wciąż znaczna.

Nie od dziś znana jest prawda, że większość błędów, a szczególnie te najpoważniejsze powstają w fazie analizy wymagań. Problem polega na tym, że do projektowania i kodowania używa się sformalizowanych języków opisu, podczas gdy wymagania użytkownika są wyrażone językiem naturalnym. Stwarza to możliwość powstawania niezgodności spowodowanych niezrozumieniem i niespójnością wymagań, a te z kolei prowadzą do błędów w programie. Problemy tego rodzaju są bardzo trudne do wykrycia podczas przeglądów projektu i testów, gdyż procesy walidacji odnoszą się tylko do takich wymagań, jakie postawiono i wyspecyfikowano. Nie przewidziane sytuacje zawsze mogą się pojawić już w trakcie działania programu i użytkownika.

Poniżej zebrano kilka ważniejszych źródeł wad:

Wymagania

- nieprawidłowe

- niespójne

- niejasne

- nielogiczne

- opuszczone.

Projekt

- zła strukturalizacja

- zła specyfikacja

- brak kontroli zmian

- brak zrozumienia wymagań

- brak zrozumienia oczekiwań.

Kodowanie

- błędy semantyczne

- błędy logiczne w tłumaczeniu projektu na kod

- szczegółowe błędy syntaktyczne

- zła walidacja danych

- nie inicjalizowane lub błędnie użyte zmienne

- niewystarczająca dokładność arytmetyczna

- niedopasowanie typu danych

- brak obsługi sytuacji wyjątkowych

- brak obsługi błędów

- błędy kompilatorów.

Koszty usuwania błędów w późniejszych fazach projektowych rosną lawinowo. Dlatego nieekonomiczne jest pozostawianie kontroli błędów "na ostatnią godzinę". Głównym sposobem eliminacji błędów powinno być zapobieganie im, przede wszystkim przez przeniesienie środków walki z błędami na wcześniejsze fazy realizacyjne. Wysoka jakość wymagań modeli analitycznych i projektu bardzo pomaga w wytworzeniu kodu o podobnej jakości. Natomiast niska jakość projektu nie pomaga, a wręcz uniemożliwia stworzenie programu wysokiej jakości.

Co to jest testowanie?

W powszechnym rozumieniu testowanie jest procesem poszukiwania błędów w działającym oprogramowaniu. Fazy analizy, projektu i wdrożenia są jedynie kolejnymi etapami transformacji różnych postaci specyfikacji, aż do momentu, gdzie zamawiany przez użytkownika program jest opisywany za pomocą konkretnych instrukcji języka programowania. Kod programu to w istocie dane sterujące dla automatu, jakim jest procesor komputera. Automat ten jest równie potężny, co bezmyślny. Z konsekwencją realizuje zadania, do których został skonstruowany - przetwarza ciągi danych binarnych bez chwili refleksji. Uszkodzenie systemu powstaje wtedy, gdy traci on swoją funkcjonalność na skutek błędów, a te z kolei są efektem wykonania przez procesor instrukcji, które wskutek pomyłki programistów nie realizują wymaganej funkcji, lecz zupełnie inną, czasem nawet szkodliwą dla programu, systemu czy użytkownika.

Jest kilka definicji testowania, przy czym dzielą się one na dwie główne grupy. Pierwsza z nich to definicje teoretyczne, uzasadnione merytorycznie i formułowane w postaci postulatów, z których wiele wynika do procesu testowania. Druga grupa to definicje praktyczne, wynikające z pragmatyki inżynierii i codziennej praktyki naszych projektów. Te drugie są ważniejsze, bo są stosowane na co dzień. Niestety, nie są one tak doskonałe, jak te pierwsze.

Przedstawicielem pierwszej grupy jest bardzo często cytowana w literaturze definicja Myers'a: testowanie jest procesem wykonywania programu z zamierzeniem znalezienia błędu. Podkreśla ona główny cel testów. Ponieważ o tym, jak zadziała program, "wie" właściwie tylko procesor, wykonujący instrukcje maszynowe w złożonym środowisku systemu operacyjnego, to przed przekazaniem programu użytkownikowi wypada choć raz sprawdzić, czy to, co robi oprogramowanie, jest rzeczywiście tym, co zamawiał użytkownik.

W tym ujęciu testowanie koncentruje się nie na poszukiwaniu błędów programowania, niewłaściwego spełnienia wymagań, złej modularyzacji itd. Błędy logiczne powinny być wyeliminowane przez procesy kontroli jakości we wczesnych fazach projektu. Toteż testowanie jest związane z błędami wdrożenia, tzn. procesu programowania założonej funkcjonalności aplikacji. Nie chodzi o stwierdzenie, czy program realizuje jakąś funkcję. Idzie o praktyczne zweryfikowanie, czy funkcja, o której wiemy, że jest realizowana, została poprawnie zaprogramowana przez zespół programistów. Jest to teoria, gdyż w większości znanych przypadków testy przeprowadza się także po to, aby ujawnić ukryte dotąd błędy, które powstały w trakcie etapów: określania wymagań, projektowania i kodowania, a które mogły być przeoczone przez kontrole jakości w poprzednich fazach.

Z tej krótkiej charakterystyki idei testowania wynika wiele dla zarządzających jakością oprogramowania. Wyraźnie widać, że testowanie ma określony cel, który wynika z natury procesu wytwarzania oprogramowania, i nie może być zastąpiony innymi, bo nie jest do tego przeznaczony, tzn. techniki, której używa się pod kątem jego realizacji. Dlatego proces testowania musi być uzupełniony innymi działaniami kontrolno-weryfikacyjnymi, tak by system różnorodnych oddziaływań na jakość produktu był pełny, tzn. ograniczał propagację błędów z wcześniejszych faz projektowych do późniejszych. Z tych względów często testowanie utożsamia się z pojęciem kontroli jakości, obejmując tym procesem także i przeglądy dokumentacji, modeli wymagań, projektu itd.

Testy nie są drobnym i marginalnym fragmentem cyklu "życia" projektu/produktu. Niełatwo przewidzieć wszystkie możliwe rodzaje uszkodzeń, które mogą wystąpić w programie, a szerzej w systemie. Trudno zatem zbudować pełny zakres lub strukturę testów, które je wykryją. Usuwanie wad i korekta błędów mogą zająć nawet połowę czasu pracy projektanta. Toteż każde efektywne udoskonalenie metodologii testów lub jej zautomatyzowanie, pozwalające zmniejszyć nakłady czasu zużywanego na testy, ma ważny wpływ na czas projektowania i koszt cyklu prac. Pamiętajmy o tym, gdy będziemy rozważać możliwość wsparcia prac zespołów testów przez różnego rodzaju narzędzia. Każda inwestycja w usprawnienie i doskonalenie procesu testowania bardzo się opłaca. Jest to widoczne w przypadku testów regresji, które dotyczą funkcjonalności już przetestowanej, a która mogła ulec degradacji na skutek efektów ubocznych zmian, np. w innym module programu. Potrzeba takich testów jest wynikiem natury błędów oprogramowania, które objawiają się nie w czasie, lecz w dynamice realizacji funkcji wykonywanych w kontekście danych wejściowych. To, że funkcja wczoraj działała, nie oznacza, iż będzie działać dzisiaj. Przeprowadzanie testów regresji bez wsparcia narzędziowego oznacza wykonywanie setki razy tych samych testów. Nie znam człowieka, który za 500. razem nie jest już tym procesem tak znudzony i zmęczony, że nie dokona pewnego "skrótu", podważającego wiarygodność przeprowadzanego testu!

Reprezentantem drugiej grupy definicji jest stwierdzenie: Testowanie jest procesem sprawdzania, czy w oprogramowaniu nie ma błędów. Tutaj sytuacja jest odwrotna. Testujemy, szukając "nie-błędów". Testy są dobierane w ten sposób, aby wykazać, że nie ma błędów i wszystko działa poprawnie. Przypadki testowe są dobierane pod kątem najbardziej i najmniej reprezentatywnych, ale za to takich, dla których program działa poprawnie. Takie ujęcie testów oprogramowania wynika ze stałego braku czasu w projekcie. Nie ma go na zaprogramowanie funkcjonalności, a tym bardziej na testy czy inne metody kontroli jakości.

Nie chcę rozpisywać się nad charakterystyką niewłaściwego - moim zadaniem - podejścia do testów. Chcę tylko zasygnalizować problem, że skoro zewsząd krytykuje się jakość oprogramowania i przedsięwzięć informatycznych, to ma to jakąś przyczynę. Moim zdaniem, wynika ona z niezrozumienia istoty testów i sprowadzenia ich roli do generalnego, uniwersalnego i ostatecznego narzędzia kontroli jakości programów. Problem nie w tym, że jest to jedno i uniwersalne narzędzie. Kłopot w tym, że jest to narzędzie jedyne.

ABC testowania

Z poprzedniego akapitu wynika, że bez względu na to, czy robimy to dobrze, czy źle, wszyscy testujemy nasze oprogramowanie i praktycznie znamy związane z nim zagadnienia. Dlatego tylko kilka uwag.

Podstawą do wykonywania testów są kryteria testowe, związane z kryteriami jakości. Testujemy program, aby sprawdzić, czy nie ma błędów w realizacji jego funkcji, czy jest tak efektywny jak zakładano, czy jest bezpieczny, przyjazny użytkownikowi. Testowanie jest procesem "pomiaru jakości" oprogramowania, i to jakości rzeczywistej, takiej, jaką wbudowano w produkt.

Kryteria testowe mogą być sprawdzane na różnym poziomie przez dowolne role w projekcie oraz w różnych jego fazach. Najogólniej mówimy o testowaniu funkcjonalnym (black-box-testing), kiedy szukamy błędów i niezgodności ze specyfikacją w określonej i zamkniętej części programu. Interesuje nas reakcja "czarnej skrzynki" na dostarczane dane wejściowe. Błędne zachowania to te, które są niezgodne ze specyfikacją. Jeśli znana jest struk- tura i logika mo- dułów i procedur, wtedy sprawdzamy wewnętrzny sposób realizacji zewnętrznych specyfikacji oraz tzw. pokrycie ścieżek. Jest to white-box-testing. Chodzi tu o sprawdzenie czy procedura lub moduł znajdują się zawsze w oczekiwanym stanie, czy są błędy w algorytmach realizacji funkcji, jak napisano obsługę sytuacji awaryjnych, czy wykonano wszystkie możliwe ścieżki itd.

W zależności od stanu zaawansowania prac nad modułami oprogramowania, testowanie przechodzi przez różne fazy. Podstawowe fazy procesu testowania scharakteryzowano w tabeli 1.

Duże znaczenie ma psychologia testów. Projektant i programista są dobrymi testerami na poziomie procedury-modułu, ponieważ bardzo dobrze znają ich konstrukcję i możliwe źródła błędów, a więc tak skonstruują testy, aby wykazać jak najwięcej błędów. To ostatnie stwierdzenie nie musi być jednak prawdą. Natura ludzka jest taka, że nikt nie lubi wystawiać się na pośmiewisko kolegów, a tym bardziej szefa. Tester, który jest autorem programu, nigdy nie będzie obiektywny, bo to tak, jakby zlecić mu znalezienie, wykazanie i opublikowanie wszystkich wad rozwojowych własnego "dziecka". Dlatego testowanie powinien przeprowadzać niezależny tester. Jego zadaniem jest znaleźć błąd. Taka jest jego rola i musi wykonywać ją rzetelnie i bez "sentymentów". To, co się liczy, to jego zaangażowanie i liczba znalezionych błędów.

Jak testować?

Są różne sposoby wykonywania testów: testowanie na ekranie, testowanie z podręcznikiem użytkownika, "obklikiwanie" aplikacji, użytkowanie jej przez użytkownika, zaplanowane i przygotowane sesje testowe.

Testy mogą przeprowadzać: programiści, zarząd firmy, pospolite ruszenie, brygada "tygrysa", sztab antykryzysowy, użytkownicy, niezależni testerzy.

Wyniki testów można: zachować dla siebie (częste zjawisko u programistów), powiedzieć Wojtkowi, omówić z kierownikiem, zapytać o nie użytkownika, dokumentować i opracowywać statystycznie.

Ciekaw jestem, ilu z czytelników po przeczytaniu powyższych akapitów rozpoznało w nich praktyki stosowane w firmach informatycznych. Moje doświadczenia pokazują, że niewielu szefów projektów przejmuje się faktem, że testowanie jest po analizie wymagań najtrudniejszą fazą cyklu "życia" projektu. Koszt testów, i tak olbrzymi, jest wielokrotnie zwiększany przez niesystematyczne, nie uporządkowane, a czasem wręcz "amatorskie" podejście do testów. Efekty takich praktyk widzimy na co dzień.

W czym tkwi tajemnica skutecznego testowania. Myślę, że w wyobraźni. Nie chodzi bowiem o bardzo nowoczesne metody testów, narzędzia, organizację itd. Wyobraźnia inżynierska powinna podpowiadać, że skoro mam coś zrobić to muszę znaleźć sposób na to, aby udowodnić, iż to zrobiłem. Tak więc o testach trzeba myśleć już na etapie analizy wymagań. Z drugiej strony, tworząc projekt, muszę zdawać sobie sprawę, że będą go kodować programiści, a program będzie testowany. Trzeba więc na etapie projektu przygotować się do testowania. Oprogramowanie musi mieć modularną strukturę pozwalającą na łatwe testowanie, musi istnieć dokumentacja projektowa, specyfikacje wymagań, interfejsów, kryteria efektywności, bezpieczeństwa itp. Bez tego nie da się przygotować skutecznych procedur testowych. Oprogramowanie także straci na niezawodności.

Kolejnym składnikiem "wyobraźni testowej" w projekcie jest planowanie testów. Najpierw strategiczne, formułujące koncepcję testów najbardziej adekwatną do specyfiki projektowanego systemu, potem operacyjne, organizujące pracę, produkty testowe i fazy testów, oraz taktyczne, a więc procedury i przypadki testowe oraz nadzorowanie wykonania testów.

Ostatnim elementem, który łączy wszystkie poprzednie, jest repozytorium testowe. Stanowi ono swego rodzaju bazę wszelkich informacji dotyczących testów w projekcie. Są tam niezbędne dokumenty, z których łatwo mogą korzystać testerzy, gotowe do wielokrotnego wykorzystania procedury testowe i zestawy danych testowych. Jest to także najwłaściwsze miejsce do rejestru błędów z historią ich obsługi, a także statystyk i charakterystyk niezawodnościowych systemu, podsystemów, modułów itd. Jest to swoista hurtownia danych testowych, pozwalająca na różnego rodzaju wielowymiarowe analizy stanu produktu, przewidywanego poziomu niezawodności itd.

Podsumowanie

Testowanie jest procesem ukierunkowanym na wykrycie błędów w oprogramowaniu. Paradoksalnie kończy się sukcesem tylko wtedy, gdy udało się wykryć błędy. Brak błędów jest ciszą przed burzą. Oznacza, że nie szukamy błędów, tam gdzie są naprawdę, albo stosujemy niewłaściwe metody poszukiwania. Tak rozumiane testowanie jest procesem destrukcyjnym, celowo zmierzającym do zanegowania dotychczasowych osiągnięć zespołu projektowego. Testowanie trudno jest dobrze przeprowadzić. Jeszcze trudniej uczynić je skutecznym narzędziem kontroli jakości. Dlatego bardziej opłaca się poświęcić więcej czasu na przeglądy i inspekcje projektu oraz kodu programu niż zdawać się na łaskę bezdusznej maszyny, która przecież nic nie rozumie...

Tomasz Byzia jest konsultantem InfoViDE.