Archiwum wrzesień 2019, strona 17


rozdział 6
07 września 2019, 22:19

Rozdział 6 Analiza kodu

W TYM ROZDZIALE  Statyczne testowanie metodami szklanej skrzynki: badanie projektu konstrukcji i kodu  Formalne przeglądy  Standardy i reguły programowania  Lista kontrolna do przeglądów kodu

Testowanie oprogramowania nie ogranicza się do tego, by traktować specyfikacje lub program jak „czarną skrzynkę”, jak to opisne zostalo w rozdziale 4-ym „Badanie specyfikacji” oraz w rozdziale 5-ym „Test z klapkami na oczach”. Znając trochę programowanie, nawet niewiele, można testować bezpośrednio projekt konstrukcji systemu oraz kod źródłowy.

W wielu branżach te metoda weryfikacji jest o wiele mniej powszechna niż testowanie metodami czarnej skrzynki. Tam, gdzie wytwarza się oprogramowanie dla zastosowań wojskowych, finansowcyh, kontroli procesów przemysłowych oraz dla zastosowań medycznych – albo jeśli ma się szczęście pracować w organizacji stosującej zdyscyplinowane metody wytwarzania - weryfikacja produktu na poziomie projektu oraz kodu jest powszechna.

Ten rozdział jest wprowadzeniem do podstaw weryfikacji projektu konstrukcji i kodu źródłowego. Nie jest to zwykle zadanie dla początkującego testera, ale przypuszczalnie – mając pewne umiejętności w programowaniu – tester zetknie się z tą dziedziną1.

Najważniejsze punkty w tym rozdziale to: 86.Zalety statycznego testu metodami szklanej skrzynki 87.Różne rodzaje statycznych przeglądów metodą szklanej skrzynki 88.Standardy i reguły kodowania 89.Ogólne metody inspekcji kodu w poszukiwaniu błędów Statyczne testowanie metodami szklanej skrzynki: badanie projektu konstrukcji i kodu

Autor 1 Autor wychodzi z założenia, że typowy tester nie jest wykształconym programistą. Oczywiście, nie musi tak być (przyp. tłumacza).

Przypomnijmy sobie definicje testowania statycznego i testowania metodami szklanej skrzynki z rozdziału 4-ego. Testowanie statyczne odnosie się do testowania czegoś co nie jest działającym kodem – do badania i inspekcji. Testowanie metodami szklanej skrzynki (albo „białej skrzynki”) oznacza, że ma się dostęp do wnętrza konstrukcji, do kodu programu.

Tak więc statyczne testowanie metodami szklanej skrzynki jest procesem starannego i metodycznego przeglądu projektu konstrukcji, architektury lub kodu źródłowego w celu znalezienia błędów bez uruchamiania programu. Czasami używa się też nazwy analiza strukturalna.

Motywy stosowania statycznego testu metodami szklanej skrzynki są dwa: znajdować błędy jak najwcześniej oraz znajdować błędy które byłoby trudno zidentyfikować i zlokalizować przy pomocy dynamicznego testowania metodami czarnej skrzynki. Im bardziej osoby dokonujące przeglądów są niezależne od zespołu wytwarzającego kod, tym lepiej, zwłaszcza jeśli znalizy dokonuje się na niskim poziomie i wcześnie w cyklu wytwarzania.

Dodatkowym zyskiem z przeprowadzania statycznego testu metodami szklanej skrzynki jest to, że w jego trakcie zespół testerów często znajduje wiele pomysłów, które daje się potem zastosować do testowania dynamicznego. Nawet nie wnikając w zawiłe szczegóły kodu, uczestnictwo w przeglądach pozwala zidentyfikować obszary programu które są zawiłe i gdzie często pojawiają się błędy.

Zespoły projektowe różnie dzielą się odpowiedzialnością za statyczne testowanie metodami szklanej skrzynki. W niektórych zespołach odpowiedzialność za zorganizowanie przeglądów spoczywa na programistach, którzy zapraszają na nie testerów w roli niezależnych obserwatorów. W innych zespołach odpowiedzialność spoczywa na testerach, którzy zapraszają programistów na przeglądy. Każde z tych podejść może działać dobrze. Zespół projektowy ma prawo wybrać to, co mu najlepiej odpowiada.

Statyczny test metodami szklanej skrzynki ma jedną wadę – nie zawsze się go przeprowadza. Wiele zespołów uległo przesądom, że ta technika jest zbyt czasochłonna, zbyt kosztowna i niezbyt produktywna. Wszystko to jest nieprawdą – w porównaniu z kosztami testowania, znajdowania niektórych i pomijania wielu błędów na samym końcu projektów. Kłopot w tym, że główne zadanie programisty wiele osób rozumie jako pisanie linijek kodu i każde zadanie, które zmniejsza tak rozumianą efektywność wydaje się spowalniać cały proces powstawania oprogramowania.

