Kategoria

Ron patton software testing, strona 8


rozdział 5 2/2
07 września 2019, 22:18

Test przepływu przejść między stanami programu

W rozdziale 3-im pokazano przykład, że testując Kalkulator ma się do czynienia z nieskończoną ilością kombinacji danych wejściowych dla programu. Jednocześnie nauczyliśmy się w kolejnych rozdziałach, że aby testowanie stało się możliwe, trzeba zmniejszyc ilość zadań testowych tworząc klasy równoważności dla najważniejszych grup wartości.

Testowanie stanów i przejść między nimi sprawia ten sam kłopot. Zwykle da się odwiedzić wszytkie stany (w końcu, gdyby się nie dało, można by je po prostu usunąć z programu). Kłopot w tym, że – z wyjątkiem rzeczywiście najprostszych programów, zwykle nie da się przebyć wszystkich możliwych ścieżek do wszystkich stanów. Złożoność oprogramowania, zwłaszcza z powodu bogactwa intefejsów użytkownika we współczesnych aplikacjach, stwarza tak wiele różnych wyborów i opcji, że ilość dostępnych ścieżek rośnie wykładniczo.

Ten problem przypomina dobrze znany problem komiwojażera: mając określony zbiór miast i odległości między nimi, znaleźć najkrótszą z dróg, która przejdzie przez wszystkie miasta i wróci do punktu wyjścia. Gdyby miast było tylko pięć, po dokonaniu obliczeń można stwierdzić, że możliwych tras przez nie jest 120. Przejechanie wszystkich i zmierzenie ich długości nie jest więc niewykonalne. Ale, jeśli zadanie dotyczy setek lub tysięcy miast – albo, w naszym przykładzie, setek albo tysięcy stanów programu – problem staje się bardzo trudny do rozwiązania.

Rozwiązeniem (dla testowania, nie dla komiwojażera) jest zastosowanie techniki podziału na klasy równoważności stanów i przejść między nimi. Stwarza się w ten sposób ryzyko, bo nie przetestuje się wszystkiego, ale można je zredukować dokonując inteligentnych wyborów.

Stworzenie mapy przejść stanów

Pierwszym krokiem będzie stworzenie włanej mapy przejść stanów programu. Czasami taka mapa jest dostępna jako część specyfikacji produktu. Jeśli tak jest, trzeba ją przetestować metodami statycznymi, jak to opisano w rozdziale 4-ym „Testowanie Specyfikacji”. Jeśli mapa przejść stanów nie istnieje, trzeba ją będzie zbudować.

Istnieją różne techniki zapisywania diagramów przejść stanów programu. Rysunek 5.10 pokazuje dwa przykłady. Na jednym rysuje się prostokąty i strzałki, a drugim kółka i strzałki1. Technika jest nieważna, o ile wszyscy członkowie zespołu umieją się nią posłużyć.

Rysunek 5.10 Diagramy przejśc stanów można rysowac na różne sposoby.

 

Diagramy przejść stanów łatwo staja się bardzo duże. Często widzi się całe ściany oklejone wydrukami diagramów. Istnieją programy rysujące takie diagramy – warto się nimi posłużyć.

 

Mapa przejść stanów powinna zawierać następujące elementy: 62.Każdy unikalny stan w którym program może się znaleźć. Prosta zasada brzmi – jeśli trudno się zdecydować, czy ma się do czynienia z jednym czy dwoma odrębnymi stanami, to zapewne są to dwa odrębne stany. Zawsze można później łatwo połączyć dwa stany z powrotem w jeden, jeśli to założenie okaże się niesłuszne. 63.Dane wejściowe które powodują przejście z jednego stanu do drugiego. Może to być naciśnięcie klawisza, dane z czujnika, dzwonek telefonu itd. Stanu nie można opuścić bez powodu. Ten powód właśnie usiłuje się tutaj zidentyfikować.

 

1 Oczywiście, różnica jest mniej niż kosmetyczna, w obu tych “różnych” technikach opisuje się stany i ich przejścia w formie grafu. Rzeczywiście odmienną (i znacznie wygodniejszą) techniką jest natomiast opis w formie tabeli, gdzie rzędy oznaczają stany wyjściowe, kolumny – stymulacją lub dane wejściowe, a pola na przecięciu rzędów i kolumn – stany docelowe (przyp. tłumacza).

 

64.Wyprodukowane dane wyjściowe albo inna zmiana warunków przy przejściu ze stanu do stanu. Może to być na przykład wyświetlenie menu albo zestawu przycisków, ustawienie flagi, pojawienie się wydruku, wykonanie obliczenia itd; wszystko, co dzieje się w trakcie przejścia z jednego stanu w drugi.

 

Testując przejścia stanów, zwykle robi się to w formie testowania metodą czarnej skrzynki, wobec czego nie musi się znać np. zmiennych w kodzie programu, które ustawiane są w trakcie przejść między stanami. Mapę stanów programu najlepiej sporządzić z punktu widzenia użytkownika.

 

Zmniejszenie ilości stanów i ich przejść, które się będzie testować

 

Sporządzenie mapy stanów dla dużego produktu informatycznego to wielkie przedsięwzięcie. Jeśli testuje się tylko część produktu, np.podsystem, wykonanie mapy stanów staje się łatwiejsze. Kiedy mapa jest gotowa, można obejrzeć opis wszystkich stanów i przejść między nimi – i to jest groźny widok!

 

Gdyby mieć do dyspozycji nieskończoną ilość czasu, można by przetestować wszystkie ścieżki prowadzące przez diagram stanów – nie tylko przejścia między dwoma sąsiednimi stanami, ale wszelkie możliwe kombinacje przejść od początku do końca, z różną ilością powtórzeń, pętalmi itp. Podobnie jak w problemie komiwojażera, nie da się przetestować wszystkich.

 

Jak podział na klasy równoważności stosuje się wobec danych, tak samo można ograniczyć ilość zadań testowych dla testów przejść stanów. Jest na to pięć sposobów: 65.Odwiedzić każdy stan przynajmniej jeden raz. Nieważne jak, ważne aby się tam dostać. 66.Przetestować przejścia ze stanu do stanu, które wydają się najczęściej odwiedzane. To brzmi – i jest – bardzo subiektywne, ale można podeprzeć się wiedzą zdobytą, gdy przeprowadzało się statyczną analizę metodą  czarnej skrzynki (rozdział 3) na specyfikacji produktu. Niektóre scenariusze zastosowań okazują się być wykonywane częściej niż inne – i one muszą działać dobrze! 67.Przetestować najrzadziej używane ścieżki między parami stanów – być może zostały one przeoczone przez konstruktorów i programistów. Tester będzie jednym z pierwszych, który je wypróbuje.

 

68.Przetestować każdy ze stanów awaryjnych oraz powroty z nich. Wiele stanów awaryjnych trudno jest wywołać. Niejednokrotnie programiści piszą kod dla obsługi różnych awarii, ale nie są go w stanie przecież przetestować, stąd wiele błędów. Często procedury obsługi błędów są niepoprawne, komunikaty o błędach mylące, albo program nie wraca do stanu normalnego we właściwy sposób. 69.Przetestować losowe zmiany stanów. Jeśli ma się do dyspozycji mape stanów programu, można w nią rzucać strzałkami i potem testować przejścia od strzałki do strzałki. W rozdziale 14ym „Automatyczne  testowanie i narzędzia do testowania” znjadują się informacje o tym, jak zautomatyzować losowe testowanie przejść stanów.

 

Co szczególnie należy przetestować

 

Kiedy się już zidentyfikuje stany i przejścia między stanami, które chce się przetestować, można przystąpić do zdefiniowania zadań testowych.

 

Testowanie stanów i przejść między stanami oznacza sprawdzenie wszystkich zmiennych określających stan: informacji, wartości, funkcji itd. wiążących się z danym stanem lub zmieniających się przy przejściu od stanu do stanu. Rysunek 5.11 pokazuje przykładowo program Paint w stanie początkowym.

 

Rysunek 5.11 Pierwszy ekran programu Paint w stanie początkowym.

 

Oto lista wartości zmiennych które charkteryzują stan początkowy programu Paint: 70.Okno wygląda tak, jak na rysunku 5.11. 71.Wymiary okna są takie jak podczas poprzedniego użycia. 72.Obszar obrazu jest pusty.

 

73.Wyświetlone jest menu do wyboru narzędzi, koloru i pasek pokazujący status. 74.Ołówek jest wybranym narzędziem. 75.Domyślne kolory to czarny pierwszy plan i białe tło. 76.Tytuł dokumentu jest bez tytułu. Program Paint ma wiele, wiele innych zmiennych określających stan, które można by wziąć pod uwagę, ale podane przykłady powinny wyjasnić, na czym polega zdefiniowanie stanu programu. Warto pamiętać, że tak samo postępuje się identyfikując stan programu niezależnie od tego, czy opis stanu określają zmienne tak widoczne jak okno lub okno dialogowe, czy niewidoczne jak w wypadku stanów programu komunikacyjnego albo aplikacji finansowej.

 

