Rozdział 1: Testowanie programów 13
rozdział 1
Testowanie programów
1.1. Cel ćwiczenia
Celem ćwiczenia jest zapoznanie się z wybranymi metodami testowania oprogramowania i typowymi środkami pomocnymi w testowaniu oraz wykorzystanie ich w praktyce, w wybranym systemie programowania.
Każdy program powinien być przede wszystkim niezawodny. Pojęcie niezawodności można rozumieć, w pewnym uproszczeniu, jako brak podatności na awarie oprogramowania. Przyjmijmy jako definicję awarii określenie sformułowane przez J. Goodenougha:
Awaria to każdy efekt lub zachowanie, którego wystąpienie jest niedopuszczalne w normalnych warunkach działania, gdzie to, co „niedopuszczalne” i „normalne”, musi być określone dla każdego systemu.
Wówczas przykładami awarii mogą być błędne wyniki lub brak wyników, zmniejszenie efektywności programu (zaskakująco długie przetwarzanie dla pewnych danych) lub niszczenie danych użytkownika. Działania takie mogą być spowodowane błędami w strukturze programu lub niepoprawnym wypełnianiem specyfikacji.
Niezawodność łączy się ściśle z poprawnością programu. Najogólniej można powiedzieć, że program jest poprawny, jeżeli zawsze działa zgodnie z sensownymi oczekiwaniami użytkownika i intencją programisty. Definicja taka jest bardzo ogólna i elastyczna, bo bazuje na pojęciach wysoce subiektywnych. Program prawidłowo zaprojektowany i napisany powinien być zgodny ze swoją specyfikacją. Należy więc upewnić się, czy badany program istotnie tę specyfikację spełnia. Z drugiej strony, czasami sama specyfikacja może nie obejmować pewnych sytuacji lub być niedoskonała albo niespójna w inny sposób, i wówczas w pełni z nią zgodny program także może ulec awarii. Należy więc sprawdzić i specyfikację.
Testowanie jest to zbiór czynności wykonywanych z intencją wykrycia w programie jak największej liczby błędów. Jednym z jego głównych składników jest obserwacja zgodności produkowanych przez program wyników z wcześniej przygotowanymi poprawnymi wynikami odniesienia. Proces ten może prowadzić do uznania, że dany program działa poprawnie we wszystkich przypadkach, w których ma działać, lub do wykrycia błędu.
Celem testowania programu jest upewnienie się, że program rozwiązuje to zadanie, do którego został zaprojektowany, i że w każdych warunkach daje poprawne wyniki. Jest to proces różny od procesu uruchamiania programu. Program uruchomiony nie zawiera błędów sygnalizowanych przez translator i jawnie niepoprawnych fragmentów kodu oraz daje jakieś wyniki. Czy wyniki te są poprawne, zgodne z oczekiwaniami ustalonymi w specyfikacji, pozwala ocenić testowanie.
Nie jest możliwe dokładne określenie, jak powinno przebiegać testowanie konkretnego programu. Można jednak sformułować kilka wskazówek dotyczących organizacji procesu testowania, które mogą pomóc w prawidłowym przetestowaniu programu.
Należy dążyć do:
· sprowokowania błędu, tzn. stworzenia sytuacji, w której błąd może się ujawnić (odpowiednie środowisko, dobrane dane testowe, przygotowane poprawne wyniki odniesienia);
· sporządzenia dokładnego opisu okoliczności, które doprowadziły do błędu, co umożliwi przejście do fazy uruchamiania w celu poszukiwania przyczyny niewłaściwej pracy programu.
Określając granice procesu testowania trzeba wspomnieć o liczbie wykonywanych testów. Chcąc starannie przetestować cały program, należałoby wykonać testowanie gruntowne. Polega ono na wykonaniu programu dla wszystkich możliwych kombinacji danych wejściowych. Jest to prawie zawsze niemożliwe ze względu na niezbędny w tym celu czas i wysiłek, choć byłby to jedyny sposób udowodnienia całkowitej poprawności programu. W praktyce, ze względu na efektywność testowania, należy stosować możliwie małą, lecz dostateczną liczbę zestawów danych testowych, przy czym powinny być one dobierane z odrobiną wyobraźni i podejrzliwości w stosunku do działania programu. Mimo istnienia wielu formalnych metod, w praktyce programista powinien bazować w znacznym stopniu na swoim sprycie i intuicji.
Testując dany program należy przeprowadzić dwa rodzaje testów. Po pierwsze, trzeba przekonać się, czy program realizuje właśnie to zadanie, które zostało sformułowane. Po wtóre, należy uzyskać pewność, że działa poprawnie.
Możliwe są dwa skrajnie odmienne podejścia do testowania programu: względem struktury wewnętrznej (kodu) i względem specyfikacji zewnętrznej.
Można jako podstawę w czasie testowania potraktować kod programu. Wtedy zabezpieczymy się przed błędami wykonania (ang. run-time errors) i wszystkie instrukcje będą się wykonywały poprawnie, jednak nie wykryjemy faktu, że program jest niezgodny ze specyfikacją, tzn. robi nie to, co należało.
Jeśli zaś nie będziemy interesować się kodem, możemy testowaniem objąć jedynie specyfikację. Wówczas mamy sytuację odwrotną – jeżeli umiejętnie dobierzemy dane testowe, możemy wykryć wiele błędów logicznych, jednak nie możemy gwarantować, że program nie zakończy się błędem wykonania. Wynika stąd, że dla właściwego przetestowania programu konieczna jest znajomość i sprawdzenie zarówno specyfikacji, jak i kodu programu.
Przy testowaniu struktury wewnętrznej pojedynczego modułu (testowaniu względem kodu) konieczna jest znajomość tekstu źródłowego programu. Generalnie przyjmuje się, że warunkami koniecznymi zakończenia testowania względem kodu są:
Przygotowując dane testowe dla testowania względem kodu korzystamy z diagramów przepływu sterowania (DPS) i tworzonych na ich podstawie macierzy pokrycia gałęzi. Technika posługiwania się nimi została przedstawiona w Dodatku A. Szerzej o zasadach doboru danych testowych traktuje punkt 1.6.
W przypadku testowania specyfikacji zewnętrznej program traktujemy jako „czarną skrzynkę”. Znamy jedynie liczbę i rodzaj danych podawanych na wejściu, liczbę i rodzaj danych wyjściowych oraz zdefiniowaną w specyfikacji zależność pomiędzy danymi wejściowymi a wyjściowymi.
W tym przypadku dobór danych testowych, a szczególnie moment, w którym uznamy testowanie za zadowalające i wiarygodne, w jeszcze większym stopniu zależy od doświadczenia i intuicji testującego. Wskazówki na temat doboru danych testowych znajdują się w punkcie 1.6.
W czasie przygotowywania zestawów danych testowych należy brać pod uwagę:
· specyfikację zewnętrzną programu,
· funkcje realizowane przez program,
· strukturę programu,
· przepływ sterowania.
Testowanie programu trzeba rozpoczynać bardzo wcześnie, właściwie już na etapie projektowania. Należy przede wszystkim sprawdzić, czy poprawnie zaprojektowano rozwiązanie sformułowanego zadania, to znaczy, czy program będzie rzeczywiście rozwiązywał takie zadanie, jakie należy, i czy wzięto pod uwagę wszystkie przypadki bez niejednoznaczności. Można to zrobić jeszcze przed rozpoczęciem kodowania za pomocą „ręcznej symulacji” przygotowanego algorytmu. Pomocny tu jest udział klienta zlecającego wykonanie programu. Nazywamy to testowaniem specyfikacji lub projektu.
Testowanie projektu pozwoli na uniknięcie poważniejszych błędów wynikających z niejasności lub niekonsekwencji w sformułowaniu zadania i specyfikacji zewnętrznej programu.
W czasie pisania programu należy pamiętać, że będzie on testowany. Struktura programu powinna być taka, by jego testowanie (także testowanie poszczególnych fragmentów) było łatwe. Jeśli dla wygody testowania do programu źródłowego wstawia się dodatkowe instrukcje, to należy je odpowiednio skomentować. Wygodnie jest je umieszczać wewnątrz dyrektyw kompilacji warunkowej[1]. Po sprawdzeniu poprawności programu będzie je można bez kłopotu usunąć lub dezaktywować.
Testowanie modułów (procedur, fragmentów programu) przeprowadza się pojedynczo, w środowisku nie korzystającym z innych modułów, także przed powstaniem tych innych modułów. Jeżeli testowany moduł odwołuje się do modułów, które jeszcze nie istnieją, stosuje się symulację tych brakujących części systemu lub programu. Można tu wykorzystać moduły – makiety posiadające jedynie punkt wejścia i punkt powrotu oraz jakąś instrukcję sygnalizującą fakt wywołania modułu. Innym rozwiązaniem jest utworzenie modułu zastępczego realizującego w sposób uproszczony niezbędne do testowania funkcje.
Jeżeli testowaniu podlega oprogramowanie numeryczne, należy pamiętać o sprawdzeniu zachowania się modułu w przypadkach:
· błędnych danych wejściowych (dla każdego modułu);
· ...
Lauviah666