Na szczęście, czasy się zmieniają. Coraz więcej przedsiębiorstw zdaje sobie sprawę z zysków płynących z wczesnego testowania i zatrudnia oraz szkoli swoich programistów i testerów, aby wykonywali testowanie metodami szklanej skrzynki. Nie jest to ścisła matematyka (o ile ktoś nie testuje implementacji algorytmów matematycznych), ale żeby zacząć ją stosować przydaje się znajomość kilku podstawowych technik. Jeśli kogoś interesuje pójście jeszcze dalej, możliwości są olbrzymie. Formalne przeglądy

Przegląd formalny to proces, który steruje statycznym testowaniem metodami szklanej skrzynki. Przegląd formalny może mieć rozmaite formy – począwszy od prostego spotkania między dwoma programistami aż po szczegółową, rygorystyczną inspekcję kodu1.

Cztery elementy wyróżniają przeglądy formalne: 90.Identyfikacja problemów. Celem przeglądów jest znalezienie błędów – nie tylko tego, co jest zrealizowane niepoprawnie, ale również tego, czego brakuje. Krytykę kieruje się na kod (przedmiot przeglądu), a nie na osobę autora. Uczestnicy nie powinni traktować krytyki osobiście. Poczucie wielkości, emocje i wrażliwe uczucia trzeba zostawić za drzwiami. 91.Postępowanie wedle zasad. Należy postępować wedle przyjętego zestawu zasad. Te zasady mogą np. określać ilośc kodu który podlega przeglądowi (zwykle kilkaset linii), ile czasu należy poświęcić (kilka godzin), na co należy zwrócić uwagę itd. Ważne jest, aby uczestnicy znali swoje role i czego się od nich oczekuje. Dzięki temu przeglądy przebiegają sprawniej. 92.Przygotowanie. Oczekuje się, że każdy uczestnik będzie przygotowany do przeglądu. Zależnie od rodzaju przeglądu, uczestnicy mogą mieć różne role i muszą je znać, aby móc je w czasie przeglądu realizować. Większość problemów znalezionych w procesie przeglądu

1 Czytelniku, wybacz, ale oto znowu nomenklatura autora różni się od powszechnie przyjętej. Otóż zwykle inspekcje i przeglądy formalne to synomimy, określające przeglądy dokonywane szczegółowo, rygorystycznie, według opisanego procesu, natomiast cała reszta – to po prostu przeglądy, albo przeglądy nieformalne. Nie ma to większego znaczenia, ale dobrze orientować się w tym nomenklaturowym chaosie, żeby rozumieć o czym jest mowa.

Warto też pamiętać, że nie istnieje powszechnie przyjęta klasyfikacja różnych typów przeglądów i podane przez autora definicje przeglądu koleżeńskiego, ręcznego sprawdzianu i inspekcji różnią się w

Dziwne coś:znajduje się w czasie przygotowań, a nie podczas samego przeglądu2. 93.Pisanie raportu. Grupa dokonująca przeglądu musi stworzyć pisemny raport podsumowujący wyniki przeglądu. Raport musi być dostępny dla całego zespołu projektowego. Jest istotne aby wszyscy poznali wyniki przyglądu: ile znaleziono błędów, gdzie itp.

Najważniejsze dla formalnych przeglądów jest to, że odbywają się według ustalonego procesu. Przypadkowe „zebrać się razem i przelecieć się przez trochę kodu” nie wystarcza, a nawet może okazać się przeciwksuteczne. Jeśli proces odbywa się bałaganiarsko, wiele błędów pozostanie nieznalezionych, a uczestnicy będą mieli poczucie zmarnowango czasu.

Właściwie realizowane, przeglądy mogą być wyśmienitą metodą wczesnego znajdowania błędów. Wyobraźmy je sobie jako pierwsze siatki na motyle (rysuek 6.1), które chwytają największe „pluskwy” na samym początku procesu wytwarzania. Oczywiściem mniejsze „pluskwy” przedostaną się, ale zostaną schwytane w kolejnych fazach testowania, stosujących siatki o coraz mniejszych oczkach.

Rysunek 6.1 Formalne przeglądy są jak pierwsze w serii siatek do łapania błędów – „pluskiew”.

Oprócz znajdowania problemów i błędów, formalne przeglądy przynoszą szereg pośrednich korzyści.

szczegółach od definicji innych autorów, czy też od nazewnictwa stosowanego w przedsiębiorstwach (przyp. tłumacza).

2 Wywód byłby jaśniejszy, gdyby słowo przegląd stosować tylko do całego procesu, zaś zebranie na którym spisuje się znalezione błędy nazywać zebraniem przeglądowym, a nie – myląco – również przeglądem (przyp. tłumacza)..