Dobrze jest przedyskutować swoje przypuszczenia dotyczące stanów i przejść między nimi z autorami specyfikacji i z programistami. Można od nich uzyskać wgląd w to, co dzieje się wewnątrz programu.

 

Flaga sygnalizuje „brudny dokument”

 

Zmienne określające stan mogą być niewidoczne lub widoczne. Typowy przykład to flaga sygnalizująca „brudny dokument”.

 

Kiedy dokument zostaje załadowany do programu, takiego jak program do przetwarzania tekstu albo program do malowania, wewnętrzna zmienna określająca stan, zwana „flagą brudnego dokumentu” jest wyzerowana i dokument jest w stanie „czystym”. Program pozostaje w tym stanie dopóty, dopóki nie zostaną dokonane jakieś zmiany w dokumencie. Dokument można czytać i przewijać – wartość zmiennej pozostaje niezmieniona. Jak tylko jednak coś zostanie napisane albo dokument zostanie zmieniony w jakikolwiek inny sposób, program zmieni swój stan na stan „brudny”.

 

Jeśli chce się zamknąć lub wyjść z dokumentu w stanie czystym, odbędzie się to bez przeszkód. Jeśli dokument jest „brudny”, użytkownik otrzyma komunikat z pytaniem, czy dokument należy zapisać przed wyjściem z aplikacji.

 

Niektóre programy są tak wyrafinowane, że jeśli dokona się jakiejś zmiany, którą potem się odwoła i dokument powróci do stanu wyjściowego, to program powróci też do stanu „czystego”. Wyjście z programu jest wtedy możliwe bez pytania, czy chce się zapisać dokument.

 

Negatywne testowanie stanów

 

Wszystko co dotąd zostało przedyskutowane dotyczyło testowania pozytywnego. Przegląda się specyfikację, szkicuje stany programu, wypróbowuje liczne dozwolone warianty i upewnia się, że stany i przejścia między stanami działają. Drugą stroną medalu, tak samo jak przy testowaniu danych, jest sprawdzenie czy program działa też wówczas, kiedy poddać go negatywnym zadaniom testowym. Przykłady takich sytuacji to warunki wyścigu, powtórzenia, stres i obciążenie.

 

Wyścig i błędna synchronizacja w czasie

 

Większość współczesnych systemów operacyjnych, zarówno dla komputerów osobistych jak i dla sprzętu specjalnego, jest zdolna do wielozadaniowości. Wielozadaniowość oznacza że system operacyjny jest skonstruowany tak, by móc wykonywać wiele różnych procesów równolegle. Te procesy to mogą być odrębne programy użytkowe, jak arkusz kalkulacyjny lub poczta komputerowa. Mogą też być częściami tego samego programu, jak na przykład wydruk w tle, gdy jednocześnie można wpisywać nowe słowa do programu przetwarzającego tekst.

 

Skonstruowanie wielozadaniowego systemu operacyjnego nie jest trywialne, tak samo jak nie jest łatwo zaprojektować program użytkowy wykorzystujący wielozadaniowość. W systemie prawdziwie wielozadaniowym niczego nie można uważać za pewnik. Program musi sobie móc poradzić z przerwaniem nadchodzącym w każdym momencie, być wykonywanym jednocześnie z nieznanymi, innymi programami, oraz posługiwać się wspólnie z innymi programami dostępem do zasobów takich jak pamięć, dysk, komunikacja i inne zasoby sprzętowe.

 

Wszystko to prowadzi niekiedy do sytuacji zwanej wyścigiem. Polega to na tym, że kilka procesów jednocześnie „ściga” się do linii mety, jaką jest np. zawładnięcie zasobem albo zapisanie wartości w pamięci, i w konsekwencji działanie programu zależy od przypadkowej „wygranej” jednego z procesów. Mówiąc ogólnie, chodzi tu o złą synchronizację w czasie. Może się też zdarzyć, że jeden proces przerwie wykonywanie drugiego procesu w momencie, który nie był przewidziany.

 

Trudno jest zaplanować testowanie nakierowane na wykrycie sytuacji wyścigu1, ale dobrym początkiem jest przyjrzeć się diagramowi zmian stanów. Jakie zewnętrzne czynniki mogą przerwać każdy stan? Co stanie się, jeśli potrzebne dane nie będą gotowe na czas albo będą zmieniane przez jeden proces w momencie, kiedy drugi proces je czyta? Co będzie, jeśli dane prowadzące do dwóch różnych przejść z aktualnego stanu nadejdą jednocześnie?

 

1 Autor używa tutaj określenia wyścig (race condition) na oznaczenie wszelkich błędów specyficznych dla systemów czasu rzeczywistego. Jest to dość pewne uogólnienie (w rzeczywistości wyścig jest tylko jednym z wielu typów takich błędów), ale bez znaczenia o tyle, że książka i tak nie opisuje żadnych technik testowania specjalnych dla systemów czasu rzeczywistego.

 

Oto kilka przykładów sytuacji, które mogą doprowadzić do zaistnienia wyścigu: 77.Zapisywanie tego samego dokumentu jednocześnie przez dwa różne programy 78.Korzystanie przez różne programy ze wspólnej drukarki, portu komunikacyjnego albo innego urządzenia peryferyjnego 79.Naciskanie klawiszy albo klikanie myszą w czasie gdy aplikacja ładuje się lub właśnie zmienia stan 80.Jednoczesne zamykanie lub otwieranie dwóch instancji tego samego programu 81.Jednoczesne zapisywanie w bazie danych przez dwa różne programy

 

Może to wszystko wydaje się zbędnie zawiłymi testami, ale tak nie jest. Oprogramowanie musi być dostatecznie odporne na błędy, żeby poradzić sobie z takimi sytuacjami. Wiele lat temu podobne sytuacje może były niezwykłe, ale dziś użytkownicy oczekują, że programy czasu rzeczywistego będą działać poprawnie.

 

Powtarzanie, obciążenie i przeciążanie

 

Innymi rodzajami negatywnych testów stanów są powtarzanie, przeciążanie i obciążenie1. Mają one na celu wychwycenie błędów przy zmianie stanów, pojawiających się w szczególnych warunkach, których programista nie wziął pod uwagę.

 

Testowanie powtarzalne polega na powtarzaniu tej samej operacji w kółko na okrągło. Może to być coś tak prostego jak wielokrotne otwieranie i zamykanie programu. Można też raz po raz zapisywać i z powrotem ładować dane z pliku, albo w ogóle powtarzać wielokrotnie jakiekolwiek zadanie. Może uda się trafić na błąd już po kilku powtórkach – a może trzeba będzie tysięcy prób, żeby problem się ujawnił.

 

1 Pewne zamieszanie terminologiczne. Testy na obciążenie i przeciążenie zaliczają się do testów wydajności (jak – szybko, dużo itd. – program pracuje), podczas gdy testowanie stanów jest metodyką stosowaną do wybierania zadań testowych głównie do testowania funkcjonalnego (czy program wykonuje właściwe funkcje). Jedno z drugim niewiele ma wspólnego. Opisane w tym podrozdziale testy wydajności znalazły się w rozdziale o testowaniu stanów raczej przez przypadek.

 

Głównym celem testowania powtarzalnego jest poszukiwanie przecieków pamięci1. Często spotykany błąd pojawia się, kiedy fragment pamięci zostanie przydzielony programowi, ale nie zostanie potem uwolniony (lub nie do końca), kiedy wykorzystująca tę pamięć operacja zostanie już zakończona. W końcu, po jakimś czasie program zużyje całą dostępną pamięć. Każdy kto posługiwał się programem, który początkowo działa dobrze, ale stopniowo zaczyna działać coraz wolniej i wolniej, albo po jakimś czasie zaczyna funkcjonować niekonsekwentnie, zetknął się przypuszczalnie właśnie z problemem przecieku pamięci. Testowanie powtarzalne ma na celu znajdowanie i usunięcie tego typu błędów.

 

Test przeciążający polega na tym, że program zmusza się do działania w niekorzystnych warunkach – za mało pamięci, za wolny mikroprocesor, powolne modemy itd. Należy zorientować się, od jakich zewnętrznych zasobów program jest uzależniony. Testowanie przeciążające polega na ograniczeniu tych zasobów do najzupełniejszego minimum. Staramy się po prostu „wygłodzić” program, zmuszając go do pracy przy niedostatecznych zasobach. Czy to nie przypomina testowania warunków brzegowych? Jak najbardziej.

 

