Analiza poprawności kodu
Wstęp.
Testowanie jest bardzo ważnym etapem w procesie tworzenia oprogramowania. Jego
rola jest niestety często lekceważona. Cierpią na tym zarówno użytkownicy jak
i firmy tworzące i rozprowadzające naszpikowany błędami software (cierpi na
tym ich dobre imie, ale także i portfel)..
Dowodzenie poprawności działania programów w sposób formalny byłoby idealnym
rozwiązaniem. Nie da się jednak tego zastosować w większości typowych przypadków
przy tworzeniu dużych aplikacji. Dlatego powstało wiele metod i narzędzi ułatwiających
testowanie. Prezentacja ta ma na celu ich omówienie.
Kilka powodów dlaczego powinno się testować programy:
- po przetestowaniu i wyeliminowaniu błędów program będzie
najprawdopodobniej działał lepiej
- im wcześniej wykryje się błąd tym łatwiejsze i co za tym
idzie tańsze będzie jego usunięcie.
- wczesne wykrycie błędych założeń sprawia, że nie marnujemy czasu na pisanie
niepotrzebnych linijek kodu
Testy dzielimy na:
- poprawnościowe
- wydajnościowe
- długotrwałego działania
- obciążenia-przeciążenia.
Nigdy nie możemy być pewni, że udało nam się już wyeliminować wszystkie błędy!
Testy jedynie minimalizują ryzyko ich wystąpienia.
Błędy.
Z raportu Software Lifecycle, sporządzonego przez National Cybersecurity Partnership's
Working Group, wynika, że w komercyjnym oprogramowaniu występuje od
jednego do siedmiu błędów na 1000 linii kodu!!!
Przy tych danych Linux wydaje się być systemem niemal bezbłędnym. Firma Coverity
podała, że w 5 milionach 700 tysiącach linii kodu, składających się na najnowsze
jądro Linuksa, znaleziono 985 błędów (0,17 błędu na 1000 linii kodu).
Co ciekawe okazuje się, że ilość błędów przypadających na 1000 linii kodu w
komercyjnym oprogramowaniu nie zmienia się znacząco na przestrzeni lat.
Różne podejścia do problemu testowania poprawności kodu.
Analiza statyczna\dynamiczna.
Podział ze względu na etap w którym odbywa się testowanie.
Testowanie statyczne
- odbywa się na etapie tworzenia programu (przed jego uruchomieniem).
- sprawdza poprawność kodu pod kontem syntaktycznym
i semantycznym.
Dużą część tych testów wykonuje za nas kompilator. Istnieją
niestety błędy przy których znalezieniu jesteśmy zdani jedynie na siebie (np.
rozmieszczenie nawiasów w wyrażeniach logicznych lub odwoływanie się do zmiennej
dynamicznej, której nie zainicjalizowaliśmy).
Testowanie dynamiczne.
- odbywa się po stworzeniu programu i jego uruchomieniu.
- sprawdza działanie programów w przypadkach szczególnych (metoda
białej skrzynki) oraz jego ogólną funkcjonalność (metoda czarnej skrzynki).
Metody czarnej i białej skrzynki.
Podział ze względu na stopień znajomości struktury programu.
Metoda białej skrzynki.
- odbywa się w oparciu o znajomość kodu testowanego programu.
- w ten sposób testujemy określone fragmenty programów w celu znalezienia
przypadkach szczególnych, które powodują błędy.
- zajmujemy się jedynie fragmentami kodu w których występuje ryzyko wystąpienia
takich przypadków pomijając te których poprawnego działania jesteśmy pewni.
Metoda czarnej skrzynki.
|
- odbywa się w oparciu jedynie o znajomośc interfejsów konkretnych modułów/funkcji
programu lub interfejsu całego systemu oraz ogólnego przeznacznia testowanej
aplikacji.
- testy te powinny być realizowane przez osoby nie zaangażowane bezpośrednio
w proces tworzenia kodu.
- testuje ogólną funkcjonalność i poprawność jednak cieżko jest w ten
sposób wychwycić przypadki szczególne.
|
Metodologia wykonywania testów funkcjonalnych\strukturalnych.
Testy funkcjonalne.
Jakość tych testów zależy głównie od sposobu wyboru danych testowych:
- Wykonanie testów dla losowego zestawu danych nie gwarantuje
poprawnego działania programu dla wszystkich danych.
- Im większa liczba testowanych danych tym lepiej.
- Im większa różnorodność testowanych danych tym lepiej.
- W celu zapewnienia lepszej jakości testów przeprowadza się
je między innymi dla granicznych wartości danych.
Testy strukturalne.
Najlepszą jakość tych testów zapewni badanie poprawności kodu
tak, żeby:
- każda instrukcja była wykonana przynajmniej raz - (pokrycie
wszystkich instrukcji).
- każdy warunek logiczny był przynajmniej raz spełniony i niespełniony - (pokrycie
instrukcji warunkowych).
Testowanie na różnych etapach projektu.
Podział ze względu na wielkość testowanych elementów:
- Testowanie jednostek - sprawdzanie poprawności
implementacji każdej jednostki programu (np. funkcji, metody, struktury danych).
- Testowanie zintegrowane - sprawdzanie poprawności
komponentów (klasy, moduły) i zależności między nimi.
- Testowanie systemu - sprawdzenie aplikacji jako całości.
Testowanie mutacyjne (testowanie testów).
Pisząc testy prawie nigdy nie możemy mieć pewności, że wykryją
one wszystkie błędy testowanego programu.
Istnieje całkiem prosta metoda poprawiania jakości testów. Nazywa
się: testowanie mutacyjne.
Działa w następujący sposób:
- Kiedy zbiór testów nie wykrywa już żadnych nowych błędów
w naszym programie, wprowadzamy celowo drobny błąd (błędy) w kodzie tego programu
(może to być zmiana + na - albo dodanie linijki zwiększającej którąs ze zmiennych
o 1)
- Tak zmieniony program nazywamy mutantem.
- Teraz uruchamiamy zbiór testów na mutancie.
- Jeśli nie został wykryty żaden błąd to powinniśmy się oczywiście
zaniepokoić i rozszerzyć zbiór testów o taki, który wykryje ten błąd.
Jeśli błąd został wykryty przez wyjściowy zbiór testów to nic nie musimy robić.
- Jeśli tylko mamy ochotę możemy powtórzyć ten algorytm wprowadzając inny
błąd do naszego programu.
Oczywiście zastosowanie tej metody nie zapewnia nam uzyskania idealnego zestawu
testów. Wiemy jedynie, że będzie niegorszy od wyjściowego.
To czy uda nam się w ten sposób rozszerzyć zbiór testów na tyle, żeby wykryć
rzeczywisty błąd naszego programu zależy w dużej mierze od tego jak bardzo podobne
do tego błędu są zaburzenia jakie wprowadzamy do tego programu.
Automatyzacja przeprowadzania testów.
Standardowe metody testowania stosowane przez programistów:
- dopisywanie w kodzie programu linijek, które wyświetlają informacje o jego
działaniu.
- uruchamianie programu dla różnych zestawów danych testowych wpisywanych
ręcznie.
- debugger
Metody te są skuteczne, ale ich wielokrotne stosowanie po dodaniu kolejnych
poprawek jest pracochłonne i niezbyt wygodne. Programista łatwo może się zniechęcić
i przez to nie będzie mógł wykonywać testów tak często jakby chciał.
Dlatego powstało wiele programów, które autoamtyzują proces testowania.
Dwie techniki automatyzowania testów:
- pisanie skryptów testowych
Do tego celu została stworzona rodzina programów xUnit w skład której wchodzą
programy takie jak: JUnit, CppUnit, PearlUnit, VBUnit, NUnit(C#, .Net), pyUnit
(Python), SUnit (Smalltalk). Praktycznie dla każdego języka programowania
istnieje odpowiedni program z rodziny xUnit. Programy te nie są niestety wygodne
w użyciu, gdyż pisanie w nich testów wymaga mimo wszystko dużego nakładu pracy.
W celu rozwiązania tego problemu powstają odpowiednie nakładki, które udostępniają
o wiele bardziej przyjazny interfejs (np. JTest to nakładka na JUnit). Nakładki
te nie są już niestety w większości przypadków darmowe.
Omówienie programu JUnit.
- capture/playback
Technika polegająca na rejestrowaniu za pomocą specjalnych programów
(np. Rational Robot, Panorama/Playback) zachowania użytkownika, a następnie
dokładne odtwarzanie go. Rejestrowane są takie zachowania jak np. kliknięcie
myszką, wpisanie jakiejś wartości w pewne pole itp.
Główne zalety automatyzacji testów:
- możliwość odtworzenia identycznego testu - jest to szczególnie przydatne
kiedy chcemy sprawdzić, czy wprowadzone przez nas poprawki rozwiązały problem..
- dokladna analiza testów - program po zakończeniu testowania dostarcza informacje
o jego przebiegu.
- znaczny wzrost prędkości testowania - programy testujące przeprowadzają
testy znacznie szybciej niż zwykli testerzy.
- możliwość podania dużej liczby danych testowych - testy zyskują na jakości.
Strona główna