94.Komunikacja. Przekazuje się sobie informacje, których nie ma w oficjalnych raportach. Na przykład, testerzy zajmujący się testowaniem metodami czarnej skrzynki mogą uzyskać cenny wgląd w obszary możliwych kłopotów. Początkujący programiści mogą nauczyć się nowych technik od doświadczonych programistów. Kierownictwo może uzyskać lepsze wyczucie, jak projekt posuwa się do przodu. 95.Jakość. Kod napisany przez programistę kontroluje się szczegółowo, linijka po linijce, funkcja za funkcją – co często powoduje, że programiści stają się staranniejsi. To nie znaczy że w przeciwnym razie byliby niechlujni – po prostu wiedząc, że kod będzie szczegółowo skontrolowany przez kolegów, odruchowo podejmuje się dodatkowy wyciłek żeby unikać błędów. 96.Powstanie ducha zespołu. Dobrze zorganizowane i poprowadzone, przeglądy mogą stać się forum, gdzie testerzy i programiści uczą się lepiej nawzajem rozumieć swoją pracę i potrzeby, i szanować nawzajem swoje umiejętności. 97.Rozwiązania. Może udać się znaleźć rozwiązania dla trudnych problemów, choć nie wszystkie typy przeglądów pozwalają na ich dyskutowanie w czasie zebrania przeglądowego. Może być lepiej, żeby taka dyskusja odbywała się poza zebraniem.

Te pośrednie korzyści zdarzają się, ale nie można na nie liczyć z pewnością. Zdarza się, że – z różnych powodów – członkowie zespołu pracują w izolacji od siebie. Formalne przeglądy to świetny sposób, żeby zebrać ich razem w jednym pokoju, dyskutujących wspólny problem.

Kontrole koleżeńskie

Najłatwiej zebrać razem członów zespołu i zlecić im wykonanie pierwszego wspólnego przeglądu w postaci przeglądu koleżeńskiego, który jest najprostszą formą przeglądu. Te metoda jest w istocie rozmową w stylu „pokaż mi jak to zrobiłeś, to ja ci pokażę jak ja to zrobiłem.”

Przeglądy koleżeńskie często odbywają się tylko z udziałem programisty – autora kodu oraz jednego czy dwóch testerów albo programistów w roli inspektorów. Ta niewielka grupa po prostu wspólnie przegląda kod i poszukuje problemów i opuszczeń. Aby mieć pewność że przegląd będzie wydajny (i nie zamieni się w przerwę na kawę), wszystkie cztery elementy formalnego przeglądu powinny być zastosowane: szukanie problemów, postępowanie według zasad, przygotowanie do przeglądu i napisanie raportu. Ponieważ przeglądy koleżeńskie są nieformalne, często omija się niektóre z tych elementów. Jednak samo zebranie się razem aby przedyskutowac kod niejednokrotnie wystarcza, żeby znaleźć błędy1.

Ręczny sprawdzian

Ręczne sprawdziany są krokiem wzwyż pod względem poziomu formalizmu w porównaniu z przeglądami koleżeńskimi. Ręczny sprwadzian polega na tym, że programista, który napisał kod, prezentuje kod programu „na piechotę” niewielkiej grupie programistów i testerów. Insepktorzy powinni zawczasu otrzymać kopie kodu, tak żeby mogli go skontrolować i zapisać komentarze i pytania, które chcą zadać w trakcie przeglądu. Ważne, by choć jeden z inspektorów był doświadczonym programistą.

Prezentator czyta kod linijka po linijce lub funkcja po funkcji i wyjaśnia, co i dlaczego kod wykonuje. Inspektorzy słuchają i pytają o wszystko, co budzi wątpliwości. Ponieważ w ręcznym sprawdzianie z reguły uczestniczy więcej osób niż w kontroli koleżeńskiej, jest bardzo ważne by wszyscy byli do zebrania przygotowani i by przestrzegać reguł. Ważne jest też, by po ukończonym sprawdzianie został napisany raport, streszczający jakie błędy zidentyfikowano i jak planuje się je skorygować.

Inspekcje

Inspekcje są najbardziej formalnym rodzajem przeglądów. Mają precyzyjną strukturę i wymagają, żeby wszyscy uczestnicy byli odpowiednio wyszkoleni. Inspekcje tym różnia się od przeglądów koleżeńskich i ręcznych sprawdzianów, że osoba która przedstawia kod, zwana prezentatorem, nie jest autorem kodu. Ta sytuacja wymaga, by ktoś inny oprócz autora rozumiał przedstawiany materiał w szczegółach.