Testowanie obciążające2 jest odwrotnością testowania przeciążającego. W czasie testowania przeciążającego staramy się „wygłodzić” program; w czasie testowania obciążającego ładujemy w program tyle danych, ile tylko się da. Przykladowo, każemy programowi używać największych możliwych plików danych. Jeśli program używa urządzeń peryferyjnych, jak drukarki albo porty komunikacyjne, podłącza się tyle ile się tylko da. Testując serwer internetowy mający obsługiwać tysiące jednoczesnych sesji, należy wypróbować właśnie tysiące jednoczesnych sesji. Należy „przycisnąć” program, obsiążyć go ile się tylko da.

 

Nie zapominajmy, że także czas jest zmienną którą można wykorzystać do testowania obciążającego. Ważne jest, żeby przetestować działanie programu przez długi czas. Niektóre typy programów powinny móc funkcjonować latami bez konieczności ponownego włączenia.

 

Oczywiście w praktyce można połączyć w jeden ciąg testowanie powtarzalne, przeciążające i obciążające.

 

1 Ten rodzaj testowania, który autor nazywa “testowaniem powtarzalnym”, określany jest często jako analiza dynamiczna. Jej celem jest znalezienie nie tylko przecieków pamięci, ale wszelkich błędów typu niedozwolone skutki uboczne działania programu, których symptomy ujawniają się często najwyraźniej dopiero po upływie dłuższego czasu (przyp. tłumacza).

 

2 Testowanie przeciążające definiuje się często jako testowanie działania programu w warunkach obciążenia większego niż maksymalne dozwolone (np. test centrali telefonicznej w sytuacji, gdy wszyscy abonenci jednocześnie podniosą sluchawki). Oczywiście, tego rodzaju “wygłodzenie” programu, jakie opisuje autor, jest skutecznym sposobem symulacji przeciążenia (ściślej, wynikłego z przeciążenia braku zasobów) bez potrzeby kłopotliwego generowania prawdziwego obciążenia, ale nie jest celem samym w sobie (przyp. tłumacza).

 

Przy testowaniu powtarzalnym, przeciążającym i obciążającym trzeba pamiętać o dwóch sprawach: 82.Programiści i kierownicy zespołów mogą nie popierać usiłowań złamania programu w taki sposób. Będą się skarżyć, że prawdziwi użytkownicy nigdy nie będą obciążać programu aż do tego stopnia. Krótka odpowiedź brzmi: owszem, będą. Zadaniem testera jest upewnić się, czy program działa również w takich warunkach i zgłosić błędy, jeśłi nie działa1. Rozdział 18-y „Raportowanie wyników” wyjaśnia, jak najlepiej zgłaszać i opisywać znalezione błędy tak, żeby zostały potraktowane poważnie i naprawione. 83.Zamykanie i otwieranie programu milion razy jest przypuszczalnie niewykonalne, jeśli robić to ręcznie. Podobnie, niełatwo będzie znaleźć i zorganizować grupę kilku tysięcy osób, żeby jednocześnie podłączyły się do testowanej aplikacji internetowej. Rozdział 14 opisuje automatyzację testowania i zawiera wiele informacji, jak można przeprowadzać testy osiągów bez konieczności angażowania osób do ręcznego wykonania tej pracy. Inne techniki czarnej skrzynki

 

Pozostałe typy technik czarnej skrzynki to nie są właściwie odrębne metody, lecz raczej odmiany już opisanych technik testowania danych i testowania stanów2. Jeśli dokonało się starannej identyfikacji klas równoważności na danych programu, sporządziło szczegółową mapę stanów programu i sporządziło na ich podstawie zadania testowe, znalazło się przypuszczalnie większość błędów, jakie może znaleźć użytkownik.

 

Pozostały nam techniki dla znajdowania „zbłąkanych błędów”, które – zupełnie jakby były prawdziwymi, żywymi „pluskwami” – wydają się poruszać, kierowane własnym, niezależnym umysłem. Znajdowanie ich może wydawać się zadaniem nie stosującym się do zasad zdrowego rozsądku, ale chcąc znaleźć i usunąć każdy, najlepiej ukryty błąd, trzeba będzie wykazać się twórczą fantazją.

 

Autor:1 W zasadzie, tego rodzaju spór między testerami a programistami nie powinien mieć miejsca: te sprawy powinny być jednoznacznie rozstrzygnięte przez specyfikację wymagań. W praktyce, specyfikacje bywają notorycznie niedokładne zwłaszcza jeśli chodzi o określenie wymagań dotyczących osiągów i wówczas intuicja tesera – jak to opisuje autor – może częściowo zastąpić brakujący opis wymagań (przyp. tłumacza).

 

2 Istnieje wiele innych interesujących technik czarnej skrzynki (m.in. bardzo skuteczne do wychwytywania pewnych typów błędów testowanie syntaktyczne) których książka dla początkujących z konieczności nie opisuje (przyp. tłumacza).

 

Zachować się jak głupi użytkownik

 

Terminem politycznie poprawnym powinno być raczej niedoświadczony użytkownik albo nowy użytkownik, ale w rzeczywistości chodzi przecież o to samo. Wystarczy posadzić przed komputerem osobę, która programu zupełnie nie zna, a zobaczymy wkrótce jak usiłuje zrobić rzeczy, które nam do głowy nie przyszły w najśmielszych marzeniach. Będzie wprowadzać do programu dane, jakie nam nawet nie przyszły do głowy. Zmieni zdanie w połowie wykonywania zadania, wycofa się i zrobi coś innego. Przesurfuje przez stronę internetową, klikając na przyciski, na które klikać nie należy3. Znajdzie błędy, które testerzy przeoczyli zupełnie.

 

To może być dla testera szalenie frustrujące, obserwować kogoś zupełnie pozbawionego doświadczenia w testowaniu, kto w ciągu pięciu minut jest w stanie spowodować awarię programu, który wcześniej testowało się tygodniami. Jak on to robi? Po prostu nie stosuje się do żadnych zasad i nie robi żadnych założeń.

 

Warto wobec tego, konstruując zadania testowe albo badając program po raz pierwszy, spróbować wejść w skórę „głupiego użytkownika”. Spróbować odrzucić wszelkie założenia i przypuszczenia o tym, jak program powinien działać. Można spróbować sprowadzić kolegę, który nie jest w projekcie i razem z nim wymyślać szalone pomysły. Niczego nie przyjmować za pewnik. Tego typu zadania testowe dodane do specyfikacji będą stanowiły całkiem pokaźną jej część.

 

Szukać błędów tam, gdzie się je już raz znalazło

 

Są dwa powody, dla których warto szukać błędów tam, gdzie się je już wcześniej znalazło.

 

3 Wygoda użytkowania i intuicyjność interfejsu, na którym nowy użytkownik zrobi to wszystko, zdaje się pozostawiać wiele do życzenia… jak w rzeczywistości rzecz ma się z wieloma programami (przyp. tłumacza).

 

84.Jak dowiedzieliśmy się w rozdziale 3-im, im więcej błędów się znajduje, tym więcej błędów tam się jeszcze ukrywa. Jeśli znajduje się na przykład dużo błędów na górnych granicach przedziałów w różnych funkcjach oprogramowania, to warto poświęcić więcej uwagi testowaniu górnych granic przedziałów. Oczywiście, to by się zrobiło i tak, ale warto dorzucić kilka dodatkowych zadań testowych, wiedząc, że mogą okazać się skuteczne. 85.Wielu programistów naprawia tylko błąd opisany w raporcie błędu – ani więcej, ani mniej. Jeśli się napisało, że wystartowanie, zatrzymanie i ponowne wystartowanie programu 255 razy powoduje awarię, to programista przypuszczalnie to właśnie naprawi. Może istniał tam gdzieś przeciek pamięci, który programista znalazł i usunął. Kiedy dostanie się naprawiony program do ponownego przetestowania, na pewno warto zrobić dokładnie to samo – 256 razy i więcej. Może gdzieś ukrywa się jeszcze kolejny przeciek pamięci?

 

Posłuchać własnego doświadczenia, intuicji i… przeczuć

 

Nie ma lepszego sposobu żeby stać się lepszym testerem niż zbierać doświadczenie. Nie ma lepszej metody nauczenia się niż testując, i nie ma skuteczniejszej lekcji niż pierwszy telefon od klienta, co znalazł błąd w programie, który właśnie skończyło się testować.

 

Nie można nauczyć doświadczenia i intuicji, trzeba je zdobywać samemu. Można zastosować opisane dotąd techniki i przegapić poważne błędy, taka jest natura tej pracy. Zdobywając doświadczenie zawodowe, uczymy się różnych rodzajów i różnych wielkości produktów, zbieramy rozmaite wskazówki, które pokierują nas w stronę trudnych do znalezienia błędów. Po jakimś czasie jest się w stanie zasiąść do testowania nowego kawałka oprogramowania i szybko znajdować błędy, których inni by nie zauważyli.

 

Warto zapisywać, co działa, a co nie, próbować różnych sposobów. Jeśli coś wygląda podejrzanie, zawsze warto się temu przypatrzeć dokładniej. Warto posłuchać własnych przeczuć, dopóki nie okażą się fałszywe.

 

Doświadczenie to nazwa, jaką ludzie nadają swoim pomyłkom.

 

Oscar Wilde Podsumowanie

 

To był długi rozdział. Dynamiczne testowanie metodami czarnej skrzynki jest obszerną dziedziną. Dla nowych testerów, to mógł być najważniejszy rozdział z całej książki. Prawdobodobnie w czasie wywiadu u pracodawcy albo w pierwszym dniu pracy otrzyma się polecenie, żeby przetestwoać program. Stosując techniki z tego rozdziału, będzie można od razu znajdować błędy.

 

Nie należy jednak przypuszczać, że to już wszystko, co testowanie oprogramowania ma do zaoferowania. Gdyby tak było, można by przestać czytać i zlekceważyć pozostałe rozdziały książki. Testowanie dynamiczne metodami czarnej skrzynki to dopiero początek. Test oprogramowania wymaga więcej wiedzy, a my dopierośmy zaczęli ją zdobywać.

 

Dwa kolejne rozdziały wprowadzają do testowania w sytuacji, kiedy ma się dostęp do kodu i można zobaczyć, jak program działa i co robi na najniższym poziomie. Poznane techniki czarnej skrzynki nadal są przydatne, ale można je uzupełnić nowymi technikami, które pozwolą stać się jeszcze skuteczniejszymi testerami oprogramowania. Pytania

 

Pytania mają na celu ułatwienie zrozumienia. W aneksie A „Odpowiedzi do pytań” znajdują się prawidłowe rozwiązania – ale nie należy ściągać! 1.Prawda czy fałsz: czy da się wykonywać dynamiczne testowanie metodami czarnej skrzynki bez specyfikacji produktu albo specyfikacji wymagań? 2.Kiedy testuje się zdolność programu do robienia wydruków, jakie ogólne, negatywne zadania testowe są właściwe? 3.Wystartuj Notatnik Windows i wybierz funkcję Drukuj z menu Plik. Pojawia się wtedy okno dialogowe pokazane na rysunku 5.12. Jakie warunki brzegowe istnieją dla funkcji Zakres w dolnym, lewym rogu okna?

 

Rysunek 5.12 Okno dialogowe funkcji Drukuj z funkcją wyboru zakresu. 4. Mamy pole do wprowadzania 10-cyfrowego kodu pocztowego, jak na rysunku 5.13. Jakie klasy równoważności można zastosować do jego testowania?

Rysunek 5.13 Przykładowe pole do wprowadzania 5-cyfrowego kodu pocztowego. 5. Prawda czy fałsz: przejście wszystkich stanów programu gwarantuje również, że przeszło się wszystkie przejścia między nimi. 6. Istnieje wiele sposobów rysowania diagramów przejścia stanów, ale każdy z nich pokazuje trzy rzeczy. Jakie? 7. Podaj niektóre z wartości zmiennych w stanie początkowym Kalkulatora Windowsów. 8. Co robi się z programem, chcąc odkryć błędy w synchronizacji w czasie (wyścig)? 9. Prawda czy fałsz: jednoczesne wykonywanie testowania na obciążenie i na przeciążenie jest niedopuszczalne.

rozdzial 5 1/2
07 września 2019, 22:16

Rozdział 5 Test z klapkami na oczach

W TYM ROZDZIALE  Dynamiczne testowanie metodami czarnej skrzynki: testowanie z zawiązanymi oczami  Test „pozytywny” i test „negatywny”  Metoda klas równoważności  Testowanie danych  Testowanie zmian stanów  Inne techniki czarnej skrzynki

OK, bierzmy się do roboty! W tym rozdziale zapoznamy się z tym, co większość ludzi rozumie pod pojęciem „testowanie oprogramowania”. Czas splunąć w garści, zasiąść przed komputerem i zacząć szukać błędów.

Dla początkującego testera może to być pierwszym zadaniem. W czasie wywiadu z kandydatami na stanowisko testera, na pewno padnie pytanie, jak zabrać się za testowanie nowego programu albo nowej funkcji.

Łatwo jest ruszyć na oślep, zacząć bębnić w klawiaturę i mieć nadzieję, że uda się coś rozwalić. Takie podejście może zadziałać – przez jakiś czas. Jeśli program nie jest jeszcze gotowy, nietrudno mieć szczęście i szybko znaleźć kilka błędów. Niestety, te łatwe zdobycze szybko znikną i trzeba będzie podejść do zadania w sposób bardziej ustrukturalizoany i celowy, żeby nadal znajdować błędy i zostać testerem na wysokim poziomie.

W tym rozdziale opisane są najpowszechniejsze i najbardziej wydajne techniki testowania oprogramowania. Nie ma znaczenia, jaki rodzaj programu się testuje – te same techniki będą skuteczne zarówno dla wykonanego na zamówienie firmy pakietu do prowadzenia rachunkowósci, jak i dla oprogramowania w automatyzacji przemysłowej, jak i dla przeznaczonej na masowy rynek gry komputerowej.

Nie trzeba być programistą aby posługiwać się tymi technikami. Nawiązują one wprawdzie do podstawowych pojęć programowania, ale nie trzeba samemu pisać kodu, aby je stosować. Dla niektórych z opisanych tu tecknik podane są pewne szczegóły dla wyjaśnienia, na czym polega ich skuteczność, ale przykłady kody źródłowego są krótkie i napisane w BASIC-u aby łatwo zilustrować o co chodzi. Ci czytelnicy, którzy są programistami i chcą poznać więcej technik dających się zastosować w testowaniu na niskim poziomie, znajdą – po przeczytaniu tego rozdziału – więcej tematów dla siebie w rozdziałach 6-ym „Analiza kodu” i 7-ym „Testowanie oprogramowania pod rentgenem”.

W tym rozdziale omówione są następujące tematy:

31.Co to jest dynamiczne testowanie metodami czarnej skrzynki? 32.Jak zmniejszyc ilość zadań testowych przy pomocy podziału na klasy równoważności 33.Jak zidentyfikować kłopotliwe warunki graniczne 34.Wartości danych, które skutecznie wywołują awarie 35.Jak testować stany i zmiany stanów oprogramowania 36.Jak posługiwać się powtarzaniem, stresem i wysokim obciążeniem aby lokalizować błędy 37.Kilka miejsc gdzie błędy lubia się ukrywać Dynamiczne testowanie metodami czarnej skrzynki: testowanie z zawiązanymi oczami

Testowanie oprogramowania kiedy nie ma się wglądu w szczegóły kodu programu nazywane jest dynamicznym testowaniem metodami czarnej skrzynki. „Dynamicznym”, ponieważ program jest wykonywany – tester poługuje się nim w podobny sposób jak zwykły użytkownik. „Czarnej skrzynki”, bo testowania dokonuje się nie wiedząc dokładnie, jak program działa – jakby z zawiązanymi oczami. Wprowadza się dane wejściowe, otrzymuje dane wyjściowe i sprawdza wyniki. Inną często stosowana nazwą na dynamiczne testowanie metodą czarnej skrzynki to „testowanie behawioralne” – bada się bowiem zachowanie programu, kiedy się nim posługiwać.

Aby móc to robić skutecznie, trzeba orientować się, co program właściwie ma wykonywać – ta informacja powinna znajdować się w specyfikacji wymagań albo w dokumentacji produktu. Nie musi się wiedzieć, co się dzieje wewnątrz „skrzynki” – wystarczy wiedzieć, że dane wejściowe A mają spowodować dane wyjściowe B, zaś dane wejściowe C – dane wyjściowe D. Dobra specyfikacja produktu powinna zawierać te informacje.

Jak się już wie co wchodzi, a co powinno wychodzić z programu, można przystąpić do konstruowania zadań testowych. Zadania testowe to lista danych wejściowych, które się zastosuje i czynności, które się wykona w czasie testowania1. Rysunek 5.1 przedstawia kilka przykładów zadań testowych, które można użyć przy testowaniu dodawania w Kalkulatorze Windows.

Zadania testowe dla dodawania dla Kalkulatora Windows 0+0 powinno dać 0 0+1 powinno dać 1 254+1 powinno dać 255 255+1 powinno dać 256 256+1 powinno dać 257 1022+1 powinno dać 1023 1023+1 powinno dać 1024 1024+1 powinno dać 1025 … …

Rysunek 5.1 Zadania testowe pokazują różne dane wejściowe i czynności w trakcie testowania programu.

Wybór zadań testowych to bezwzględnie najważniejsze zadanie testera. Nieprawidłowy dobór może spowodować, że przetestuje się za dużo, za mało, albo po prostu nie to co trzeba. Te „czary” polegają na tym, by mądrze wziąć pod uwagę stopień ryzyka i umiejętnie zredukować nieskończoną ilość możliwych zadań testowych do ilości, którą rzeczywiście daje się użyć.