Autor :1 Istnieje modna ostatnio metoda wytwarzania kodu, zwana “programowaniem ekstremalnym” (extreme programming, xP), polegająca na tym, że programiści pracują w parach: jeden pisze kod, drugi dokonuje na bieżąco “koleżeńskiej inspekcji” powstającego kodu; po jakimś czasie zamieniają się rolami (przyp. tłumacza).

Pozostali uczestnicy inspekcji nazywani są inspektorami1. Każdy otrzymuje odrębne zadanie zanalizowania kodu z innego punktu widzenia, np. użytkownika, testera, albo osoby zajmującej się konserwacją oprogramowania. To pozwala uwzglądnić w czasie inspekcji różne aspekty produktu i często pozwala zidentyfikować różne rodzaje błędów. Jeden z inspektorów otrzymuje nawet zadanie inspekcji kodu od tyłu – to znaczy od końca do początku – aby upewnić się czy materiał został zrealizowany jednolicie i w pełni.

Dwaj inspektorzy otrzymuja również rolę przewodniczącego i sekretarza, którzy odpowiadają za przestrzeganie reguł i płynny przebieg całego procesu.

Po zebraniu przeglądowym inspektorzy mogą zebrać się ponownie, aby przedyskutować znalezione problemy i wspólnie z przewodniczącym przygotować pisemny raport idnetyfikujący konieczne zmiany dla naprawienia błędów. Programista następnie przeprowadza te zmiany, a przewodniczący sprawdza, czy zostały zrobione właściwie. Zależnie od zakresu i wielkości zmian oraz od stopnia krytyczności oprogramowania, może zostać zarządzona kolejna inspekcja dla znalezienia pozostałych błędów.

Stwierdzono, że inspekcje są bardzo skuteczne w znajdowaniu błędów, zwłaszcza w dokumentacji projektu konstrukcji i w kodzie. Popularność inspekcji wzrasta, gdy coraz więcej przedsiębiortstw i zespołów projektowych odkrywa ich zalety. Standardy i reguły programowania

W trakcie formalnych przeglądów inspektorzy poszukują problemów i opuszczeń w kodzie programu. Istnieje wiele typowych błędów, przy których program po prostu nie będzie działać poprawnie. Najlepjej znajduje się je poprzez staranną analizę kodu – doświadczeni programiści i testerzy zwykle umieją to robić.

Istnieje też typ problemów, gdzie kod wprawdzie działałby poprawnie, ale nie jest napisany zgodnie ze standardem lub normą. Można to porównać ze zdaniem, które wprawdzie da się zrozumieć i przekazuje informację, ale nie spełnia zasad gramatyki i składni języka polskiego. Standardy to są ustalone, zapisane, wymagane zestawy reguł – co wolno, a czego nie wolno. Reguły to są sugerowane, praktyczne wskazówki, rekomendacje, zlecane sposoby. Stanardy nie dozwalają wyjątków. Reguły nie są równie obowiązujące.

Autor 1 W języku angielskim inspektorzy w procesie przeglądu nazywani są tu reviewers, a inspektorzy uczestniczący w inspekcji – inspectors. Po polsku podobne rozróżnienie byłoby językowo bardzo niezręczne, dlatego zachowano jednakową nazwę dla obu (przyp. tłumacza).

Może to brzmieć dziwnie, że fragment oprogramowania może działać, może być nawet przetestowany i bardzo stabilny, a mimo to niepoprawny, ponieważ nie spełnia innych kryteriów. Istnieją trzy główne powody, dla których przestrzega się standardów i reguł: 98.Niezawodność. Udowodniono, że kod napisany zgodnie ze standardem lub zestawem reguł jest bardziej niezawodny i ma mniej błędów niż kod nie przestrzegający standardów. 99.Czytelność/łatwość konserwacji. Kod spełniający standardy i reguły jest łatwiej przeczytać, zrozumieć i konserwować. 100.Przenośność. Kod musi być niekiedy przeniesiony na inny sprzęt albo skompilowany innym kompilatorem. Jeśli jest zgodny ze standardem, będzie przypuszczalnie łatwiej – może nawet zupełnie bezboleśnie – przenieść kod na inna platformę.

Wymagania projektu mogą niekiedy domagać się bezwzględnego przestrzegania zasad międzynarodowego albo krajowego standardu, albo tylko zalecać przestrzeganie wewnętrznch zasad zespołu projektowego. Co jest ważne, to stosować jakiś zestaw reguł i weryfikować jego przestrzeganie w trakcie formalnych przeglądów.

Przykłady standardów i reguł programowania

Rysunek 6.2 pokazuje przykład standardu programowania, który określa zasady stosowania instrukcji goto, while i if-else w języku C. Niewłaściwe używanie tych instrukcji często prowadzi do błędów w kodzie, i większośc standardów programowania podaje zasady posługiwania się nimi.

TEMAT: 3.05 Ograniczenia zastsowania struktur kontrolnych STANDARD Nie należy używać instrukcji go to (jak rónież etykiet). Pętla while powinna być stosowana zamiast pętli do-while, z wyjątkiem miejsc gdzie logika problemu jednoznacznie wymaga aby wnętrze pętli zostało wykonane chociaż raz niezależnie od wartości warunku kontrolnego pętli. Tam gdzie pojedyncze if-else może zastąpić continue, należy używać ifelse.

UZASADNIENIE Instrukcja go to jest zabroniona, ponieważ stwierdzono empirycznie korelację jej użycia z występowaniem błędów i trudnym do zrozumienia kodem, oraz z powodu abstrakcyjnego, że algorytmy powinny być wyrażone w postaci struktur ułatwiających skontrolowanie zgodności programu ze strukturą realizowanego procesu. Użycia do-while odradza się z tego powodu, że pętla powinna być kodowana tak, żeby mogła byc łatwo ominięta, to znaczy kontrola warunku pętli powinna być dokonywana przed wejściem do wnętrza pętli.

Rysunek 6.2 Przykład standardów kodowania wyjaśniający jak należy używać kilku struktur kontrolnych języka (Dostosowane z Zasady programowania w C++, Thomas Plum i Dan Saks, © 1991, Plum Hall).

Pokazany tutaj standard składa się z czterech głównych części: 101.Tytuł opisuje czego standard dotyczy. 102.Standard (albo reguła) opisuje treść standardu/reguły wyjaśniając szczegółowo co jest, a co nie jest dozwolone. 103.Uzasadnienie wyjaśnia motywację standardu tak aby programista mógł zrozumieć czemu nakazyana praktyka jest korzystna. 104.Przykład pokazuje proste fragmenty kodu stosującego się do standardu. Nie zawsze jest to konieczne.

Rysunek 6.3 jest opisem reguły stosowania funkcji języka C w języku C++. Zwróćmy uwagę na różnice w sformułowaniach. Tutaj zaczyna się od „należy starać się unikać”. Reguły nie są tak surowe jak standardy i pozwalają na pewną elastyczność w razie potrzeby.

TEMAT: 7.02 C_problemy – Problemowe dziedziny z C

REGUŁA Należy starać się unikać aspektów języka C niezgodnych w programowaniem w C++ 1. Nie używać setimp i longimp jeśli istnieją obiekty z destruktorami, które mogłyby powstać między wykonaniem setimp a wykonaniem longimp. 2. Nie stosować makro offsetof z wyjątkiem kiedy stosuje się do członków tej samej struktury prostej. 3. Nie mieszać I/O w stylu C (używającego stdio.h) z I/O w stylu C++ (używającego iostream.h lub stream.h) w tym samym pliku. 4. Unikać stosowania funkcji języka C takich jak memcpy albo memcap dla kopiowania albo porównywania obiektów innych typów niż wektor znaków lub prosta struktura. 5. Unikać makro NULL z języka C; używać w zamian 0.

UZASADNIENIE Każdy z tych aspektów dotyczy obszaru tradycyjnych zastosowań z języka C, które stwarzają pewne problemy w C++. Rysunek 6.3 Przykład reguł kodowania pokazuje jak stosować pewne aspekty C w C++ (Dostosowane z Zasady programowania w C++, Thomas Plum i Dan Saks, © 1991, Plum Hall).

To kwestia stylu

Istnieją standardy, istnieją reguły, a wreszcie jest też styl. Z punktu widzenia jakości i testu oprogramowania, styl jest bez znaczenia.

Każdy programista, tak samo jak każdy pisarz albo malarz, ma swój włany niepowtarzalny styl. Nawet kiedy przestrzegane są reguły, a użycie konstrukcji jest z nimi zgodne, zwykle nadal jest łatwo powiedzieć, kto jest autorem danego oprogramowania.

Czynnikiem wyróżniającym jest styl. W programowaniu może to być na przykład długość komentarzy albo nazewnictwo zmiennych, albo jakiej formy są wcięcia w kodzie pętli. To jest specyficzny wygląd kodu.

Niektóre zespoły, w zapale stwarzania standardów i reguł, zaczynają czasem krytykować styl kodu. Tester oprogramowania powinien trzymać się z dala od takich dyskusji. Kiedy wykonuje się przeglądy fragmentów oprogramowania, należy ograniczyć uwagi do tego co jest błędne, czego brakuje albo co nie jest zgodne z pisanymi standardami czy regułami. Należy sobie samemu zadać pytanie - czy ma się naprawdę do czynienia z błędem czy też z różnicą opinii, z kwestią stylu. W drugim wypadku nie może być mowy o błędzie.

Skąd wziąć standardy?

Jeśli projekt musi stosować się do istniejących standardów, albo jeśli tylko chce się porównać kod ze standardami, do dyspozycji ma się kilka źródeł.