Pozostała część tego rozdziału – a także znaczna część reszty książki – nauczy nas sposobów strategicznego dobierania dobrych zadań testowych. Rozdział 17-y „Jak pisać zadania testowe i rejestrować ich wyniki” opisuje konkretne sposoby zapisywania i zarządzania zadaniami testowymi.

Testowanie eksploracyjne kiedy brakuje specyfikacji

Testując oprogramowanie wytwarzane w ramach profesjonalnego, dojrzałego procesu wytwarzania, ma się do dyspozycji szczegółową specyfikację. Testując programy wytwarzane metodą skokową albo prób i błędów, zwykle nie ma się żadnej specyfikacji. To marna sytuacja dla testera, ale jest szansa na działające rozwiązanie, jeśli posłużyć się tak zwanym testowaniem eksploracyjnym.

Program traktuje się jak specyfikację i metodycznie „eksploruje” wszystkie jego funkcje jedna po drugiej. Zbiera się notatki na temat działania programu, sprządza mapę jego funkcji i stosuje niektóre z technik czarnej skrzynki opisanych w rozdziale 3-im „Test oprogramowania w rzeczywistości”. Program analizuje się tak jakby rzeczywiście był specyfikacją, po czym stosuje się opisane w niniejszym rozdziale techniki testowania dynamicznego metodami czarnej skrzynki.

Nie da się tą metodą przetestować programu równie gruntownie jak wtedy, kiedy specyfikacja jest dostępna – nie uda się na przykład odnaleźć funkcji brakujących. Niemniej, da się program systematycznie przetestować. Jeśli uda się znaleźć troche błędów, to już w tej sytuacji sukces. Test „pozytywny” i test „negatywny”

Istnieją dwa podstawowe podejścia do testowania programów: testowanie pozytywne i testowanie negatywne. Testy pozytywne kontrolują jedynie, że oprogramowanie funkcjonuje poprawnie na swego rodzaju minimalnym poziomie. Nie testuje się granic zbyt daleko, nie szuka się sposobów wywołania awarii za wszelką cenę. Postępuje się delikatnie, w rękawiczkach, stosując tylko najprostsze i najbardziej oczywiste zadania testowe.

Jeśli jednak celem ma być znajdowanie błędów, po co w ogóle robić testy pozytywne? Czyż nie chcemy za wszelką cenę znajdować błędów, stosując najbardziej demomniczne zadania testowe? Tak, ale nie od razu.

Wyobraźmy sobie analogię z testowaniem nowego modelu samochodu (rysunek 5.2). Testując pierwszy prototyp, który dopiero co opuścił linie montażową i nigdy jeszcze nie jeździł, nie będzie się go od razu prowadzić na pełen gaz tak ostro, jak tylko się da. Kierowca testowy przypuszczalnie szybko rozbiłby próbny samochód. W nowym samochodzie pełno błędów ujawni się nawet przy powolnjej jeździe w normalnych warunkach. Może opony maja złą średnicę albo hamulce są niesprawne, albo silnik zbyt szybko wchodzi na wysokie obroty. Należy znaleźć te usterki i naprawić je, zanim przyciśnie się gaz do końca na torze próbnym1.

1 Autor stosuje – jak wynika z podanego przykładu – termin testowanie negatywne częściowo w tym znaczeniu, które zwykle określa się jako testowanie wydajności albo testowanie pod obciążeniem. Zaś testowanie negatywne to wedle powszechnej praktyki określenie testowania zadaniami, gdzie dane wejściowe są niedozwolone, albo odwrotne do wymagań opisanych w specyfikacji (przyp. tłumacza).

Rysunek 5.2 Należy najpierw posłużyć się testowaniem pozytywnym zanim przejdzie się do testowania negatywnego.

Projektując i wykonując zadania testowe, zawsze najpierw należy wykonywać testy pozytywne. Trzeba najpierw sprawdzić, czy program działa w normalny sposób, zanim zacznie się testować „z grubej rury”. Może to zdziwić, ale wielką ilość błędów znajduje się używając programu w najnormalniejszy sposób.

Kiedy jest się już przekonanym, że oprogramowanie działa zgodnie ze specyfikacją w zwykłych warunkach, czas zastosować podstępne, złe, przebiegłe zadania, które mają na celu zmusić nawet dobrze ukryte błędy do ujawnienia się. Projektowanie i wykonywanie zadań testowych, których głównym celem jest złamanie programu nazywane jest testowaniem negatywnym albo wymuszniem awarii. Jak zobaczymy w dalszej części tego rozdziału, testy negatywne nie zawsze muszą wyglądać „groźnie”. Często wyglądają jak testy pozytywne, ale są wybrane strategicznie w punktach, gdzie często kryją się typowe słabości oprogramowania.

Komunikaty o awarii: test pozytywny czy test negatywny?

Często spotykaną grupą zadań testowych są zadanie mające na celu wymuszenie komunikatów o awariach programu. Znamy je dobrze – jak ten który się pojawia jeśli polecić zapisanie pliku na dyskietce nie włożywszy dyskietki do stacji dysków. Tego rodzaju zadania znajdują się na samej granicy między testami pozytywnymi i negatywnymi. Specyfikacja zapewne określa, że pewna sytuacja powinna spowodować wyświetlenie komunikatu o awarii. To jest typowe testowanie pozytywne. Z drugiej strony, wymuszając zaistnienie awaryjnej sytuacji, mamy do czynienia z czymś podobnym do testowania negatywnego. W gruncie rzeczy, test komunikatów o awariach jest zapewne jednym i drugim zarazem.

Nie warto trapić się ścisłą definicją. Ważne jest natomiast, aby wymusić pojawienie się wszystkich komunikatów o awariach, które przewidziała specyfikacja wymagań, ale też wynaleźć takie sytuacje awaryjne, których specyfikacja nie przewiduje. Prawdopodobnie znajdzie się błędy w obu wypadkach. Metoda klas równoważności

Wybór zadań testowych jest najważniejszym zadaniem testera, a metoda podziału na klasy równoważności jest najbardziej podstawową techniką stosowaną w tym celu. Podział na klasy równoważności jest procesem metodycznego zmniejszenia olbrzymiego (często nieskończonego) zbioru możliwych zadań testowych do zbioru znacznie mniejszego, ale nadal dostatecznie skutecznego.

W przykładzie z testowaniem Kalkulatora Windowsów z rozdziału 3-ego, nie dało się przetestować wszystkich możliwych kombinacji dodawania dwóch liczb. Podział na klasy równoważności jest sposobem, żeby wybrać wartości istotne i odrzucić zbędne.

Na przykład, nawet nic nie wiedząc o podziale na klasy równoważności, czy można przypuszczać, że przetestowawszy 1+1, 1+2, 1+3 i 1+4 warto jeszcze testować 1+5 i 1+6? Czy też dałoby się bezpiecznie założyć, że będą działać poprawnie?

A co można przypuszczać o 1+99999999999999999999999999999999 (największa liczba jaką daje się wprowadzic do kalkulatora)? Może to zadanie testowe jest trochę inne od pozostałych, może należy jakby do innej klasy, do innej klasy równoważności? Stanąwszy przed koniecznością wyboru, czy wybrałoby się raczej to zadanie, czy na przykład 1+13?

Patrzcie, już zaczynamy myśleć jak zawodowi testerzy oprogramowania!

Klasa równoważności to zbiór zadań testowych które testują to samo albo ujawniają ten sam błąd.

Jaka jest różnica między 1+99999999999999999999999999999999 a 1+13? Jeśli chodzi o 1+13, to wygląda ono na zwykłe, proste dodawanie, takie samo jak na przykład 1+5 albo 1+392. Natomiast 1+99999999999999999999999999999999 jest daleko, gdzieś na skraju. Jeśli wziąć największa możliwą liczbę i dodać do niej 1, może zdarzyć się coś niedobrego – może odkryje się błąd? Ten wypadek skrajny jest osobną klasą równoważności1, inną niż klasa „zwyczajnych” liczb.

Identyfikując klasy równoważności, warto wziąć pod uwagę sposoby, jak pogrupować razem podobne dane wejściowe, podobne dane wyjściowe i podobne działania programu. Te grupy mogą być stosownymi klasami równoważności.

Przypatrzmy się kilku przykładom.

38.Przy dodawaniu dwóch liczb, wydawała się istnieć wyraźna różnica między przetestowaniem 1+13 a 1+99999999999999999999999999999999. Nazwijmy to intuicją, ale pierwsze wydaje się zwykłym dodawaniem, drugie wydaje się ryzykowne. Ta intuicja jest trafna. Program musi wykonywac dodawanie jednego do wartości maksymalnej w zupełnie inny sposób niż dodawanie dwóch małych liczb. Te dwa wypadki, ponieważ program przypuszczalnie działa dla nich zupełnie inaczej, należą do dwóch różnych klas równoważności.

Jeśli zna się trochę programowanie, przychodzi do głowy jeszcze klika innych „specjalnych” liczb na których program będzie wykonywał operacje w specjalny sposób. Jeśli nie zna się programowania, uszy do góry – można opanować opisane techniki i stosować je bez konieczności rozumienia szczegółów kodu. 39.Rysunek 5.3 przedstawia menu Edycji z Kalkulatora z rozwiniętymi poleceniami Kopiuj i Wklej. Każdą z tych funkcji można wykonac na pięć różnych sposobów. Aby skopiować, można kliknąć na pozycję menu, nacisnąć „c” albo „C”, nacisnąć kombinację Ctrl+c albo Ctrl+Shif+c. Każde z tych poleceń skopiuje aktualną liczbę do Schowka – wykona tę samą procedurę i osiągnie ten sam wynik.

Rysunek 5.3 Różne sposoby przywołania funkcji kopiowania dają ten sam wynik.

Jeśli ma się przetestować funkcję kopiowania, można podzielić tych pięć różnych ścieżek na trzy grupy (klasy równoważności): kliknięcie na polecenie z menu, napisanie „c” albo naciśnięcie Ctrl+c. Jeśli wzrasta nasze zaufanie do jakości testowanego oprogramowania i weiemy, że jak dotąd funkcja kopiowania działała dobrze niezależnie od sposobu, w jaki się ją przywołało, można się zdecydować na dalsze ograniczenie ilości klas równoważności, nawet do jednej tylko, np. Ctrl+c. 40.W trzecim przykładzie, przyjrzyjmy się możliwym sposobom wprowadzenia nazwy pliku do standardowego okna dialogowego „Zapisz jako” (rysunek 5.4).

Rysunek 5.4 Pole do wprowadzenia nazwy pliku w oknie dialogowym „Zapisz jako” ilustruje kilka różnych możliwości podziału na klasy równoważności.

Nazwa pliku w Winodows może zawierać dowolne znaki z wyjątkiem \ / : * ? „ < > i | . Nazwy plików mogą składać się od jednego do 255 znaków. Konstruując zadania testowe dla nazw plików, można na przykład wybrać osobne klasy równoważności dla dozwolonych i niedozwolonych znaków, nazw o dozwolonej ilości znaków, nazw zbyt krótkich i nazw zbyt długich.

Pamiętajmy, że celem podziału na klasy równoważności jest zredukowanie zbioru możliwych zadań testowych do małego i dającego się zastosować w praktyce, ale wciąż jeszcze dostatecznie dobrze testującego oprogramowanie. Podejmując decyzję, że nie będzie się testować wszystkiego, ponosi się ryzyko, trzeba więc dobrze się zastanowić, jak wybrać klasy równoważności, żeby poziom ryzyka mieć pod kontrolą.

Jeśli posunąć się za daleko w redukcji ilości klas równoważności po to, aby zmniejszyc ilość zadań testowych, ryzykuje się wyeliminowanie testów które mogłyby ujawnic błędy. Początkującym testerom można zalecić, żeby poddali wybrane przez siebie klasy równoważności ocenie kogoś bardziej doświadczonego.

Na zakończenie wykładu o klasach równoważności warto dodać, że podział na klasy nie jest ścisły, jest subiektywny. Nie jest nauką, lecz raczej sztuką. Dwaj testerzy testujący ten sam program mogą zaproponować dwa różne zestawy klas równoważności. Jest to do przyjęcia pod warunkiem, że wybrane klasy zostały poddane przeglądowi i że zdają się zapewniać dostateczne pokrycie testowanego oprogramowania. Testowanie danych

Najprostszym sposobem wyobrażenia sobie programu komputeroego jest podzielic go na dwie części: dane oraz program. Danymi stają się dane wejściowe z klawiatury, kliknięcia myszą, pliki z dysku, wydruki itd. Program to przepływ sterowania, transakcje, logika i wyliczenia numeryczne. Często dzieli się test oprogramowania na analogiczne dwie części.

Wykonując testy na danych programu, kontroluje się informacje wprowadzane przez użytkownika, uzyskiwane wyniki, a także pośrednie wyniki ukryte w programie.

Kilka przykładów danych: 41.Wyrazy napisane przez użytkowanika w programie do przetwarzania tekstu 42.Liczby wprowadzone w arkusz kalkulacyjny 43.Ilość pozostających jeszcze do dyspozycji strzałów w grze komputerowej 44.Obraz wydrukowany przez oprogramowanie do przetwarzania fotografii 45.Kopie zapasowe plików na dyskietce 46.Dane przsyłane za pośrednictwem modemu przez linie telefoniczne

Ilość danych przetwarzanych nawet przez najprostszy program może być ogromna. Przypomnijmy sobie wszystkie możliwe kombinacje dodawania na zwykłym kalkulatorze. Wyobraźmy sobie teraz program do przetwarzania tekstu, system naprowadzający pocisków rakietowych albo program do przetwarzania danych na giełdzie. Sztuczka (jeśli można tak to nbazwać) jak uczynić to możliwym do przetestowania polega na tym, aby inteligentnie zmniejszyc ilość klas równoważności posiłkując się kilkoma podstawowymi zasadami: warunków granicznych, warunków granicznych dla podklas, pustych zbiorów danych i niedozwolonych danych1.

1 Uwaga tłumacza: Cały ten rozdział należałoby raczej zatytułować “Jak stosować klasy równoważności w praktyce”. Tytuł “Testowanie danych” wprowadza w błąd, ponieważ kłóci się z powszechnie przyjętymi definicjami “testowania danych”. Statyczne testowanie danych polega na poszukiwaniu w kodzie źródłowym błędów typu użycie zmiennej przed przydzieleniem jej wartości, użycie zmiennej poza zakresem jej definicji itp. Dynamiczne testowanie danych polega na oszacowaniu pokrycia testowego np. wszystkich ścieżek od definicji do pierwszego użycia wszysktkich zmiennych programu. Te

Warunki graniczne

Najlepszym sposobem opisania testowania warunków granicznych jest rysunek 5.5. Jeśli daje się bezpiecznie i pewnie przejść brzegiem urwiska i nie spaść, to prawie na pewno daje się też iść środkiem pola. Jeśli oprogramowanie działa poprawnie dla wartości skrajnych, to prawie na pewno będzie też działać dobrze w normalnych warunkach.

Warunki graniczne są szczególnych wypadkiem, ponieważ programownaie jest ze swojej natury narażone na problemy związane z definicją warunków brzegowych2. Oprogramowanie jest binarne – coś jest albo prawdą, albo nie. Jeśli jakaś operacja może być wykonywana na przedziale wartości, to istnieje ryzyko, że nawet jeśli programista nie zrobił błędu dla wartości ze środka przedziału, to popełnił błąd właśnie na krawędzi. Wydruk nr 5.1 pokazuje, jak problem warunków brzegowych łatwo wciska się do prościutkiego programu.

Wydruk 5.1 Prosty program w BASIC-u, demostrujący błąd warunków brzegowych.

1. Rem Stwórz 10-elementowy wektor całkowitoliczbowy 2. Rem Przydziel każdemu elementowi wartośc –1 3. Dim data (10) As Integer 4. Dim i As Integer 5. For i=1 To 10 6. data(i)=-1 7. Next i 8. End

Rysunek 5.5 Granica w oprogramowaniu przypomina krawędź urwiska.

metody są poza zakresem niniejszej książki (przyp. tłumacza).

2 Słów “brzegowe”, “graniczne” i “skrajne” używa się tutaj zupełnie zamiennie (przyp. tłumacza).

Ten fragment kodu ma stworzyć 10-elementowy wektor i nadać każdemu elementowi wektoru wartość –1. Wydaje się to bardzo proste. Wektor na 10 liczb całkowitych i licznik (i) zostają stworzone. Pętla For jest wykonywana od 1 do 10, i każdy element wektoru otrzymuje wartość –1. Gdzie problem z wartością graniczną?

W programach w BASIC-u, kiedy stwarza się wektor o podanym zasięgu – w tym wypadku, Dim data (10) as Integer – pierwszy element wektoru orzymuje numer 0, a nie 1. Podany program w rzeczywistości stwarza wektor mający 11 elementów, od data(0) do data(10). Program wykonuje pętlę od 1 do 10 i nadaje tym elementom wektoru wartość –1, ale ponieważ pierwszy element to data(0), nie otrzymuje on żadnej wartości. Kiedy program skończy przebieg, wartości wektoru są następujące: data(0)=0 data(6)=-1 data(1)=-1 data(7)=-1 data(2)=-1 data(8)=-1 data(3)=-1 data(9)=-1 data(4)=-1 data(10)=-1 data(5)=-1 Zwróćmy uwagę, że wartość elementu data(0) nie jest –1, lecz 0. Gdyby programista później zapomniał o tym, albo inny programista nie zdawał sobie sprawy, jak ten wektor został zainincjowany, mógłby użyć pierwszego elementu wektoru, data(0), sądząc że ma on wartośc –1. Tego typu problemy są bardzo powszechne i we większych, złożonych systemach często powodują przykre, trudne do zlokalizowania błędy1.