Narodowe i międzynarodowe standardy w dziedzinie języków programowania i technologii informatycznych można znaleźć: 105.American National Standard Institute (ANSI), www.ansi.org 106.International Engineering Consortium (IEC), www.iec.org 107.International Organisation for Standardisation (ISO), www.iso.ch 108.National Committee for Information Technology Standards (NCITS), www.ncits.org

Można też znaleźć dokumentację reguł programowania i opisy najlepszych praktyk w następujących organizacjach: 109.Association for Computing Machinery (ACM), www.acm.org 110.Institute of Electrical and Electronics Engineers, Inc (IEEE), www.ieee.org

Często można też uzyskać informację od producenta oprogramowania. Wielu producentów publikuje standardy do własnego oprogramowania i można je kupować za niewielką opłatą. Lista kontrolna do przeglądów kodu

Pozostała część tego rozdziału zajmuje się problemami, których należy szukać dokonując formalnego przeglądu kodu. Te listy kontrolne1 stosuje się oprócz porównywania kodu ze standardami oraz oprócz kontroli czy kod spełnia wymagania konstrukcyjne.

Aby rzeczywiście rozumieć i stosować opisane poniżej punkty kontrolne, należy mieć pewne doświadczenie z programowaniem. Jeśli nie ma się dosyć doświadczenia, warto przeczytać książkę dla początkujących, na przykład „Jak nauczyć się samemu programowania w 24 godziny” z wydawnictwa Sams Publishing2 zanim podejmie się próby szczegółowej analizy kodu programu.

Autor:1 Obawiam się, że taka wiedza może okazać się dramatycznie niewystarczająca (przyp. tłumacza).

Błędy w referencjach zmiennych

Błędy w referowaniu zmiennych często spowodowane są użyciem zmiennej, stałej, wektoru, ciągu znaków albo rekordu które nie zostały zainicjalizowane w sposób pasujący do trybu czy sposobu użycia i referowania. 111.Czy istnieje referencja do niezainicjalizowanej zmiennej? Szukanie opuszczeń jest tu równie ważne jak szukanie błędów. 112.Czy indeks wskazyjący pozycję w wektorze znaków albo w ciągu znaków zawsze jest w zakresie zgodnym z rozmiarami wektora? 113.Czy istnieją jakieś możliwe błędy „o jeden” w indeksowych referencjach do wektorów? Przypomnijmy sobie listing 5.1 z rozdziału 5ego. 114.Czy używa się zmiennych tam gdzie stała byłaby odpowiedniejsza, na przykład do kontroli granic wektora? 115.Czy zmiennej jest kiedykolwiek przydzielona wartość innego typu niż zmienna? Na przykład, czy kod przydziela wartość zmiennoprzecinkową zmiennej całkowitoliczbowej? 116.Czy pamięć, do której referują wskażniki, została przydzielona? 117.Kiedy wspólna struktura danych jest używana w kilku funkcjach, czy definicja struktury jest wszędzie taka sama?

Błędy w deklarowaniu zmiennych

Błędy w deklaracjach są spowodowane przez niewłaściwe deklarowanie zmiennych lub stałych. 118.Czy wszystkie zmienne mają zadeklarowaną właściwą długość, typ i rodzaj pamięci? Na przykład, czy zmienna nie powinna być raczej zadeklaraowana jako ciąg znaków niż jako wektor znaków? 119.Czy zmienne inicjalizowane w momencie deklaracji są inicjalizowane poprawnie i wartościa zgodną ze swoim typem? 120.Czy istnieją zmienne z podobnymi nazwami? Nie jest to koniecznie błędem, ale być może symptomem, że pomieszały się zmienne o podobnych nazwach z różnych miejsc w programie.

Wersja 9 października 2001, Bogdan Bereza

Ron Patton „Test oprogramowania” Część II strona 59 (86) 121.Czy istnieją zadeklarowane zmienne, których się potem nie używa albo użyte są tylko jeden raz? 122.Czy wszystkie zmienne zadeklarowane są w swoich modułach? Jeśli nie, czy jest zrozumiałe że zmienna jest wspólna z najbliższym modułem wyższego rzędu?

Błędy obliczeniowe