Rodzaje warunków granicznych

Czas już aby naprawdę uruchomić szare komórki i zastanowić się, co właściwie stanowi granicę. Początkujący testerzy nie zawsze zdaja sobie sprawę, jak wiele różnych granic moża wyznaczyć na jednym zbiorze danych. Często mamy do czynienia z kilkoma granicami rzucającyjmi się w oczy, ale jeśli poszperać głębiej, znajdzie się liczne dalsze granice bardziej ukryte, słabo zauważalne i narażone na trudne do wykrycia błędy.

Warunki graniczne to sytuacje powstające na krawędziach planowanych operacyjnych ograniczeń oprogramowania.

Zetknąwszy się z zagadnieniem w dziedzinie testowania oprogramowania, które wymaga zidentyfikowania granic, należy wziąć pod uwagę następujące typy danych: numeryczne szybkość znaki położenie pozycja wielkość ilość

Następnie trzeba uwzględnić następujące atrybuty powyższych typów: pierwszy/ostatni minimum/maksimum początek/koniec ponad/poniżej pusty/pełny najkrótszy/najdłuższy najwolniejszy/najszybszy najbliższy/najdalszy (w czasie) największy/najmniejszy najwyższy/najniższy następny/najdalszy (w kolejności)

Nie są to listy ostateczne. Zawierają większość możliwych warunków brzegowych, ale każde oprogramowanie jest inne i mogą w nim znajdować się zupełnie inne typy danych ze swoistymi warunkami granicznymi.

Mając możliwośc wyboru wartości, której użyje się do przetestowania danej klasy równoważności, zaleca się użycie wartości leżących na granicy klasy (przedziału wartości).

Testowanie w pobliżu granic

Nauczyliśmy się jak dotąd, że w celu ograniczenia ilości zadań testowych, należy podzielić zbiory danych, na których dokonuje operacji testowane oprogramowanie, na mniejsze zbiory zwane klasami równoważności. Ponieważ błędy oprogramowania często występują na granicach przedziałów wartości, wybierając dane testowe należące do klasy równoważności, warto wybierać wartości leżące na granicach – w ten sposób zwykle znajduje się więcej błędów.

Jednak samo testowanie wyłącznie na granicy przedziałów zwykle nie wystarcza. Najlepiej jest testować po obu stronach granicy1.

(przyp. tłumacza).1 Szegółowa analiza tych zagadnień należy to tak zwanego testowania domen (nie ma nic wspólnego z domenami Internetowymi). Te zagadnienia opisane są obszernie w kilku pozycjach z listy referencji

Najwięcej błędów znajduje się, stworzywszy dwie klasy równoważności (wokół każdej granicy). Do pierwszej z nich zaliczy się wartości, które leżą bezpiecznie we wnętrzu przedziału, stanowiącego klasę równoważności. Do drugiej klasy zaliczy się wartości niedozwolone, leżące poza testową klasą – wartości co do których oczekuje się, że mogą wywołać jakąś awarię.

Tesutując warunki graniczne, należy zawsze przetestować dozwoloną wartość tuż przy samej wartości granicznej, wartość graniczną oraz wartość niedozwoloną położoną jak najbliżej granicy.

Testowanie poza granicami jest z reguły łatwo uzyskać dodając jeden albo inną małą wielkośc, do górnej granicy, a odejmując jeden albo inną małą wielkość, od dolnej granicy2. Na przykład: 47.Pierwszy-1 / ostatni+1 48.Start-1 / koniec+1 49.Mniej niż pusty / więcej niż pełny 50.Jeszcze wolniejszy / jeszcze szybszy 51.Największy+1 / najmniejszy-1 52.Minimum-1 / maksimum+1 53.Tuż ponad / tuż pod 54.Jeszcze krótszy / jeszcze dłuższy 55.Jeszcze wcześniej / jeszcze później 56.Najwyższy+1 / najniższy-1

Przypatrzmy się kilku przykładom, które ułatwią zrozumienie, jak mysleć na temat możliwych granic i ich testów: 57.Jeśli pole tekstowe dopuszcza wprowadzenie od 1 do 255 znaków, należy wprowadzić 1 i 255 znaków, jako wartości z klasy wartości dozwolonych. Następnie należy wprowadzić 0 i 256 znaków jako wartości z klasy wartości niedozwolonych. 58.Jeśli program czyta i pisze dane na dyskietce, należy spróbować zapisać plik bardzo mały, najlepiej z jedną tylko pozycją, a następnie plik bardzo duży – dokładnie limit pojemności dyskietki. Należy też spróbować zapisać plik pusty i plik większy niż pojemność dyskietki.

2 Dotyczy to oczywiście zmiennych całkowitych. Zmienne rzeczywiste (zmiennoprzecinkowe) wymagają bardziej precyzyjnych manewrów numerycznych (przyp. tłumacza).

59.Jeśli program pozwala na wydruk wielu stron na jednej stronie, należy wydrukować jedną stronę i maksymalną ilość stron, na którą program zezwala, a następnie spróbować wydrukować zero stron i większą ilość niż maksymalna dozwolona przez program. 60.Program ma pole do wprowadzania 5-cyfrowego kodu pocztowego. Należy wtedy wypróbować 00-000, najprostsze i najmniejsze, a także wartość największą 99-999. Następnie należy wypróbować np. 00-00 i 100-000. 61.Testując symulator lotów, należy spróbować lecieć dokładnie na poziomie ziemi oraz na maksymalnej dozwolonej dla samoloty wysokości. Należy też spróbować lotu poniżej poziomu gruntu, poniżej poziomu wody, a także spróbowac wylecieć w kosmos.

Ponieważ nie da się przetestować wszystkiego, przeprowadzenie podziału na klasy równoważności i uwzględnienie warunków brzegowych, tak jak w powyższych przykładach, pozwoli storzyć zadania testowe dla wartości krytycznych. Jest to najskuteczniejsza metoda zmniejszenia ilości testowania do wykonania.

Jest niezmiernie ważne, żeby nauczyć się wciąż poszukiwać warunków brzegowych w każdym oprogramowaniu, z którym ma się do czynienia. Im więcej będzie się szukać, tym więcej odkryje się warunków granicznych, a tym samym i znajdzie błędów.

Wewnętrzne warunki graniczne

Normalne warunki graniczne wyżej opisane jest bardzo łatwo znaleźć. Są opisane w specyfikacjach i rzucają się w oczy, kiedy używa się programu. Niektóre wewnętrzne granice nie są widoczne dla użytkownika, ale powinien je skontrolować tester oprogramowania. Nazywa się je wewnętrznymi warunkami granicznymi albo warunkami granicznymi podklas.

Nie musi się być programistą ani umieć czytać kodu źródłowego, aby identyfikować te granice, ale potrzebna jest pewna ogólna orientacja w działaniu oprogramowania. Przykładami mogą być potęgi dwójki i tabela ASCII. Testowany program może mieć jeszcze inne granice wewnętrzne, warto więc porozmawiać z programistami, czy znają jeszcze jakieś inne wewnętrzne warunki graniczne, które wartoby przetestować1.

1 Przykłady opisane w książce stosują się w pierwszym rzędzie do interfejsu użytkownika dla typowych aplikacji użytkowych. Analogiczne przykłady mozna by znaleźć także dla innych typów oprogramowania, łącznie z systemami wbudowanymi, dla interfejsu komunikacyjnego itd.

Potęgi dwójki

Komputery i oprogramowanie oparte są na liczbach binarnych – bitach mogących miec wartość 0 lub 1, bajtach składających się z ośmiu bitów, słowach składających się z czterech bajtów itd. Tablea 5.1 pokazuje często używane potęgi dwójki i ich wartości.

Tabela 5.1 Wybrane wartości potęg dwójki Nazwa Wartość Bit 0 lub 1 Połówka bajtu 0-15 Bajt 0-255 Słowo 0-65 535 albo 0-4 294 967 295 Kilo 1024 Mega 1 048 576 Giga 1 073 741 824 Tera 1 099 511 627 776

Przedziały pokazane w tabeli 5.1 są listą wartości, które warto uwzględnić przy testowaniu warotści granicznych. Zwykle nie znajduje się ich definicji w specyfikacji produktu, często jednak są używane wewnętrznie przez program.

Przykłady potęg dwójki

Potęgi dwójki mają znaczenie przy testowaniu oprogramowania do realizacji protokolów komunikacyjnych. Szerokość pasma, czyli zdolność przesyłowa nośnika informacji, jest zawsze ograniczona. Zawsze jest potrzeba, żeby przesyłać informację jeszcze szybciej. Z tego powodu inżynierownie usiłują upchnąć jak najwięcej danych w każdy meldunek czy pakiet przesyłowy.

Jednym ze sposobów zwiększenia przepustowości łączy jest zagęszczenie informacji.