Błędy obliczeniowe to po prostu błędna matematyka. Wynik obliczenia jest fałszywy. 123.Czy gdzieś w obliczeniach używa się zmiennych różnych typów, jak np. dodawanie liczby całkowitej do zmiennoprzecinkowej? 124.Czy gdzieś w obliczeniach używa się zmiennych tego samego typu ale różnej długości – na przykład dodanie bajtu do słowa? 125.Czy zasady konwersji kompilatora wobec zmiennych różnych typów lub długości są wzięte pod uwagę w kodzie realizującym obliczenia? 126.Czy zmienna której przydzielony zostanie wynik obliczenia jest mniejsza niż wynik wyrażenia po prawej stronie? 127.Czy możliwy jest niedomiar lub przepełnienie w trakcie obliczeń? 128.Czy może gdzieś wystąpić dzielenie przez zero? 129.Czy w wypadku działań całkowitoliczbowych kod uwzględnia możliwość utraty dokładności, np. przy dzieleniu? 130.Czy wartość zmiennej może przekroczyć wartość „rozsądną”? Na przykład, czy wynik obliczania prawdopodobieństwa może być mniejszy niż zero lub wiekszy niż 100%? 131.W wyrażeniach zawierających wiele operatorów, czy zasady kolejności obliczania oraz zasady priorytetu operatorów są uwzględnione? Czy potrzebne są nawiasy dla wyjaśnienia?

Błędy w porównaniach

Mniejsze niż, większe niż, równe, nierówne, fałsz, prawda. Porównania i decyzje są zwykle bardzo narażone na błędy warunków brzegowych.

132.Czy porównanie jest poprawne? To się wydaje trywialne, ale prawie zawsze jest jakas wątpliwość czy porównanie ma być np. „mniejszy niż” czy też „mniejszy niż lub równy”. 133.Czy dokonuje się porównań między wartościami zmiennoprzecinkowymi? Jeśli tak, czy mogą wystąpić jakieś kłopoty z dokładnością? Czy na przykład 1.00000001 jest dostatecznie blisko 1.00000002, żeby porównanie wykazało równość? 134.Czy wyrażenie boole‘owskie wyraża właściwie to co powinno? Czy obliczenia na zmiennych boole‘owskich są poprawnie zbudowane? Czy kolejność oszacowania jest wzięta pod uwagę? 135.Czy argumenty wyrażenia boole‘owskiego są boole‘owskie? Czy na przykład zmienna całkowitoliczbowa zawierająca liczbę całkowitą nie jest używana w boole‘owskich obliczeniach?

Błędy przepływu sterowania

Błędy przepływu sterowania są skutkiem nieprzewidzianego – innego niż zamierzone – działania pętli i innych konstrukcji języka. Spowodowane są zwykle – bezpośrednio lub pośrednio – przez błędy w obliczeniach albo w porównaniach. 136.Jeśli język zawiera takie konstrukcje jak begin…end oraz do…while, czy zakończnia są jawne i czy właściwie zamykają konstrukcje? 137.Czy program, moduł, subrutyna albo pętla kiedykolwiek zakończą działania? Jeśli nie, czy to jest w porządku? 138.Czy istniej ryzyko przedwczesnego wyjścia z pętli? 139.Czy istnieje możliwość, że kod we wnętrzu pętli nie zostanie wykonany ani razu i czy jest to do przyjęcia? 140.Jeśli program zawiera wielokrotne rozgałęzienie, takie jak instrukcja switch… case, czy wartość zmiennej indeksowej może przekroczyć wartości indeksów możliwych rozgałęzień? Jeśli tak, czy istnieje odpowiednia porcedura postępowania w tym wypadku? 141.Czy istnieją błędy „o jeden”, które mogą spowodować niezaplanowane wykonanie wnętrza pętli?

Błędy w parametrach procedur

Błędy w parametrach procedur biorą się z niewłaściwego przekazywania danych do podprocedur. 142.Czy typ i wielkość parametrów otrzymanych przez procedurę zgodne są z wysłanymi przez kod przywołujący porcedurę? Czy ich kolejność jest poprawna? 143.Czy jeśli procedura ma wiele punktów wejścia (brr…), to istnieją odniesienia do parametrów nie zdefiniowanych dla danego punktu wejścia? 144.Czy jeśli zmienne posyła się do procedury jako parametry, czy ich wartość może zostać przypadkowo zmieniona przez procedurę? 145.Czy procedura zmienia wartość parametru, który ma być tylko wartością wejściową? 146.Czy skala dla poszczególnych parametrów jest taka sama w rutynie wywołującej i wywołanej – np. europejskie i amerykańskie miary? 147.Jeśli występują zmienne globalne, czy mają one takie same definicje we wszystkich procedurach, które z nich korzystają?

Błędy danych wejściowych i wyjściowych

Ta klasa błędów odnosi się do wszelkich działań związanych z czytaniem z pliku, przyjmowaniem danych z klawiatury albo z myszy, pisanie na urządzeniu wyjścia, np. na drukarce albo na ekranie. Podane poniżej punkty są bardzo ogólnikowe i uproszczone. Należy je uzupełnić, żeby zastosować do różnych typów oprogramowania i urządzeń, które się testuje. 148.Czy oprogramowanie dokładnie spełnia wymagania formatu danych pisanych albo czytanych przez urządzenie zewnętrzne? 149.Jeśli plik albo urządzenie peryferyjne nie jest gotowe, czy program radzi sobie właściwie z tą sytuacją? 150.Czy oprogramowanie radzi sobie gdy zewnętrzne urządzenie jest odłączone, nie odpowiada, albo jest przepełnione w trakcie czytania lub pisania? 151.Czy wszelkie możliwe błędy są obsługiwane przez oprogramowanie w przewidywalny sposób?

152.Czy wszystkie komunikaty błędów zostały sprawdzone i są trafne, mają odpowiednią treść, są poprawne gramatycznie i ortograficznie?

Inne kontrole

Poniższa lista zawiera pozycje, które nie pasowały do pozostałych kategorii. Nie jest ona w żadnym razie kompletna, ale powinna być dobrym początkiem dla sporządzenia listy pełniejszej, stosownej dla konkretnego projektu. 153.Czy oprogramowanie będzie działać poprawnie z wszystkimi wymaganymi językami? Czy jest w stanie posługiwać się rozszerzonym kodem ASCII? Czy używa Unicode zamiast ASCII? 154.Jeśli oprogramowanie ma być przenośne na inne kompilatory i mikroprocesory, czy zostalo to wzięte pod uwagę? Przenośność, jeśli wymagana, staje się często dużym problemem jeśli się jej nie planowało i nie testowało pod jej kątem. 155.Czy uwzględnione zostały wymagania kompatybilności tak żeby oprogramowanie działało poprawnie z różną ilością dostępnej pamięci, różnymi rodzajami sprzętu takiego jak karty dźwiękowe i graficzne, i z różnymi urządzeniami peryferyjnymi, jak drukarki i modemy? 156.Czy kompilacja programów powoduje różne komunikaty „ostrzegawcze” albo „informacyjne”? Zwykle oznacza to że w kodzie kryje się coś wzbudzającego wątpliwości. Puryści twierdziliby, że żadne komunikaty ostrzegawcze nie są dopuszczalne. Podsumowanie

Badanie kodu – statyczne testowanie metodami szklanej skrzynki – okazało się wielokrotnie skutecznym sposobem wczesnego znajdowania błędów. Ta metoda wymaga sporo przygotowań, aby mogła być produktywna, ale wiele badań pokazuje, że spędzony tym czas dobrze się opłaca. Żeby zaś uczynić ten rachunek jeszcze atrakcyjniejszym, dostępne są dzis programy pozwalające zautomatyzować dużą część pracy. Istnieją programy sprawdzające zgodność kodu źródłowego z wieloma istniejącymi standardami i z własnymi regułami, które można dostosowywać do potrzeb klienta. Również kompilatory są dzisiaj na tyle udoskonalone, że uruchomiwszy pełny arsenał ich możliwośi kontrolnych, można automatycznie zidentyfikowac większość problemów znajdujących się na omówionej na poprzednich stronach liście. Te narzędzia nie likwidują potrzeby przeglądów i inspekcji kodu, tylko ułatwiają je znacznie i dają testerom czas, aby móc szukać błędów jeszcze głębiej.

Jeśli w zespole projektowym nie testuje się obecnie na tym poziomie, a testerzy mają nieco doświadczenia w programowaniu, można zaproponować rozważenie takiej możliwości. Programiści i kierownictwo mogą się początkowo obawiać tego przedsięwzięcia, nie bardzo wierząc, by zyski rzeczywiście były aż tak duże – w końcu trudno jest udowodnić, że na przykład znalezienie błędu podczas inspekcji zaoszczędziło projektowi pięć dni, które trzeba by było spędzic wiele miesięcy później podczas testowania metodami czarnej skrzynki. Niemniej, statyczne testowanie metodami szklanej skrzynki nabiera coraz więcej impetu, i w niektórych branżach nikt nie odważy się dziś dostarczać niezawodnego oprogramowania, nie poddawszy go statycznemu testowaniu. 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.Wymień kilka korzyści wynikających ze stosowania statycznego testowania metodami szklanej skrzynki. 2.Prawda czy fałsz: statyczne testowanie metodami szklanej skrzynki pozwala znaleźć zarówno brakujące elementy jak i problemy. 3.Jakie są kluczowe elementy formalnych przeglądów? 4.Oprócz poziomu formalizmu, jaka jest zasadnicza różnica między inspekcjami a innymi rodzajami przeglądów? 5.Jeśli programista został poinformowany, że wolno mu używać nazw zmiennych nie dłuższych niż osiem znaków i wszystkie muszą zaczynać się dużą literą, czy mamy do czynienia ze standardem czy z regułą? 6.Czy listę kontrolną do przeglądów kodu opisaną w tym rozdziale powinno się przyjąć jako standard waszego zespołu do weryfikacji kodu?

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.