Załóżmy, że w protokole komunikacyjnym jest 256 poleceń. Protokół mógłby zezwalać na wysyłanie 15 najczęstszych poleceń zakodowanych w połówce bajtu. Komendy z numerami 16 – 256 trzeba by wysyłać zakodowane w cały bajt.

Użytkownik tego oprogramowania potrzebuje jedynie wiedzieć, że może wysłać 256 różnych poleceń i nie musi wiedzieć, że oprogramowaniue musi wykonywać specjalne wyliczenia na granicy półbajt/bajt.

Tworząc klasy równoważności, należy uwzględnić warunki graniczne wynikające z potęg dwójki. Jeśli, na przykład, program przyjmuje dane o wartości od jednego do 1000, należy włączyć do klasy wartosci dozwolonych 1 i 1000, może też 2 i 999. Aby pokryć testami także granice wynikające z zasady potęg dwójki, włącza się również 14, 15 i 16 na granicy pół-bajtu i 254, 255 oraz 256 na granicy bajtu.

Tabela ASCII

Innym wartym uwzględnienia warunkiem wewnętrznej granicy jest tabela znaków ASCII. Tabela 5.2. zawiera część tabeli ASCII.

Tabela 5.2  Częściowa tabela znaków ASCII

Znak Wartość ASCII Znak Wartość ASCII wartość zerowa 0 B 66 znak spacji 32 Y 89 / 47 Z 90 0 48 [ 91 1 49 ‘ 96 2 50 a 97 9 57 b 98 : 58 y 121 @ 64 z 122

A 65 { 123

Zwróćmy uwagę, że tabela 5.2 nie jest ciągłą listą. Znaki od ‘0‘ do ‘9‘ mają warotści ASCII od 48 do 57. Ukośnik, ‘/‘, wypada przed ‘0‘. Dwukropek, ‘:‘, wypada po ‘9‘. Duże litery od ‘A‘ do ‘Z‘ mają warotści od 65 do 90. Małe litery mają wartości od 97 do 122. Wszystkie te wartości mogą stanowić wewnętrzne warunki graniczne.

Testując oprogramowanie które przyjmuje tekstowe dane wejściowe albo które dokonuje konwersji plików tekstowych, warto przestudiować tabelę ASCII i wziąć pod uwagę wynikające z niej warunki graniczne przy ustalaniu klas równoważności. Na przykład testując pole do wprowadzania danych, które przyjmuje tylko znaki A – Z i a – z1, należy uwzględnić w klasie wartości niedozwolonych znaki bezpośrednio pod i bezpośrednio nad A, Z, a i z: ‘@‘, ‘[‘, ““ i ‘{‘.

ASCII i Unicode

Choć kod ASCII jest nadal bardzo popularny jako metoda kodowania znaków, stopniowo zastępuje go nowy standard zwany Unicode. Unicode został stworzony przez konsorcjum Unicode w 1991 roku aby rozwiązać problemy związane z tym, że kod ASCII nie wystarcza na wszystkie znaki istniejące we wszystkich pisanych językach.

Kod ASCII jest 8-bitowy i wystarcza wobec tego tylko na 256 różnych znaków. Unicode używa 16 bitów i wystarcza na 65536 znaków. Do dziś wykorzystano około 39000 znaków, z czego ponad 21000 przypada na znaki chińskie.

Wartość domyślna, wartość pusta, spacja, wartość zerowa, brak danych

Innym źródłem błędów jest sytuacja, kiedy program oczekuje danych wejściowych, ale użytkownik nie wprowadza niczego, jedynie na przykład naciska klawisz Enter. Ta sytuacja jest często przeoczona przy pisaniu specyfikacji wymagań i zapomniana przez programistów, ale zdarza się powszechnie podczas użytkowania programów.

Poprawnie działające oprogramowanie jest w stanie poradzić sobie z tą sytuacją. Program może posłużyć się wartością domyślną, często najniższą z przedziału dozwolonych wartości, albo wyświetlić zawiadomienie o awarii.

Okno dialogowe ustawień programu Paint w Windows (rysunek 5.6) umieszcza wartości domyślne w polach Szerokość i Wysokość. Co się stanie, jeśli użytkownik usunie te wartośći, celowo lub przez przypadek?

(przyp. tłumacza).1 Sprawa komplikuje się w wypadku języków używających znaków spoza serii A – B, na przykład polskiego. Znaki legalne dla pola tekstowego będą wtedy znajdować się rozsiane po większym przedziale wartości

Rysunek 5.6 Okno dialogowe ustawień programu Paint z wyzerowanymi polami Szerokość i Wysokość.

Najlepiej byłoby, aby program dostarczył jakieś typowe wartości domyślne, albo wyświetlił komunikat o błędzie. To właśnie robi program Paint (rysunek 5.7). Komunikat nie jest zbyt jasny, ale to odrębne zagadnienie.

Rysunek 5.7 Komunikat błędu wyświetlony jeśli nacisnęło się klawisz Enter z wyzerowanymi polami Szerokość i Wysokość.

Zawsze należy storzyć klasę równoważności dla wartości domyślnej, pustej, znaku pustego, wartości zerowej, zera albo braku danych.

Dla tych wartości warto stworzyć osobną klasę, zamiast upychać je razem z klasą wartości dozwolonych albo z klasą wartości niedozwolonych, ponieważ programy zwykle postępują z nimi w specjalny sposób. Jest prawdopodobne, że program wykonuje inną ścieżkę kiedy wprowadzi się wartość domyślną niż kiedy wprowadzi się niedozwolone wartości - 0 albo 1. Jeśli oczekuje się innego działania programu, dane te powinny mieć osobną klasę równoważności.

Dane nieprawidłowe, błędne, mylne i śmieci

Ostatnim rodzajem testowania danych jest użycie „śmieci”, danych bezużytecznych. Jest to przykład testu negatywnego. Kiedy już sprawdziło się, że program działa dla zadań testowych pozytywnych, pora sięgnąć po grubszy kaliber – prawdziwe śmieci.

Puryści mogą się spierać, że nie jest to naprawdę potrzebne, że skoro przetestowało się wszystko, co opisaliśmy dotąd, w tym dane niedozwolone, to program będzie działać poprawnie. W rzeczywistości jednak może się przydać sprawdzenie, jak program poradzi sobie z zupelnie przypadkowymi danymi.

Jeśli wziąć pod uwagę, że wiele programów sprzedaje się dziś w setkach milionów egzemplarzy, na pewno jakiś procent użytkowników posłuży się nimi nieprawidłowo. Jeśli w wyniku tego program ulegnie poważnej awarii i nastąpi utrata danych, użytkownik nie będzie obwiniać siebie samego – będzie winił oprogramowanie. Jeśli program nie robi tego, czego od niego oczekują, to jest błąd.

Tak więc możemy dobrze się zabawić podstawiając programowi dane nieprawidłowe, błędne, mylne. Jeśli program oczekuje liczb, podamy mu litery. Jeśli przyjmuje tylko liczby dodatnie, podamy mu ujemne. Jeśli działanie programu zależy od daty, sprawdzimy czy będzie działal w roku 3000-ym. Będziemy udawać, że mamy grube paluchy, naciskając po kilka klawiszy jednocześnie.

Nie ma innych zasad dla tego typu testowania – po prostu chodzi o to, by próbowac złamać program. Trzeba być pomysłowym, złośliwym i dobrze się przy tym bawić. Testowanie zmian stanów

Jak dotąd testowaliśmy jedynie dane – liczby, słowa, dane wejściowe i dane wyjściowe. Teraz zaczniemy sprawdzać, jak program przechodzi przez różne swoje stany. Stan programu to jego aktualny stan lub tryb działania. Spójrzmy na rysunki 5.8 i 5.9.

Rysunek 5.8 Program Paint w Windowsach w trybie rysowania ołówkiem.

Rysunek 5.7 Program Paint w Windowsach w trybie malowania rozpryskiwaczem.

Rysunek 5.8 pokazuje program Paint w Windowsach w trybie rysowania ołówkiem. Jest to stan początkowy po starcie programu. Zwróćmy uwagę, że wybrawszy jako narzędzie ołówek, kursor przybiera kształt ołówka, a na ekranie rysuje się cienka linia.  Rysunek 5.9 pokazuje ten sam program w trybie malowania rozpryskiwaczem. W tym stanie podane są rozmiary rozpryskiwacza, kursor wygląda jak puszka farby z rozpryskiwaczem, a linia wygląda jak spryskana farbą.

Spójrzmy bliżej na rozmaite opcje, jakie oferuje program Paint – wszystkie narzędzia, pozycje menu, kolory itd. Kiedy wybiera się jedną z tych opcji i program zmienia wygląd, rodzaje dostępnych komend, działanie – to znaczy że zmienił się jego stan. Program wykonuje kod: zmienia wartośc kilku bitów, przydziela wartość kilku zmiennym, ładuje jakieś dane – i zmienia stan.

Tester musi przetestować wszystkie stany programu i przejścia między nimi.