Testowanie oprogramowania: podręcznik dla początkujących 9788328301399

Testuj programy i śpij spokojnie! Ogólna teoria testowania, czyli po co nam testy i jak sobie z nimi radzić Projekt a

455 17 17MB

Polish Pages 213 Year 2014

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Testowanie oprogramowania: podręcznik dla początkujących
 9788328301399

Table of contents :
Spis treści
Przedmowa
Wstęp
Rozdział 1. Ogólna teoria testowania
1.1. Techniki testowania
1.2. Miara jakości oprogramowania
1.3. Środowisko testowe i produkcyjne
1.4. Replikacja błędów
1.5. U mnie błąd nie występuje
1.6. Symulatory aplikacji oraz generatory danych
1.7. Dokumentowanie testów
1.8. Kontrola wersji oprogramowania
1.9. Obsługa zgłoszeń
1.10. Testowanie obsługi wyjątków w kodzie
1.11. Narzędzia wsparcia pracy testera
1.12. Presja czasu
1.13. Profil profesjonalnego testera
1.14. Testowanie w oknie czasu
1.15. Jak wygląda realizacja projektu w praktyce?
1.16. Testowanie w cyklu życia oprogramowania
Rozdział 2. Poziomy wykonywania testów
2.1. Testy modułowe
2.2. Testy integracyjne
2.3. Testy systemowe
2.4. Testy akceptacyjne
Rozdział 3.Typy testów
3.1. Testy funkcjonalne
3.2. Testy niefunkcjonalne
3.3. Testy regresywne
Rozdział 4. Wprowadzenie do projektowania testów
4.1. Projektowanie testu w oparciu o technikę czarnej skrzynki
4.2. Projektowanie testu w oparciu o technikę białej skrzynki
4.3. Projektowanie testu w oparciu o doświadczenie testera
4.4. Przypadki testowe w ujęciu praktycznym
Rozdział 5. Psychologiczne aspekty procesu testowania
Rozdział 6. Syndrom zniechęcenia testami
Rozdział 7. Testowanie usług sieciowych
7.1. Narzędzie SoapUI — klient usługi sieciowej
7.2. Symulator serwera usług sieciowych — SoapUI Mock Services
7.3. Monitor TCP — Apache TCPMon
Rozdział 8. Wprowadzenie do automatyzacji testów
Dodatek A. Generowanie sumy kontrolnej
Dodatek B. Membrane SOAP Monitor
Dodatek C. Wireshark — analizator ruchu sieciowego
Dodatek D. Generowanie danych testowych
O autorze
Skorowidz

Citation preview

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Redaktor prowadzący: Ewelina Burska Projekt okładki: Studio Gravite/Olsztyn Obarek, Pokoński, Pazdrijowski, Zaprucki

Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/szteop_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

ISBN: 978-83-283-0139-9

Copyright © Helion 2014

 Poleć książkę na Facebook.com

 Księgarnia internetowa

 Kup w wersji papierowej

 Lubię to! » Nasza społeczność

 Oceń książkę

Więcej na: www.ebook4all.pl

Spis treści Przedmowa . ....................................................................................................... 5 Wstęp . ............................................................................................................... 7 Rozdział 1. Ogólna teoria testowania . ............................................................... 11 1.1. Techniki testowania . ................................................................................................. 13 1.2. Miara jakości oprogramowania ................................................................................ 17 1.3. Środowisko testowe i produkcyjne ........................................................................... 23 1.4. Replikacja błędów . ................................................................................................... 28 1.5. U mnie błąd nie występuje ....................................................................................... 30 1.6. Symulatory aplikacji oraz generatory danych . ......................................................... 31 1.7. Dokumentowanie testów . ......................................................................................... 34 1.8. Kontrola wersji oprogramowania ............................................................................. 35 1.9. Obsługa zgłoszeń . ..................................................................................................... 39 1.10. Testowanie obsługi wyjątków w kodzie ................................................................. 43 1.11. Narzędzia wsparcia pracy testera ............................................................................ 51 1.12. Presja czasu . ........................................................................................................... 52 1.13. Profil profesjonalnego testera ................................................................................. 54 1.14. Testowanie w oknie czasu ...................................................................................... 58 1.15. Jak wygląda realizacja projektu w praktyce? . ........................................................ 60 1.16. Testowanie w cyklu życia oprogramowania . ......................................................... 62

Rozdział 2. Poziomy wykonywania testów . ........................................................ 65 2.1. Testy modułowe . ...................................................................................................... 66 2.2. Testy integracyjne . .................................................................................................... 67 2.3. Testy systemowe . ...................................................................................................... 71 2.4. Testy akceptacyjne . .................................................................................................. 72

Rozdział 3. Typy testów . ................................................................................... 73 3.1. Testy funkcjonalne . .................................................................................................. 73 3.2. Testy niefunkcjonalne . .............................................................................................. 74 3.2.1. Testy wydajności . ............................................................................................ 74 3.2.2. Testy bezpieczeństwa aplikacji ....................................................................... 91 3.2.3. Testy przenośności kodu — testy instalacji . ................................................. 117 3.2.4. Testy ergonomii systemu informatycznego . ................................................. 118 3.3. Testy regresywne . ................................................................................................... 125

4

Testowanie oprogramowania. Podręcznik dla początkujących

Rozdział 4. Wprowadzenie do projektowania testów . ....................................... 129 4.1. Projektowanie testu w oparciu o technikę czarnej skrzynki . .................................. 131 4.1.1. Wartości brzegowe . ....................................................................................... 131 4.1.2. Przejścia pomiędzy stanami ........................................................................... 134 4.1.3. Projektowanie testu w oparciu o przypadki użycia . ...................................... 135 4.2. Projektowanie testu w oparciu o technikę białej skrzynki . .................................... 136 4.3. Projektowanie testu w oparciu o doświadczenie testera . ....................................... 140 4.4. Przypadki testowe w ujęciu praktycznym . .............................................................. 140

Rozdział 5. Psychologiczne aspekty procesu testowania . ................................. 149 Rozdział 6. Syndrom zniechęcenia testami . ..................................................... 153 Rozdział 7. Testowanie usług sieciowych . ....................................................... 165 7.1. Narzędzie SoapUI — klient usługi sieciowej . ....................................................... 165 7.2. Symulator serwera usług sieciowych — SoapUI Mock Services .......................... 171 7.3. Monitor TCP — Apache TCPMon ......................................................................... 177

Rozdział 8. Wprowadzenie do automatyzacji testów . ........................................ 183 Dodatek A Generowanie sumy kontrolnej . ....................................................... 187 Dodatek B Membrane SOAP Monitor . ............................................................. 189 Dodatek C Wireshark — analizator ruchu sieciowego . ..................................... 195 Dodatek D Generowanie danych testowych . .................................................... 197 O autorze . ...................................................................................................... 207 Skorowidz . .................................................................................................... 209

Przedmowa Niniejsza publikacja skierowana jest do wszystkich, którzy w sposób świadomy chcą odnaleźć się w roli testera oprogramowania. W momencie powzięcia decyzji o napisaniu tej książki założyłem, że powinna ona wstępnie ukształtować jednostkę w materii kontroli jakości oprogramowania. Książkę kieruję do osób myślących o rozpoczęciu pracy w zawodzie testera. Studiując tę publikację, Czytelnik powinien wynieść wystarczającą ilość wiedzy, która otworzy mu drogę do dalszego samokształcenia i kształtowania własnych poglądów w obszarze kontroli jakości oprogramowania. Istotą współczesnej edukacji jest zaszczepienie potrzeby nieustannego rozwijania potencjału intelektualnego, a system edukacji oprócz przekazania elementarnej wiedzy powinien nauczyć jednostkę, jak ma się sama uczyć. Pokładam nadzieję, że w niniejszej publikacji zostanie dostrzeżona taka wartość. Osoby, które regularnie uczestniczą w projektach testowych, studiując tę książkę, będą mogły zweryfikować i usystematyzować własną wiedzę. Nierzadko spojrzenie z innej perspektywy na znane problemy może okazać się katalizatorem zmian. Książka nie stanowi typowego podręcznika dydaktycznego. Stworzona została w oparciu o doświadczenia wyniesione z uczestnictwa w projektach, własne spostrzeżenia, przemyślenia i proces twórczy autora. Nie stanowi ona odpowiedzi na program kursu ISTQB ani nie omawia standardów IEEE dotyczących testowania oprogramowania. Według mnie — autora — to wartość dodana publikacji, gdyż pozwala na wychylenie się poza obszar pracy odtwórczej i złamanie szablonowego podejścia do tytułowej problematyki. Jednak od elementarza się nie ucieknie, dlatego też pewna partia materiału może znaleźć swoje odzwierciedlenie w powyżej wymienionych dokumentach. Drogi Czytelniku! W przypadku kiedy znajdziesz błąd w niniejszej publikacji, przyjmij moje szczere gratulacje. Właśnie postawiłeś pierwszy krok w zawodzie testera! Błąd zgłoś do redakcji wydawnictwa Helion. Proces testowania rozpoczyna się już na etapie pisania dokumentacji.

6

Testowanie oprogramowania. Podręcznik dla początkujących

Wstęp Testowanie oprogramowania to proces zapewnienia jakości oprogramowania. „Jakość” to termin określający stopień zgodności implementacji kodu z oczekiwaniami, potrzebami i założonymi wymaganiami postawionymi przez zamawiającego. Jakość to miara określająca, w jakim stopniu oprogramowanie spełnia wymagania biznesowe oraz zaspokaja oczekiwania klienta. Jakość oprogramowania można opisywać poprzez jego atrybuty, które zostaną wstępnie omówione w rozdziale 1.2. Testowanie to proces obejmujący wszelkie czynności mające na celu potwierdzenie zgodności zaproponowanych rozwiązań ze specyfikacją wymagań. Celem testowania jest wykrywanie i raportowanie błędów. Za błąd należy uważać pomyłkę programisty, której skutkiem jest niepożądane zachowanie aplikacji. Proces testowania ujawnia odchylenia od założonego działania systemu poprzez porównanie otrzymanego wyniku testu z oczekiwanym rezultatem. Błąd w wykonywanym kodzie znajdzie swoje odwzorowanie w postaci niepoprawnego zachowania programu. Na potrzeby niniejszej publikacji przyjmijmy uogólnienie, że błąd to nieoczekiwane zachowanie systemu na skutek pomyłki programisty. Jako błąd traktujmy rozbieżności w działaniu systemu w porównaniu z wyspecyfikowanymi wymaganiami. Błędem będziemy nazywać widoczny skutek, a nie przyczynę (wadę kodu). Oczywiście jedno wynika z drugiego. Niemniej jednak w tej publikacji oba pojęcia mogą się delikatnie przenikać. Notabene jest to odmienne podejście od prezentowanego w ramach kursu ISTQB, gdzie błędem jest pomyłka człowieka, która wywołuje defekt w programie, a wykonanie kodu z defektem zwykle skutkuje awarią. Intencją testowania oprogramowania jest zmniejszenie ryzyka wystąpienia błędu w środowisku produkcyjnym. Wczesne wykrycie błędu zmniejsza koszty jego naprawy oraz minimalizuje potencjalne konsekwencje. Wystąpienie niepożądanego zdarzenia w środowisku produkcyjnym wiąże się z wysokimi kosztami poprawy oraz generowaniem strat w postaci utraconych korzyści biznesowych (np. czasowa blokada możliwości realizowania zakupów w sklepie internetowym realnie wpłynie na wyniki finansowe przedsiębiorstwa). Do negatywnych skutków błędów produkcyjnych należy również zaliczyć ujmę w wizerunku oraz obniżenie zaufania do podmiotu (np. operatora, który czasowo stracił zdolność do obsługiwania transakcji kartą).

8

Testowanie oprogramowania. Podręcznik dla początkujących

Testowanie oprogramowania umożliwia zmierzenie jakości produktu. Wskaźnikiem oceny jest liczba wykrytych błędów. Jeżeli w całym projekcie testowym znaleziono mało błędów, tym samym podnosi się zaufanie do wytworzonego kodu. Testy winny być zaprojektowane w sposób miarodajny. Luki w pokryciu testami aplikacji mogą zafałszować ostateczną ocenę stanu systemu. Zgodnie z nauką ISTQB samo testowanie nie podnosi jakości oprogramowania. Jakość systemu wzrasta dopiero wtedy, gdy owe problemy zostaną rozwiązane. Podstawowa prawda o testowaniu mówi, że testy nie są w stanie udowodnić, iż w aplikacji błędów nie ma. Testy wykrywają błędy, ale nie udowadniają bezbłędności programu, nawet jeżeli wszystkie założone przypadki testowe zostaną zakończone pozytywnie. Podjęcie testów powinno nastąpić tak wcześnie, jak jest to możliwe. Rozpoczęcie czynności dopiero po etapie kodowania jest bardzo ryzykowne i stanowczo spóźnione. Testy powinny rozpocząć się już na etapie analizy i projektowania. Materiałem podlegającym weryfikacji będzie powstała dokumentacja. Testerzy w oparciu o rzeczywiste doświadczenia mogą wychwycić błędne założenia, których skorygowanie na etapie specyfikowania uchroni przedsięwzięcie od dodatkowych kosztów. Implementacja wadliwie zaprojektowanych rozwiązań podniesie kilkukrotnie koszty naprawienia błędu. Czynności testowe muszą być rozpoczynane we wczesnych fazach cyklu życia oprogramowania. Druga prawda o testowaniu mówi, że testy powinny kiedyś się zakończyć, mimo że nigdy nie nabierzemy przekonania o bezbłędności programu. Decyzja o przerwaniu testów uzależniona jest od poziomu ryzyka, prawdopodobieństwa wystąpienia sytuacji niepożądanych oraz płynących z owych zdarzeń konsekwencji. Testy gruntowne  tj. uwzględnienie wszystkich warunków wstępnych i kombinacji danych wejściowych  są niemożliwe. Ograniczenia ekonomiczne oraz brak uzasadnienia praktycznego negują potrzebę kontynuowania testów w nieskończoność. Kluczem do sukcesu jest trafne wytypowanie momentu zakończenia testów. Warunkiem koniecznym jest zamknięcie wszystkich przypadków testowych z wynikiem pozytywnym. Niestety nad wymienionym kryterium często biorą górę tak zwane decyzje polityczne mające na celu skrócenie terminu przekazania produktu klientowi (świadome przekazywanie produktu z ujawnionymi i niepoprawionymi błędami, emisja bez przeprowadzenia pełnych testów). Zamknięcie przypadków testowych uwiarygodnia pogląd, że cel projektu został osiągnięty. Istotą sukcesu jest optymalne zaplanowanie testów i prawidłowe zaprojektowanie przypadków testowych. Ta tematyka będzie analizowana w kolejnych rozdziałach książki. Trzecia prawda o testowaniu mówi, że zespół kontroli jakości może być postrzegany jako czynnik blokujący wydanie produktu klientowi w momencie, kiedy cały projekt osiągnął punkt krytyczny (datę końcową). Otóż „twórca” przedsięwzięcia polegającego na wyprodukowaniu oprogramowania zgodnego z oczekiwaniami klienta i w zadanym harmonogramie powołał grupy i powierzył im ściśle określone zadania. Ostateczne testy jakościowe wykonywane są jako jeden z ostatnich etapów w cyklu produkcji. Niestety często występującym zjawiskiem jest przekazywanie produktu do testów jakościowych ze znaczącym opóźnieniem. Rysunek W.1 ilustruje hipotetyczną sytuację, w której zespół programistów skonsumował własny czas, czas przypisany na testy wewnętrzne oraz fragment czasu zarezerwowanego na testy jakościowe (punkty A i B grafiki).

Wstęp

9

Rysunek W.1. Hipotetyczny przebieg realizacji projektu produkcji oprogramowania

Zespół testów wewnętrznych (testy modułowe) rozpoczął pracę w momencie, kiedy faktycznie aplikacja powinna być już weryfikowana na etapie ostatecznych testów jakościowych (punkt B). Zatem faza, w której decyduje się o oficjalnym wydaniu produktu, rozpoczęła się niemalże przed datą finalnego zakończenia projektu (punkt C). Osiągnięcie punktu C stanowi moment krytyczny, gdyż pojawia się realna groźba utraty części zysku na skutek pokrycia kar umownych za niedotrzymanie terminu realizacji umowy. Nietrudno sobie wyobrazić, że powstanie konflikt interesów pomiędzy kierownikiem projektu (ang. Project Manager — PM) a zespołem testów. Każdy z uczestników będzie bronił partykularnych interesów. O tym, jak się zachować w takiej sytuacji, będzie mowa w dalszych częściach książki. Istnieje zasada kumulowania się błędów. Oznacza to, że większość wykrytych problemów znajduje swoje źródło w niewielkiej liczbie modułów. W aspekcie praktycznym duża „błędogenność” określonego fragmentu kodu wygeneruje większość błędów w całościowym ujęciu aplikacji.

10

Testowanie oprogramowania. Podręcznik dla początkujących

Rozdział 1.

Ogólna teoria testowania Warunkiem wstępnym do zgłębiania tajników testowania oprogramowania jest usystematyzowanie wiedzy już posiadanej oraz ujęcie luźnych myśli i odczuć w nieco bardziej ścisłe ramy. W tym rozdziale postaramy się wypracować podstawy niezbędne do dalszego studiowania niniejszej pozycji. Testowanie to proces weryfikacji zachowania systemu i porównywanie otrzymanego rezultatu z przewidywaniami. Jednym z elementów testowania jest uruchamianie aplikacji w zadanych warunkach początkowych. Celem testowania jest zminimalizowanie ryzyka wystąpienia błędu w środowisku produkcyjnym oraz uzyskanie maksymalnie dużej pewności, że oprogramowanie funkcjonuje zgodnie z założeniami. Test to wykonywanie czynności polegających na uruchamianiu określonej funkcjonalności w zadanych warunkach i parametrach wejściowych. Celem wykonania testu jest weryfikacja zachowania aplikacji, tj. porównanie rzeczywistego efektu z oczekiwanym. Test kończy się sukcesem w sytuacji, kiedy realne działanie systemu pokrywa się z przewidywaniami. Retest to nic innego jak powtórne wykonanie przypadku testowego, który uprzednio zakończył się niepowodzeniem. Wynik negatywny pierwotnego testu determinował wykonanie zmiany w kodzie w celu osiągnięcia założonego rezultatu. Ocenę skuteczności poprawki realizuje się poprzez powtórzenie testu. Liczba iteracji jednego lub zbioru przypadków testowych z zadanego obszaru świadczy o skuteczności i jakości kodu przekazanego przez zespół programistów. Moduły, obszary aplikacji, które naznaczone są wysokim współczynnikiem powtórzeń testów, powinny być oznaczone jako elementy o podwyższonym ryzyku. Pośrednio płynącym wnioskiem ze znacznej ilości retestów może być personalne wskazanie osób, których kod należy kojarzyć jako błędogenny. W analogiczny sposób można by poczynić wnioski odwrotne i wytypować koderów, do których pracy nie trzeba podchodzić ze szczególną ostrożnością. Błąd — w niniejszej książce termin „błąd” odnosi się do ostatecznego zachowania aplikacji, które jest niezgodne, sprzeczne lub niepełne w porównaniu z zapisem w wymaganiach. Reakcja programu jest odzwierciedleniem instrukcji kodu, która w danym momencie została wykonana. De facto niepożądane zachowanie systemu jest skutkiem (objawem) błędnie wykonanej implementacji, tj. wady kodu. Niemniej jednak postarajmy się mówić o błędzie jako skutku, którego przyczyna tkwi w instrukcjach kodu.

12

Testowanie oprogramowania. Podręcznik dla początkujących

Błąd krytyczny — często spotykamy się z określeniem „błąd krytyczny” w odniesieniu do zachowania systemu. W zależności od kontekstu i kultury pracy określenie to może przybierać nieco inne znaczenie, a w zasadzie błąd może mieć nieco inną wagę w stosunku do porównywalnych sytuacji. Wynika to z różnic w specyfikach systemów, roli, jaką odgrywają, i wrażliwości klienta na problemy z oprogramowaniem. Niemniej jednak za błąd krytyczny należy uznać niepożądane zachowanie systemu, które realnie wpływa na funkcjonowanie procesów biznesowych. Ów wpływ może objawić się poprzez konieczność wyłączenia jakiegoś modułu, gdyż nie może on dalej stanowić wsparcia dla użytkowników, lub jako skutek błędnego wykonania jakiegoś procesu biznesowego, np. błędnie wyliczono kwoty faktur VAT za usługi telekomunikacyjne. Jeżeli system błędnie zaksięguje odsetki od niezapłaconych faktur VAT, to jest to błąd krytyczny, gdyż usługodawca poniesie realne straty (operacyjne, dotyczące wizerunku). Jeżeli aplikacja wygeneruje billingi jedynie dla klientów, którzy nie przekroczyli 5000 połączeń w okresie rozliczeniowym, to również jest to błąd krytyczny. Natomiast w przypadku, kiedy jakiś raport błędnie podaje sumę połączeń (np. minuty), nie należy klasyfikować tego zdarzenia jako krytyczne. Stosunkowo często środki masowego przekazu informują o problemach z dostępem do środków przez klientów jakiegoś banku — to jest błąd krytyczny. Natomiast błędne wyświetlanie waluty rachunku (bez wpływu na zdarzenia księgowe), w jakiej faktycznie jest prowadzony rachunek, nie jest sytuacją krytyczną; nawiasem mówiąc, całkiem przyjemnie nagle zobaczyć 5000,00 euro zamiast 5000,00 zł. Błąd blokujący — to pojęcie podnoszone jest głównie w procesie testowym jako sytuacja, która uniemożliwia kontynuowanie testów danego obszaru. Załóżmy, że w systemie istnieje wada uniemożliwiająca mu poprawną kooperację z usługą sieciową, której wywołanie jest niezbędne do przejścia do kolejnych kroków operacji biznesowej. Jest to błąd blokujący, gdyż nie jesteśmy w stanie zweryfikować niczego, co kryje się poniżej tego etapu (wywołania funkcji WS). Tego rodzaju błędy powinny być poprawiane z bardzo wysokim priorytetem, gdyż blokują testy i jednocześnie nie zatrzymują upływu czasu (termin dostawy). Trywialność wykrycia błędu — do jednych z najbardziej przykrych sytuacji w pracy testera należy zaliczyć „wpadki”, które niosą poważne konsekwencje, a istota (błąd) owego stanu rzeczy była łatwa do wykrycia w fazie testów. Niestety bywa tak, że coś umknie uwadze testera i ujawni się dopiero w środowisku produkcyjnym, niosąc za sobą wymierne problemy. Sytuację należy również odwrócić i pochylić się nad analizą ewentualnych „ciężkich błędów”, które ujawniają się w bardzo wyrafinowanych i skomplikowanych sytuacjach. Niezależnie od tego, z jakim typem błędu przyszło nam się borykać (pod względem wysiłku wykrycia), należy poddać gruntownej analizie zaistniałą sytuację w celu nauki i udoskonalenia testów (uszczelnienie). Debugowanie (ang. debugging — odrobaczanie) jest to proces analizowania, wyszukiwania i eliminacji wad w oprogramowaniu poprzez kontrolowane (krokowe) wykonywanie kodu. Jest to czynność wykonywana przez programistów. Przypadek testowy to zestaw początkowych i końcowych warunków wykonania określonej ścieżki w programie, realizacja owej ścieżki oraz zapis wyniku w celu zweryfikowania zachowania aplikacji (fragmentu), tj. sprawdzenia zgodności z zapisanymi wymaganiami. Lista przypadków testowych to jak sama nazwa wskazuje dokument, który w sposób skonsolidowany ujmuje powołane przypadki testowe.

Rozdział 1.  Ogólna teoria testowania

13

Scenariusz testów opisuje sekwencje czynności (zdarzenia), jakie należy wykonać w celu przeprowadzenia testu. Wymaganie (specyfikacja) to zestaw warunków i oczekiwań, jakie musi spełniać aplikacja. Wymagania wobec programu powinny być ujęte w formalnej dokumentacji. Specyfikacja powinna być precyzyjna, kompletna i niepozwalająca na zbyt dowolną interpretację przez różne zainteresowane strony. W specyfikacji wymagań oprócz literalnego zapisu oczekiwanego zachowania systemu powinny znaleźć się wszelkie materiały pochodne (wspomagające), takie jak pliki WSDL, opisy plików wymiany danych, spis reguł walidacyjnych dla pól formularzy itp. Dokumentacja to określenie odnoszące się do formalnie wydanych dokumentów, takich jak specyfikacja wymagań, projekt techniczny, scenariusze i przypadki testowe, dokumentacja użytkownika, instrukcje wgrania itp. Na podstawie tych materiałów wykonana zostanie implementacja, przeprowadzone testy jakościowe i odbiorcze. Dokumentacja będzie również stanowić wsparcie do uruchomienia produkcyjnego aplikacji oraz sporządzenia instrukcji dla użytkownika końcowego. Na potrzeby niniejszej książki to pojęcie będzie miało szerokie i ogólne znaczenie, które odnosi się do formalnych dokumentów. Standardy odnoszące się do testowania oprogramowania Standaryzacja jest wszechobecna w niemal każdym obszarze życia. Testowanie oprogramowania również ujęte zostało w szeregu upublicznionych standardów. Do najpopularniejszych z nich należy zaliczyć:  IEEE 829-1998 (IEEE Standard for Software Test Documentation):

http://standards.ieee.org/findstds/standard/829-1998.html  BS 7925-2 Software Component Testing Standard:

http://www.testingstandards.co.uk/bs_7925-2.htm  IEEE 1012-2012 (IEEE Standard for System and Software Verification and

Validation): https://standards.ieee.org/findstds/standard/1012-2012.html IEEE (ang. Institute of Electrical and Electronics Engineers) to organizacja skupiająca inżynierów elektryków i elektroników — Instytut Inżynierów Elektryków i Elektroników: http://www.ieee.org/, http://www.ieee.pl/.

1.1. Techniki testowania Tester ma do dyspozycji dwie podstawowe techniki testowania:  metodę czarnej skrzynki (ang. Black Box),  metodę białej skrzynki (ang. White Box).

Metoda czarnej skrzynki nie przewiduje zgłębiania wewnętrznej struktury programu. Skupia się na weryfikacji założeń funkcjonalnych bez odniesienia do kodu aplikacji.

14

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.1 obrazuje ideę metody czarnoskrzynkowej. Testy oparte na tej metodzie polegają na uruchamianiu programu w warunkach maksymalnie zbliżonych do naturalnych (rzeczywista obsługa systemu). Zaletą metody czarnej skrzynki jest brak wymogu posiadania umiejętności analizy i czytania kodu. Kontroler jakości posługuje się jedynie udostępnionym interfejsem lub zewnętrznym klientem, np. SoapUI w przypadku testów funkcji web service. Tester nie zgłębia, w jaki sposób kod aplikacji realizuje założenia funkcjonalne. Tester uruchamia weryfikowaną opcję programu, definiuje parametry wejściowe i sprawdza wynik wykonanej akcji. Wadą tej metody jest brak gwarancji, że wszystkie instrukcje kodu zostaną przetestowane (wykonane). W przypadku wystąpienia błędu jego przyczyna nie jest znana. Testy metodą czarnoskrzynkową powinny być uzupełnione testami opartymi na technice białej skrzynki. Rysunek 1.1. Technika czarnoskrzynkowa

Metoda testów białej skrzynki opiera się na analizie struktury kodu. Wymaga ona podstawowej znajomości zasad programowania (czytanie i rozumienie kodu) oraz zastosowanej technologii, np. Javy. Dostęp do źródła kodu pomaga w ustaleniu typu i zakresu danych wejściowych potrzebnych do przeprowadzenia testu. Ponadto w momencie wystąpienia błędu jawność kodu umożliwia zidentyfikowanie problemu oraz ustalenie miejsca jego występowania. Rysunek 1.2 przedstawia ideę techniki białoskrzynkowej. Główną wartością testów białej skrzynki jest możliwość zmierzenia (oceny) pokrycia testami analizowanego obszaru aplikacji. Jednak jest to równocześnie wadą omawianej metody, gdyż tester projektując i/lub wykonując testy, może ulec podprogowej sugestii i zaaranżować przypadek „pod kod”, a nie względem wymagania funkcjonalnego. Zważywszy na powyższe zagrożenie, testy wykonywane metodą białej skrzynki powinny być wykonywane po testach funkcjonalnych. Testy białej skrzynki powinny wykazać luki w pokryciu (wykonaniu) kodu przez scenariusze testowe. Wyniki takich testów po analizie posłużą do udoskonalenia testów (przypadków testowych), tak aby zwiększyć staranność pokrycia testami weryfikowanych struktur. Rysunek 1.2. Technika białoskrzynkowa

Wartością testów wykonywanych metodą białej skrzynki jest możliwość wychwycenia fragmentów kodu, które potencjalnie mogą obniżyć wydajność. Dokonać oznaczenia „podejrzanego” kodu może tester, który posiada podstawowe kompetencje

Rozdział 1.  Ogólna teoria testowania

15

programistyczne, zna właściwości i cechy zastosowanej technologii oraz umie przełożyć zapis kodu na docelowy skutek jego uruchomienia. Nie, nie trzeba wykazywać się w pełni umiejętnościami przypisywanymi do stanowiska programisty, choć znajomość zasad programowania jest podstawą pozwalającą wspierać optymalizację kodu w trakcie wykonywania testów. Wydatnym przykładem jest instrukcja IF, której warunek logiczny składa się z dwóch członów (listing 1.1, listing 1.2). Jeżeli w podanym przykładzie zostanie spełniony pierwszy warunek instrukcji (ten skomplikowany), to drugi jest automatycznie pomijany (ten prostszy). Przekładając algorytm na język naturalny: jeżeli pracownik nie był na zwolnieniu lekarskim (zwLekarskie=0), to weryfikujemy, czy jego pensja nie przekracza 7000. Jeżeli nie przekracza, to otrzymuje on premię w wysokości 1500, w innym przypadku nie otrzymuje dodatku. Jeśli pracownik przebywał na zwolnieniu lekarskim (1), nie jest już badany warunek wysokości pensji. Z biznesowego punktu widzenia kod wygląda jak najbardziej dobrze. Problem polega na tym, że parametr wysokości pensji (kwotaPensja) pobierany jest z interfejsu użytkownika (GUI), tj. pani kadrowa wpisuje kwotę ręcznie, natomiast informacja o zwolnieniach lekarskich pobierana jest z bazy danych (przykład nie odwzorowuje tego w kodzie). Testy funkcjonalne potwierdziły, że pole obsługujące wynagrodzenie zawsze musi być uzupełnione — zatem jest to pewnik. Przebywanie na zwolnieniu lekarskim jest zdarzeniem losowym. Gdy analizuje się przedstawiony kod (listing 1.2), nasuwa się koncepcja zamienienia miejscami warunku A z B (listing 1.3)  tak aby w pierwszej kolejności program sprawdzał wysokość pensji, a dopiero później starał się potwierdzić brak absencji w pracy. Takowe rozwiązanie zoptymalizuje kod pod względem wydajnościowym, gdyż zagwarantuje wykonywanie „kosztownego kodu” (łączenie się z bazą danych oraz wykonywanie zapytania) w sytuacjach tego wymagających. Takie podejście oszczędza zasoby z uwagi na to, że zezwala na wykonanie kosztownego kodu jedynie w odpowiedzi na rzeczywiste zapotrzebowanie biznesowe, a nie zawsze, tak jak to miało miejsce przy pierwszej propozycji rozwiązania (listing 1.2). Listing 1.1. Pseudokod opisujący instrukcję IF z warunkiem logicznym && języka Java if (A_zwLekarskie=true && B_kwotaPensja) { premia=tak; } else { premia=nie; }

Listing 1.2. Instrukcja IF z warunkiem logicznym && w Javie. Weryfikacja pensji na drugiej pozycji package pl.testowanie; public class Optymalizacja { static int zwLekarskie = 1; // 0 - brak zwolnienia, 1 - zwolnienie static int kwotaPensja = 5000; static int kwotaPremia; public static void main(String[] args) { if (zwLekarskie == 0 && kwotaPensja < 7000) { kwotaPremia = 1500; System.out.println("Przyznano premię w wysokości: " + kwotaPremia); }

16

Testowanie oprogramowania. Podręcznik dla początkujących else { kwotaPremia = 0; System.out.println("Premia nie została przyznana z powodu zbyt wysokiej pensji i/lub zwolnienia lekarskiego"); } } }

Listing 1.3. Instrukcja IF z warunkiem logicznym && w Javie. Weryfikacja zwolnienia lekarskiego na drugiej pozycji package pl.testowanie; public class Optymalizacja2 { static int zwLekarskie = 0; // 0 - brak zwolnienia, 1 - zwolnienie static int kwotaPensja = 5000; static int kwotaPremia; public static void main(String[] args) { if (kwotaPensja < 7000 && zwLekarskie == 0) { kwotaPremia = 1500; System.out.println("Przyznano premię w wysokości: " + kwotaPremia); } else { kwotaPremia = 0; System.out.println("Premia nie została przyznana z powodu zbyt wysokiej pensji i/lub zwolnienia lekarskiego"); } } }

Instrukcje zamieszczone w powyższych przykładach są mocno abstrakcyjne. Niemniej jednak opisują jedną z elementarnych sytuacji, w której tester powinien zachować czujność i otwarcie powiedzieć o swoich obawach. Zwykle testerzy nie posiadają kompetencji regularnego programisty, zatem często podnoszone przez nich kwestie związane z optymalizacją kodu mogą nie mieć odzwierciedlenia w realnym zagrożeniu dla aplikacji. Jednakże nie powinno mieć to negatywnego wpływu na pracę np. poprzez zaniechanie zgłoszenia swoich podejrzeń w obawie przed „kompromitacją”. Koszt obsługi kilku czy kilkunastu nie do końca zasadnych zgłoszeń jest niewspółmiernie niski w stosunku do kosztów obsługi pojedynczego incydentu w środowisku produkcyjnym. Testerowi nie wolno „przymykać oka” w obawie przed konfrontacją z programistą, projektantem lub projektantem i analitykiem. Jawność kodu w procesie testowania aplikacji stwarza warunki do weryfikacji, czy programista stosuje się do tak zwanych dobrych zasad programowania lub wręcz czy przestrzega ogólnie narzuconych wytycznych. Pożądaną praktyką w programowaniu jest oddzielenie logiki systemu od warstwy prezentacji. Testy wykonywane metodą białej skrzynki umożliwiają ocenę, czy taka zasada jest respektowana. Równie istotne jest przestrzeganie przyjętej konwencji nazewnictwa np. zmiennych, stałych, klas,

Rozdział 1.  Ogólna teoria testowania

17

pakietów etc. Implementacja kodu według wspólnej konwencji jest niezmiernie istotna z punktu widzenia zarządzania projektem i/lub całym przedsiębiorstwem. Kod źródłowy nie jest trwale związany z jego autorem. Unifikacja ułatwia podjęcie pracy innemu programiście, który uprzednio nie uczestniczył w danej implementacji, a jednocześnie ma doświadczenie w innych projektach tej samej korporacji. Programista, który powołał określoną funkcjonalność, może zmienić pracę, skorzystać z urlopu wypoczynkowego, zachorować lub ulec innemu zdarzeniu losowemu, ale kod przez niego napisany nadal pozostaje w repozytorium i w razie konieczności musi być rozwijany i serwisowany. Standaryzacja pracy jest wymogiem koniecznym w celu zachowania ciągłości obsługi kodu.

1.2. Miara jakości oprogramowania Oprogramowanie poddawane jest testowaniu w celu zminimalizowania ryzyka wystąpienia błędów w środowisku produkcyjnym. Jakość to termin opisujący istotne cechy, które wyróżniają produkt w sposób szczególny — zarówno negatywny, jak i pozytywny. Określenie jakości produktu w oparciu o relacje opisowe nie przynosi spodziewanych rezultatów, gdyż treść przekazu zależna jest od podmiotu sporządzającego (poznawczego). Subiektywne podejście do oceny wartości oprogramowania stanowi przeszkodę w uzyskaniu rzeczywistej oceny stanu produktu. Próby podzielenia się wnioskami z przeprowadzonej analizy mogą okazać się kłopotliwe ze względu na dużą swobodę interpretacji przekazywanych treści. Naturalnym porządkiem rzeczy wydaje się ujęcie poziomu jakości w syntetycznych ramach. Fakty liczbowe stanowią wartościową bazę do przygotowania raportu informującego o bieżącej jakości produktu. Podstawowym parametrem oceny jest kryterium ilościowe opisujące proporcję błędów wykrytych w fazie produkcji oprogramowania do liczby zgłoszeń ujawnionych w środowisku zewnętrznym (po zakończeniu procesu produkcji w fazie odbiorczej). Za klienta ostatecznego należy uważać docelowego użytkownika systemu. Zastrzec należy, że odbiorcą oprogramowania nie musi być zupełnie niezależny klient zewnętrzny. Software produkowany jest również na potrzeby własne podmiotu lub dla jednostek o powiązanym kapitale. W tym miejscu należy poszerzyć wiedzę zarówno w tematyce klienta zewnętrznego, jak i wewnętrznego w oparciu o niezależne i dedykowane publikacje. Ów wskaźnik (ilościowy) nie wystarcza do odwzorowania rzeczywistego stanu aplikacji. Niemniej jednak uwzględniany jest w procesie opiniowania i powinien mieć ustaloną wartość graniczną, po której przekroczeniu inicjowany jest sygnał ostrzegawczy. Powyższy wskaźnik wymaga szczególnej uwagi w momencie interpretacji. Nie rozróżnia on rangi błędów, jedynie konfrontuje ze sobą ich liczbę. Błędy krytyczne traktowane są na równi z usterkami oraz z innymi mniej istotnymi problemami. Rysunek 1.3 prezentuje porównanie ilościowe błędów wykrytych w środowisku testowym producenta w stosunku do błędów ujawnionych u klienta. Kryterium ilościowe powinno być wspierane parametrem określającym zbiorczy ciężar merytoryczny błędów. Waga 100 dużych błędów nie odpowiada takiej samej ilości zgłoszonych usterek, np. literówek, drobnych niedociągnięć graficznych itp.

18

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.3. Porównanie ilościowe zgłoszeń własnych oraz zewnętrznych

Trzecim parametrem są informacje zwrotne płynące bezpośrednio od użytkowników systemu. Opinie klientów należy zestawić z zebraną statystyką pozostałych parametrów oprogramowania. Zabieg ten pozwoli na uwiarygodnienie trafności pomiaru (pokrycie liczb zgodne z powszechnym mniemaniem) lub zasygnalizuje konieczność zmodyfikowania wskaźników. W sytuacji kiedy przyjęte kryteria rozmijają się z opinią klienta, należy zmodyfikować zasady pomiaru, np. zmieniając progi lub dodając nowe wskaźniki. Niepokojące wartości pewnych parametrów nie muszą oznaczać poważnych problemów z jakością produktu. Produkcja oprogramowania jest procesem skomplikowanym i zależnym od wielu czynników. Niestandardowe okoliczności mogą w mniejszym lub większym zakresie uzasadniać odstępstwo od reguł. Elementem niezmiernie istotnym jest określenie (wyliczenie z danych popartych analizą) współczynnika powagi (skutków) błędów do poziomu skomplikowania sytuacji ich wystąpienia. Nadmieniłem na uprzednich kartach książki, że bywają błędy o dużym ciężarze w skutkach, a trywialnej sytuacji ich występowania. Duży odsetek błędów potencjalnie błahych do wykrycia, które ujawnione zostały przez klienta, nie świadczy dobrze o jakości oprogramowania. Nieco inaczej należy interpretować błędy, które znajdują podbudowę w wyrafinowanych i trudno osiągalnych scenariuszach dla testera u wytwórcy. Niepodważalnie istnieją podmioty, które dysponują wystarczającą siłą i środkami, aby realizować produkcję oprogramowania na własne potrzeby we własnym zakresie. Wydatnym przykładem mogą się okazać korporacje wytwarzające urządzenia elektroniczne, tj. tablety, smartfony, telewizory, laptopy itp. Niemniej jednak z punktu widzenia oceny jakości oprogramowania niewiele to zmienia. Przymykając oko na niuanse i detale związane z funkcjonowaniem poszczególnych podmiotów, można uznać, że powyżej przytoczony ogólny model oceny jakości wytwarzanego oprogramowania nosi cechy uniwersalności. Zaiste realizacja projektu na potrzeby grupy kapitałowej lub wręcz w ramach tego samego podmiotu może z natury wydawać się łatwiejsza. Niemniej jednak nie jest to tożsame z dewaluacją ryzyka dla obu ze stron, tj. klienta

Rozdział 1.  Ogólna teoria testowania

19

i zleceniobiorcy. Wprawdzie projekt może przepływać przez jednostki związane kapitałowo, jednak zawsze występuje ktoś w roli zleceniodawcy i realizatora. Wszakże należy pamiętać, że nawet projekty podejmowane wewnętrznie obciążone są kosztami operacyjnymi, które prędzej czy później muszą zostać rozliczone. Załóżmy, że produkujemy oprogramowanie do nowej linii telewizorów. Załamanie terminu zakończenia prac wpłynie realnie na harmonogram wprowadzenia do sprzedaży gotowego produktu (telewizory). Myślenie typu „nic się nie stanie, jakoś się dogadamy wewnętrznie” jest zgubne i błędne. Dlatego system nadzoru nad jakością oraz efektami pracy jest równie istotny jak w realizacjach kontraktu dla klienta zupełnie niepowiązanego z podmiotem wytwarzającym. Notabene, jak już wcześniej wspominałem, całościowe parametry jakości składają się z wielu pomniejszych, które z kolei mogą odnosić się do pracy poszczególnych osób lub zespołów. Wykrycie i zidentyfikowanie ewentualnych źródeł pogorszenia jakości ma kluczowe znaczenie dla udoskonalenia procesu wytwórczego oraz dojrzałości korporacji jako całości. Zgodnie z powiedzeniem co kraj to obyczaj różne firmy mogą odmiennie podchodzić do problematyki jakości. Pewne jest jedno: bez nadzoru nad jakością i harmonogramem żaden projekt nie ma prawa zakończyć się sukcesem. Miałem okazję rozmawiać z programistami realizującymi projekty dla bardzo dużej korporacji. Programowali oni w parach. Dwóch programistów przy jednym komputerze. Jeden „pisał”, drugi weryfikował kod, uczył się i zgłaszał wszelkie wątpliwości merytoryczne. Ów minizespół cyklicznie zamieniał się rolami, tzn. obserwator z koderem. Drugą arcyciekawą praktyką było odsyłanie wytworzonego kodu przed jego formalną publikacją do programisty o wyższych kompetencjach, który oceniał napisane instrukcje, wnosił uwagi i poprawki, a następnie odsyłał materiał do autora. Takie rozwiązanie wnosi wiele do procesu edukacji i kształtowania pracy programistów. Z pewnością w dłuższej perspektywie ostateczna jakość kodu zostanie znacznie zwiększona. Niewątpliwie powyższe dwie metody mogą istotnie przyczynić się do efektywności pracy i niezawodności wytwarzanego kodu. W takich okolicznościach można by się spodziewać, że testerzy funkcjonalni nie będą często borykać się z błędami oczywistymi i krytycznymi. Cóż, programista niejako „poprawiający kod po kolegach” jest pierwszym testerem i to on ma świadomość ilości, wagi i typów błędów popełnianych przez zespół programistów. W tym miejscu również można zastosować markery jakości. Metodologii (praktycznych) badania i zapewnienia jakości oprogramowania jest tak wiele, jak wiele jest podmiotów je wytwarzających. Pewne ogólne przesłanki znajdą zastosowanie w większości podmiotów. Niemniej jednak warto pamiętać, że wypadkowa polityka może znacznie się różnić pomiędzy różnymi firmami. Teoria teorią, a praktyka praktyką. Obecne czasy wykluczają opieranie się podczas rekrutacji pracowników tylko i wyłącznie na dokumentach potwierdzających kwalifikacje. Elementem wiodącym są konkretne umiejętności. Rzecz jasna pożądane dla pracodawcy. Zatem nie rekomenduje się wchłaniania niezliczonej ilości informacji, które zwykle po kilkunastu miesiącach mogą okazać się nieaktualne. Istotne jest posiadanie wiedzy elementarnej, lecz aktualnej, która pozwoli na szybkie dostosowanie się do specyfiki pacy i wymogów pracodawcy. W ICT kluczem do sukcesu jest szybka asymilacja, aktualna i wiarygodna wiedza, która stanowi bazę do dalszego samorozwoju. Jeżeli nie wspomniałem jeszcze o aktualnym trendzie w edukacji, to zrobię to teraz.

20

Testowanie oprogramowania. Podręcznik dla początkujących

System szkolnictwa nie jest w stanie „wlać” całości niezbędnej wiedzy „do głowy studenta”. Co więcej, nie jest w stanie jej zweryfikować i wyegzekwować. Między innymi z tego powodu większy nacisk kładzie się na nauczenie jednostki, jak należy się samodzielnie uczyć i rozwijać, niż na zapamiętywanie tysięcy wersów z książek, których treści przestaną być aktualne, zanim słuchacz ukończy szkołę (przynajmniej w informatyce). Taką też rolę założyłem dla niniejszej publikacji. Ma ona otworzyć drogę Czytelnikowi do dalszego samorozwoju w roli testera oprogramowania. Jak to się ma do oceny jakości? Przyjęty model weryfikacji efektów pracy może na pierwszy rzut oka okazać się krzywdzący, niezrozumiały, wadliwy etc. Z punktu widzenia jednostki może pojawić się nawet poczucie zagrożenia i istnienia narzędzia represji. Sęk w tym, aby znaleźć w owym systemie elementy, które mogą potencjalnie chronić nasze interesy, np. raportować problemy dyskusyjne, które później może poruszyć klient w fazie odbioru oprogramowania. Jak to się mówi: szklanka jest do połowy pełna lub do połowy pusta. Kluczem jest zdolność do przystosowania się, czyli umiejętność posłużenia się nauką „jak się samodzielnie uczyć”. Powyżej pochyliliśmy się nad podstawowymi markerami, za pomocą których można opisywać ostateczną jakość oprogramowania. Pozostaje jeszcze kwestia interpretacji wyniku oraz metody zbierania danych. Satysfakcję użytkownika końcowego można mierzyć poprzez ankiety. Niezawodność można określać jako czas bezproblemowej pracy weryfikowanego modułu. Efektywność pracy można również mierzyć poprzez zestawienie ilości błędów w stosunku do napisanych linii kodu. Termin jakość oprogramowania można spróbować zamknąć w ramach kilku jego atrybutów:  funkcjonalność (poziom zaspokojenia potrzeb klienta),  niezawodność,  przenośność i łatwość instalacji,  łatwość utrzymania,  wydajność,  ergonomia i łatwość obsługi (ang. usability),  użyteczność dokumentacji,  podatność na rozwój.

Powyżej opisywane markery (punkty kontroli oprogramowania) powinny odwzorowywać rzeczywisty stan aplikacji, tzn. stanowić liczbowy trzon dla opisowych atrybutów. Duża liczba nierozwiązanych problemów wydajnościowych, rzecz jasna, obniża atrybut wydajności. Analogicznie często raportowane błędy dla tego samego obszaru aplikacji świadczą negatywnie o niezawodności oprogramowania. Zwykle pomiędzy producentem a klientem oprogramowania zostaje zawiązana umowa, która określa parametry krytyczne oraz pożądane cechy, jakie musi posiadać system. Ostateczna polityka zarządzania jakością oraz parametry progowe, które zadowalają klienta, ustalane są w formie negocjacji. Dlatego można się spodziewać różnic w po-

Rozdział 1.  Ogólna teoria testowania

21

dejściu do aspektów jakości przy realizacji kolejnych i niezależnych kontraktów. Jednemu klientowi może znacznie bardziej zależeć na niezawodności, a drugi dopuszcza krótkotrwałe przerwy w pracy na rzecz polepszenia innych parametrów, np. atrakcyjności GUI. Wskaźniki odnoszące się do produktu mogą i powinny być wykorzystane do określenia:  ogólnego stanu produktu,  efektywności działu kontroli jakości,  skuteczności testera,  sprawności programisty,  jakości zarządzania projektem (w tym analizy wymagań biznesowych).

Rozważmy hipotetyczny projekt, który przewiduje 22 dni testów. Liczba osób zaangażowanych nie jest istotna. Wskaźnik ilościowy został ustalony w proporcji 5:1. Oznacza to, że producent dopuszcza maksymalną liczbę błędów zgłoszonych w fazie odbiorczej (testy u klienta) na poziomie 20% wszystkich zarejestrowanych błędów w trakcie testów jakościowych (u producenta). Projekt zakończył się rezultatem 119:21, co daje wynik 15% (rysunki 1.3 i 1.4). Zakładając, że w puli 21 błędów zgłoszonych przez klienta nie ma problemów krytycznych i/lub o znaczącym ciężarze merytorycznym, można przyjąć, że projekt zakończył się sukcesem (jeżeli współczynnik nie przekracza przyjętego założenia). Rysunek 1.4. Procentowy rozkład zgłoszeń wewnętrznych i zewnętrznych

Monitorowanie jakości w trakcie trwania projektu testowego jest kluczowym elementem pozwalającym na bieżącą ocenę sytuacji. Skutecznym narzędziem może okazać się wykres obrazujący ilość błędów zgłaszanych w poszczególnych dniach testowania. Rysunek 1.5 przedstawia wykres projektu testowego trwającego 22 dni robocze (mianem projektu testowego określa się jeden z etapów procesu wytwarzania oprogramowania, w którym weryfikowana jest jakość dostarczonego produktu). Pierwsze sześć dni wskazuje na tendencję rosnącą. Oznacza to, że testerzy wykazują się aktywnością, a aplikacja i środowisko nie blokują postępu prac. Pomiędzy czwartym a jedenastym dniem wpłynęło najwięcej błędów. Dwunasty dzień rozpoczyna trend malejący,

22

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.5. Przykładowy przebieg rejestracji błędów w trakcie trwania projektu testowego

który w kolejnych dniach zbliża się ku zerowej wartości. Mniej więcej w okolicy siedemnastego dnia można wysnuć wniosek, że testerzy nie ujawniają błędów przy standardowej obsłudze systemu i przeszli do fazy bardziej złożonych oraz wyrafinowanych testów („szukają błędów”). W kolejnym dniu został zarejestrowany pojedynczy błąd, jednak mimo to sytuacja nadal jest stabilna. Kolejne dni testów potwierdzają, że jakość aplikacji jest na dobrym poziomie. Rysunek 1.6 odnosi się do projektu testowego, który z dużym prawdopodobieństwem nie zakończy się w założonym terminie. Tendencja w drugiej połowie harmonogramu wskazuje, że aplikacja w dalszym ciągu zawiera błędy, które są ujawniane w kolejnych dniach testów na mniej więcej równym poziomie. Nie widać trwałego spadku zgłoszeń. Zatem z dużym prawdopodobieństwem w nadmiarowym czasie testów mogą wystąpić dodatkowe problemy. Zakończenie prac po upływie założonych 22 dni roboczych jest wielce ryzykowne. Interpretacja wykresu sugeruje kontynuowanie testów w celu poprawienia jakości produktu. Wiąże się to z przekroczeniem planowanego terminu zamknięcia projektu testowego. Analiza wykresu wskazuje jeszcze jeden problem, który miał miejsce w początkowej fazie testów. Pierwsze trzy dni nie przyniosły dostatecznych efektów w postaci liczby zgłoszonych błędów. Ów fakt może sugerować, że na etapie rozpoczęcia testów wystąpiły problemy z uruchomieniem aplikacji (środowisko) lub zawierała ona błędy krytyczne, które zablokowały postęp prac. Sformułowane podejrzenie nie musi znaleźć odzwierciedlenia w rzeczywistości, jednakże powinno być zweryfikowane. Wykres z rysunku 1.7 przedstawia sytuację, która wymaga szczególnej uwagi i szybkiej reakcji. Dzień czwarty i piąty nie przyniosły żadnych nowych zgłoszeń, mimo że spodziewany był trend rosnący. Oznaczać to może, że zespół kontroli jakości utracił możliwość kontynuowania testów ze względu na błędy blokujące (brak możliwości przejścia do dalszych obszarów aplikacji) lub kłopoty środowiska pracy. Gwałtowny spadek charakterystyki, a następnie równie szybki wzrost stanowi sytuację niepokojącą.

Rozdział 1.  Ogólna teoria testowania

23

Rysunek 1.6. Przykładowy przebieg rejestracji błędów w trakcie trwania projektu testowego

Rysunek 1.7. Przykładowy przebieg rejestracji błędów w trakcie trwania projektu testowego

W teorii testowanie oprogramowania jest procesem nieskończonym. Aspekt praktyczny wspierany przez względy ekonomiczne nakazuje wyznaczyć moment, w którym testy mogą być zaprzestane. Zakończenie testów możliwe jest po spełnieniu przez system wszystkich punktów kontrolnych. Oprogramowanie powinno być oficjalnie wydane w przekonaniu, że spełnia pokładane w nim oczekiwania oraz będzie funkcjonowało w sposób satysfakcjonujący w środowisku produkcyjnym.

1.3. Środowisko testowe i produkcyjne Środowisko testów to ogół elementów infrastruktury technicznej oraz modułów oprogramowania, które stanowią podbudowę do uruchomienia właściwej aplikacji poddawanej testowaniu. Zdolność do wykonania testów w wymaganym zakresie bezpośrednio zależy od stanu i możliwości zaplecza. Środowisko testowe powinno być w optymalny sposób zbliżone do produkcyjnych warunków funkcjonowania systemu (aplikacji). Odwzorowanie środowiska w relacji 1:1 jest w praktyce bardzo trudne, kosztowne i na ogół niepotrzebne. Kluczową kwestią dla procesu testowania jest możliwość uruchomienia wszystkich funkcjonalności (opcji) systemu.

24

Testowanie oprogramowania. Podręcznik dla początkujących

Środowisko testowe powinno być odizolowane i niezależne od wpływu programistów, którzy często nie mogą oprzeć się pokusie pracy w nim. Dostęp do tego środowiska jest bardzo atrakcyjny dla programistów ze względu na to, że ich własne zasoby są zwykle dużo bardziej ubogie w dane oraz posiadają luki funkcjonalne. Ponadto analiza i naprawa błędu na „żywym organizmie” w realnych okolicznościach jest dużo łatwiejsza. Dobro wspólne wymaga, aby wspierać pracę programistów, jednak nie oznacza to bezwarunkowej zgody na penetrację środowiska testów. Pożądane cechy środowiska testów:  stabilność i ciągłość pracy,  zaufanie do gromadzonych danych,  zaufanie do stanu kodu,  niezależność od środowiska deweloperskiego,  optymalnie pełne pod względem uruchamiania funkcjonalności.

Wszelkie niedomagania środowiska potencjalnie mogą wpłynąć na obniżenie jakości produktu końcowego. Zespół kontroli jakości w celu przeprowadzenia efektywnych testów powinien mieć zagwarantowany stosowny komfort pracy. Podstawowym wymogiem jest utrzymanie ciągłości pracy środowiska oraz jego stabilności. Nierzadko przygotowanie testu trwa kilkakrotnie dłużej niż jego wykonanie. Środowisko musi pracować w sposób przewidywalny, bez nieplanowanych przerw i anomalii. Wykonując określone zadania, testerzy równolegle gromadzą dane z zamierzeniem ich powtórnego wykorzystania w przyszłości. Niespodziewane zniszczenie lub utrata tak wypracowanego zasobu może w znacznym stopniu cofnąć poziom zaawansowania projektu testowego lub odwlec w czasie rozpoczęcie nowego przedsięwzięcia. Tester musi mieć pewność, że przygotowane środowisko do testów zastanie w dniu następnym w stanie nienaruszonym. Każda ingerencja programisty w środowisko testowe niesie ze sobą ryzyko potraktowania wadliwego działania systemu będącego wynikiem chwilowej modyfikacji kodu jako błąd. Błąd, który w normalnych warunkach nie istnieje. Dodatkowym utrudnieniem może okazać się konieczność wyłączenia (zablokowania) części środowiska z zadań testowych na potrzeby deweloperskie. Istotne jest wypracowanie poczucia odpowiedzialności koderów za stan środowiska testów, tj. należy wymusić nawyk „sprzątania po sobie”, czyli przywracania kodu do stanu uprzedniego. Pochodną powyższego zagadnienia jest konieczność separacji środowisk testowych i deweloperskich. W momencie przejęcia projektu przez dział testów sekcja programistów zwykle rozpoczyna inne zadania, które mogą wchodzić w konflikt z wcześniej przygotowanym materiałem (brak przezroczystości). Przemawia to za rozdzieleniem tych dwóch środowisk. Testy funkcjonalne wymagają, aby każda z opcji była dostępna (działała). Sytuację komplikują obszary, które są zależne od systemów zewnętrznych (kooperują z nimi), do których producent oprogramowania nie ma dostępu. W takich okolicznościach można zastosować symulatory aplikacji obcych w celu zaślepienia obszaru odpowiadającego za komunikację pomiędzy nimi. Eliminując luki o powyższym charakterze, zwiększa się obszar pokrycia aplikacji testami.

Rozdział 1.  Ogólna teoria testowania

25

Termin „środowisko testowe” nie powinien być kojarzony jedynie z deweloperem, gdyż klienci zamawiający oprogramowanie również dysponują takim środowiskiem. Służy ono do wykonywania testów odbiorczych zamawianego oprogramowania, jak również do replikacji błędów zauważonych w środowisku produkcyjnym. Cechą dominującą tego środowiska jest potencjał, jakim dysponuje ono w przedmiocie odwzorowania warunków produkcyjnych (docelowego środowiska pracy testowanej aplikacji). Kluczowy wpływ na to ma fakt posiłkowania się kopią danych pochodzących z realnie funkcjonującego systemu oraz możliwość integracji pozostałych elementów systemu, które były produkowane przez niezależne firmy. Nie mniej istotne jest obsługiwanie systemu zgodnie z obowiązującymi standardami i procedurami. Zespół testów odbiorczych znacznie lepiej rozumie potrzeby i praktyki stosowane przez użytkowników docelowych. Wszystkie powyższe elementy przyczyniają się do utrwalenia przewagi środowiska testowego po stronie klienta nad analogicznym środowiskiem u producenta. Oprogramowanie „pudełkowe” to znaczy takie, w przypadku którego to producent decyduje o jego funkcjonalności i momencie oficjalnego wydania do szerokiej sprzedaży, nie posiada środowiska testowego po stronie klienta. Podmioty nabywające oprogramowanie zawierzają producentowi, sądząc, że dostarczony produkt jest odpowiedniej jakości, i od razu wdrażają system jako produkcyjny. Klient nie wykonuje testów odbiorczych w aspekcie procesu produkcji oprogramowania. Wielokrotnie termin „system testowy” odnosi się do aplikacji, które zostały udostępnione do nauki i szkolenia użytkowników. Stosowanie owej definicji w powyższym kontekście nie znajduje uzasadnienia w terminologii kontroli jakości. Oprogramowanie dedykowane dla urządzeń, np. tablety, mapy samochodowe, telefony etc., powinno również podlegać weryfikacji na fizycznych urządzeniach docelowych. Oczywiście trudno sobie wyobrazić, aby producent aplikacji posiadał pełne spektrum wspieranych urządzeń. Z tego powodu mocno eksploatowane są wszelkie emulatory. Niemniej jednak obsługa programu bezpośrednio na wybranym urządzeniu niesie niezmiernie dużą wartość merytoryczną. W ten sposób unikniemy „smakowania cukierka przez papierek”. Tylko w taki sposób uda nam się poczuć realnie „ducha” aplikacji. Środowisko produkcyjne to ogół komponentów technicznych oraz programowych, które bezpośrednio uczestniczą w obsłudze procesów biznesowych przy pełnej odpowiedzialności za jakość obsługi. Zatem wszelkie modyfikacje głównej infrastruktury powinny być poprzedzone szczegółowymi testami zmienianego elementu. Rozbieżności, jakie mogą wystąpić pomiędzy środowiskiem produkcyjnym a testowym:  różnica w infrastrukturze technicznej (serwery, sieć LAN/WAN),  rozbieżność w architekturze (wielowarstwowość),  niezgodność wersji systemu operacyjnego oraz pozostałego oprogramowania

(np. IIS, Oracle, JBoss, Apache etc.),  dysproporcja zarówno w ilości danych, jak również ich różnorodności.

26

Testowanie oprogramowania. Podręcznik dla początkujących

Odrębnym zagadnieniem wymagającym szczególnej uwagi są poprawki wykonywane bezpośrednio w środowisku produkcyjnym. Stawia to zespół testerów w niekomfortowej sytuacji, gdyż 100-procentowe potwierdzenie skuteczności zmiany może okazać się niemożliwe. Pełny obraz efektów funkcjonowania poprawki z reguły można uzyskać dopiero po implementacji jej w środowisku produkcyjnym. W wielu przypadkach problematycznym obszarem okazują się skrypty SQL lub pliki zmieniające konfigurację serwerów. Z uwagi na rozbieżności w infrastrukturze technicznej lub różny stan danych owe modyfikacje mogą ujawnić problemy o charakterze:  Zmiana nie może zostać w ogóle przetestowana; brak możliwości implementacji

w środowisku testowym, np. specyficzna konfiguracja serwera aplikacji.  Zmiana może być wgrana, jednak kontroler jakości nie wychwyci różnicy

w działaniu systemu; problemy optymalizacji i wydajności.  Zmiana może być przetestowana z uzyskaniem zauważalnego wyniku, lecz

wymaga wcześniejszego spreparowania środowiska: modyfikacji danych z poziomu bazy, zmiany konfiguracji środowiska, przygotowania skryptów wspomagających etc. Bardzo trudno jest wychwycić zauważalne efekty zmian optymalizacyjnych z uwagi na ograniczony wolumen danych lub niewspółmierną możliwość obciążenia systemu. Warunkiem akceptacji takich modyfikacji jest utrzymanie dotychczasowego standardu pracy, czyli niepogorszenie parametrów systemu (wydajność i funkcjonalność) w środowisku testowym. Spełnienie powyższego kryterium daje podstawy do oczekiwania, że poprawka odegra swoją rolę w środowisku produkcyjnym. Szczególnie interesujące są aspekty związane z naprawianiem danych bezpośrednio na żywym organizmie (środowisku produkcyjnym). W zależności od komplikacji problemu poziom trudności plików SQL może być różny. Najtrudniej wykonać testy skryptów o złożonym algorytmie, który musi odpowiednio „obliczyć” dane, a następnie zmienić je sekwencyjnie w wielu tabelach. Przygotowanie środowiska w sposób pozwalający na przetestowanie skryptu naprawczego może okazać się bardzo pracochłonne lub niemożliwe. Ograniczenia wynikające z różnic pomiędzy środowiskiem testowym a produkcyjnym nie czynią testera zupełnie bezsilnym wobec obowiązku weryfikacji poprawki. Kontroler jakości może przeprowadzić analizę kodu oraz porównać aktualną i uprzednią wersję pliku itp. Dobrą praktyką jest przyjrzenie się zagadnieniu i potwierdzenie, czy obrana droga eliminacji problemu ma szanse okazać się właściwą. Ową analizę należy oprzeć na własnym doświadczeniu, znajomości systemu oraz kompetencjach technicznych. Wszelkie wątpliwości należy rozstrzygnąć z autorem poprawki, gdyż czasami spojrzenie z innej perspektywy ujawni niedociągnięcia lub zainspiruje programistę. Niezależnie od możliwości zgłębienia testów zawsze należy starać się przeprowadzić kontrolę przez dwie instancje (dwie ręce). Wielowątkowy rozwój systemu determinuje konieczność mnożenia pewnych elementów lub nawet całego środowiska. Związane jest to z relacjami, jakie zachodzą pomiędzy poszczególnymi obszarami systemu. Modyfikacje mogą być testowane niezależnie (są przezroczyste) lub wymagają innych modułów zależnych. Duże systemy informatyczne

Rozdział 1.  Ogólna teoria testowania

27

rozwijane są równolegle na różnych płaszczyznach, co przekłada się na konieczność dostosowania zasobów do bieżących potrzeb (powielanie i izolowanie środowisk). Administracja środowiskiem testów Profesjonalny zespół testów powinien dysponować zasobami, które stanowią podbudowę niezależnego środowiska testów. Wspomniana autonomia wnosi nowe możliwości, podnosi elastyczność i efektywność testów, a zarazem stawia wyzwania. Środowisko testów jest bardzo wymagające pod względem administracji. Jest także bardzo czułe na wszelkiego rodzaju zmiany konfiguracji ze względu na zmniejszoną hermetyczność. Systemy produkcyjne zarządzane są w oparciu o bardzo restrykcyjne wytyczne. Środowisko testów z natury musi być bardziej otwarte i podatne na zmiany w konfiguracji, modyfikacje kodu, zmiany danych etc. Zaspokojenie potrzeb zespołu testerów wymaga zwiększenia uprawnień do pewnych obszarów środowiska, wskutek czego zmniejsza się promień twardego kręgu bezpieczeństwa i równocześnie podnosi ryzyko destabilizacji pracy. Skuteczną i efektywną praktyką jest włączenie w szeregi zespołu testów osoby, która obejmie obowiązki administratora. Idealnym rozwiązaniem jest powierzenie zadań administracyjnych jednemu z członków grupy testerów, gdyż taka osoba doskonale rozumie potrzeby zespołu. Niemniej jednak realizacja owego scenariusza może być kłopotliwa w krótkiej perspektywie czasu ze względu na możliwe luki w kompetencjach. Generalnie administrator środowiska powinien dążyć do jak najlepszego zrozumienia istoty testów, specyfiki systemu i potrzeb z tego wynikających. Środowisko testów nie jest monolitem. Nieustannie się zmienia. Z każdym nowym projektem przechodzi do stanu bardziej złożonego. Wymaga to wzmożonej czujności administratora, ale także odpowiedzialności każdego z kontrolerów jakości w ramach posiadanych uprawnień do systemu. Od administratora środowiska testów powinno oczekiwać się:  zagwarantowania ciągłości pracy,  stabilności środowiska,  wsparcia technologicznego (w tym konsultacji),  rozwijania zasobów w odpowiedzi na potrzeby.

Administrator jest zobowiązany wydać opinię o możliwościach, wolnych zasobach, zagrożeniach i krokach przygotowawczych na etapie planowania testów. Pewna grupa testów może znacząco obciążyć zasoby (np. hurtowy import danych oraz ich przetworzenie), czego efektem będzie uniemożliwienie kontynuowania prac przez pozostałych członków zespołu. W takich okolicznościach należy rozważyć izolację testów, tj. przenieść je w odrębne środowisko. Planując testy, należy rozważyć w aspekcie środowiska testów:  konieczność separacji projektu lub jego fragmentu od podstawowego środowiska

testów, np. testów wydajnościowych lub funkcjonalnych z dużą liczbą danych;  możliwość uruchomienia dodatkowych serwerów aplikacji lub zupełnie nowych

komponentów, np. serwera SMTP, WWW itp.;

28

Testowanie oprogramowania. Podręcznik dla początkujących  ryzyko utraty lub „zepsucia” danych np. w przypadku testu jednorazowych

skryptów bazodanowych (skrypt można wykonać tylko raz, w celu ponownego testu należy przywrócić pierwotny stan bazy);  możliwość chwilowego włączenia do infrastruktury zasobów zdalnych, np.

zewnętrznego web service, który został „użyczony” na czas testów. Wachlarz potencjalnych potrzeb jest bardzo szeroki. Administrator środowiska to osoba najbardziej kompetentna do oceny stanu i możliwości zasobów oraz ewentualnego ryzyka. Niemniej jednak administrator musi być powiadomiony przez kierownika testów o potrzebach i planie wykorzystania zasobów, tak aby mógł wypracować optymalne rozwiązanie. Warto nadmienić, że administrator nie jest wyrocznią, a proponowane założenia powinny zabezpieczać interesy obu ze stron. Środowisko testów powinno być szczelnie chronione przed nieautoryzowanym dostępem szczególnie ze strony działu produkcji. Programiści mają nawyk debugowania i wykonywania poprawek w środowisku testowym. Niestety bardzo często pozostawiają „śmieci” w kodzie lub modyfikacje usuwające błąd. Administrator powinien ściśle reglamentować dostęp do środowiska testów dla osób wykraczających poza krąg kontrolerów jakości. Powodzenie projektu wymaga współpracy międzyzespołowej, dlatego sytuacje „goszczenia” programisty w środowisku testów są nieuniknione. Niemniej jednak każda taka ingerencja powinna być autoryzowana i rozpowszechniona wśród członków zespołu testów. Samowolne działania programistów mogą spowodować konieczność powtarzania testów oraz raportowanie „fałszywych” błędów. Tester musi mieć pewność, że obserwowane działanie systemu jest wynikiem wykonania rzeczywistego kodu, takiego, do którego ma zaufanie. Kontrola jakości powinna obejmować oficjalnie wydany materiał. Wszelkie modyfikacje programisty muszą mieć charakter tymczasowy i odbywać się pod kontrolą. Programista po zakończeniu prac powinien przywrócić środowisko do stanu pierwotnego oraz przygotować oficjalną poprawkę, którą zweryfikuje tester. Środowisko testów powinno funkcjonować w sposób przewidywalny, stabilny i ciągły. Wśród czynności administracyjnych są takie, które można rozpocząć dopiero w momencie zwolnienia środowiska przez zespół testów  np. rozszerzenia sprzętu, kopie bazy, restarty serwera itp. Oczywiście nie zawsze można zaplanować te czynności, gdyż mogą one wystąpić w reakcji na niespodziewane incydenty. Niemniej jednak od administratora należy wymagać, aby optymalizował czynności.

1.4. Replikacja błędów Elementarną zasadą procesu eliminacji wady aplikacji jest wykonanie replikacji błędu. Replikacja polega na odtworzeniu błędu w środowisku deweloperskim i/lub testowym. Celem owego zabiegu jest potwierdzenie wystąpienia problemu, zebranie danych o okolicznościach ujawnienia się błędu oraz zaplanowanie testów. Odwzorowanie sytuacji, w której występuje błąd, umożliwi ułożenie adekwatnych przypadków testowych oraz wytworzy idealne środowisko do potwierdzenia skuteczności wykonanej poprawy kodu.

Rozdział 1.  Ogólna teoria testowania

29

Replikacje błędu stosuje się w celu:  weryfikacji zasadności zgłoszenia, które spłynęło ze środowiska produkcyjnego

(klient zewnętrzny);  przygotowania środowiska testowego, weryfikacji skuteczności poprawki;  wsparcia prac programistycznych.

W ramach przyjętej odpowiedzialności system powinien być serwisowany, zatem wszelkie zgłoszenia odnoszące się do zachowania aplikacji muszą być rozpatrywane. Nie wszystkie przypadki można zakwalifikować jako błąd, gdyż wykraczają poza obszar zdefiniowanych wymagań (rozbudowa funkcjonalności) lub też określone funkcje systemu zostały użyte niezgodnie z wytycznymi (błędy procedur, pomyłki użytkowników, błędy danych etc.). Wstępna selekcja zgłoszeń ogranicza koszty związane z serwisowaniem systemu. Zdarza się, że czas wykonania testu stanowi niewielki ułamek całkowitego kosztu przygotowania środowiska. Systemy o wysoce skomplikowanej logice biznesowej i/lub architekturze mogą okazać się czasochłonne pod względem „dojścia” do momentu wystąpienia błędu. Skuteczność poprawki możliwa jest do zbadania w momencie zrozumienia istoty problemu. Testy poprawki na odpowiednio spreparowanym środowisku stwarzają szansę uzyskania miarodajnego rezultatu. Z uwagi na ograniczone kompetencje obsługi systemu poprzez interfejsy lub wąskie środowisko (niepełna architektura, mały wolumen danych, tymczasowa konfiguracja etc.) programista nie zawsze jest w stanie samodzielnie odtworzyć błąd. Tester w ramach swoich kompetencji stanowi wsparcie dla zespołu programistów w materii symulacji błędu i weryfikacji poprawki. Odtworzenie błędu niesie ze sobą dodatkową wartość w postaci wyznaczenia miejsca, od którego należy rozpocząć testy. Ów punkt stanowi marker, od którego testy powinny być poszerzane (testy regresywne po potwierdzeniu skuteczności poprawki). Groźnym zjawiskiem jest pomijanie etapu symulacji błędu. Przystąpienie do weryfikacji kodu bez uprzedniego zapoznania się z problemem może spowodować, że wykonany test będzie nietrafiony. Tester może wykonać przypadek testowy, który nie pokryje modyfikowanego obszaru lub nie ujmie specyficznych okoliczności występowania błędu. Próba replikacji błędu powinna być podejmowana za każdym razem. Niemniej jednak należy mieć świadomość, że nie we wszystkich przypadkach uda się taką sytuację odtworzyć. Fakt wystąpienia skutków błędnego zadziałania systemu wymusza podjęcie kroków zaradczych, mimo że próby odtworzenia sytuacji (środowisko klienta oraz producenta) kończą się niepowodzeniem. Niemożność reprodukcji błędu nie zwalnia z konieczności modyfikacji kodu. Modyfikacja może polegać na dodaniu dodatkowego logowania zdarzeń i ulepszeniu algorytmu w podejrzanym obszarze. Zabiegi prewencyjne wykonywane są w oparciu o analizę kodu, doświadczenie i przeczucie programisty. Zdarzenia o charakterze jednostkowym, incydentalne są bardzo trudne do odtworzenia, dlatego też wykonywane są modyfikacje asekuracyjne, które w przyszłości pozwolą dowiedzieć się więcej o istocie problemu.

30

Testowanie oprogramowania. Podręcznik dla początkujących

1.5. U mnie błąd nie występuje Środowisko deweloperskie nigdy nie powinno być używane jako podstawowe środowisko testów! Sentencja ta powinna być powtarzana jak mantra, a treść wymawianej formuły przestrzegana bezwzględnie. Separacja owych środowisk wymusza zachowanie czujności — podjęcie testów — już na etapie instalacji aplikacji (łącznie z instrukcją wgrania). Szczególnie irytującym zachowaniem ze strony zespołu deweloperskiego jest cofanie zgłoszonych błędów z komentarzem „u mnie to działa”. Jest to zachowanie wielce nieodpowiedzialne, wręcz patologiczne. Naznaczone jest brakiem współodpowiedzialności za projekt. Dopóki tester nie wyjaśni przyczyny wykrytego problemu, nie może w standardowych okolicznościach zgodzić się na oficjalne wydanie produktu. Wszelkie nieoczekiwane zachowania systemu odbiegające od założonego powinny być traktowane jako błąd, nawet jeżeli programista ma trudności w odtworzeniu analogicznej sytuacji u siebie. Gotowe komponenty są ekstrahowane ze środowiska deweloperskiego, a następnie implementowane w środowisku testowym. Kod aplikacji nie może zostać uznany za poprawny, jeżeli nie przejdzie pomyślnie wszystkich przewidzianych testów przynajmniej w jednym autonomicznym środowisku. Potencjalne przyczyny błędów/problemów, które nie znajdują odwzorowania w środowisku programistycznym:  kod programu nie został wprowadzony do repozytorium systemu kontroli

wersji, w konsekwencji nie został przekazany do działu testów;  testowana aplikacja zawiera błędy, które ujawniły się w specyficznych warunkach,

dotyczące np. ilości pamięci maszyny, konfiguracji serwera aplikacji etc.;  środowisko testów zostało błędnie przygotowane.

Nader często zdarzają się błędy powstałe na skutek braku aktualizacji repozytorium. Naturalnie tego typu sytuacje zostaną wychwycone w fazie testów jakościowych, o ile odbywają się one w niezależnym środowisku. W innym przypadku diametralnie zwiększa się ryzyko przekazania niepełnej (wadliwej) aplikacji klientowi. Pikanterii dodaje fakt, że emisja zostanie wykonana w przekonaniu o wysokiej jakości produktu. Wykryte błędy zostały poprawione w środowisku deweloperskim, ale kod nie został zaktualizowany w repozytorium, np. nie załączono pliku konfiguracyjnego serwera, pliku CSS, zawieruszyła się jakaś biblioteka, brakuje skryptu bazodanowego itp. Finalnie oficjalna wersja produktu będzie odbiegać od zatwierdzonego stanu na koniec testów. Specyfika pracy programisty nie wymaga od niego, aby posiadał on w pełni funkcjonalne środowisko, a z całą pewnością nie dysponuje on rzetelnym wolumenem danych. Posługując się kolokwializmem, można rzec „dużo nie trzeba”, aby program zadziałał w sposób nieoczekiwany. Nie muszą to być szczególnie specyficzne sytuacje. Czasami wystarczy uruchomić system w „innym” środowisku.

Rozdział 1.  Ogólna teoria testowania

31

Błędy wynikłe wskutek nieprawidłowości środowiska testów mogą być konsekwencją wadliwie sporządzonej instrukcji wdrożenia — notabene ona także podlega kontroli — lub niedokładnie przeprowadzonej konfiguracji. W przypadku kiedy instrukcja instalacji nie zaspokaja potrzeb, należy ją poprawić lub uzupełnić. Powyższe przesłanki umacniają pogląd, że testy powinny być wykonywane w niezależnym środowisku. Weryfikacja jakości oprogramowania w środowisku deweloperskim wprowadza znaczne ryzyko przeoczenia problemów, które z dużym prawdopodobieństwem ujawnią się w momencie wdrażania aplikacji lub w pierwszym etapie testów u klienta. W celu zminimalizowania powyższego zagrożenia konieczne jest „przejście” oprogramowania przez środowisko inne niż deweloperskie. Równie istotne co same testy jakościowe są warunki, w których pracuje aplikacja. Kod można uznać za poprawny, jeżeli spełnia wymagania biznesowe oraz jest uruchamialny w różnych środowiskach, które spełniają zdefiniowane wymagania. Więcej informacjo o aspektach przenośności kodu oraz testów instalacji zostanie przytoczonych na dalszych kartach niniejszej publikacji.

1.6. Symulatory aplikacji oraz generatory danych Informacja jako dobro niematerialne odgrywa coraz bardziej istotną rolę w życiu społeczno-ekonomicznym. Rosnące przystosowanie i gotowość do posługiwania się nowymi technologiami informatycznymi stymulują rozwój usług związanych z przesyłaniem, przetwarzaniem i gromadzeniem danych. Społeczeństwo informacyjne błyskawicznie asymiluje nowe technologie w codziennym życiu politycznym, społecznym i gospodarczym. W celu zaspokojenia owego apetytu tworzone są coraz bardziej wyrafinowane i skomplikowane systemy informatyczne. Pojedyncza operacja, jedna czynność inicjowana przez jednostkę społeczeństwa angażuje wiele różnych systemów informatycznych. Osoba zgłaszająca chęć skorzystania z określonej usługi aż do momentu otrzymania ostatecznego wyniku nie jest szczegółowo angażowana w kolejne kroki wykonania operacji. Proces, przechodząc przez wiele różnych instytucji, przetwarzany jest w oparciu o heterogeniczne obiekty informatyczne. Ochrona myśli technicznej stanowi podstawowy, a zarazem najważniejszy obowiązek każdej korporacji. Oprogramowanie — w ujęciu całościowym systemu — pisane na zlecenie jednego zamawiającego, ale produkowane przez kilka autonomicznych przedsiębiorstw, musi w pewnym momencie zostać zintegrowane. Intencją klienta jest uzyskanie określonego efektu końcowego (biznesu), który będzie realizowany przy współpracy wielu programów. Przy założeniu, że aplikacja zlecona korporacji A musi komunikować się z programem B i oba z tych modułów jeszcze nie istnieją, automatycznie pojawia się pytanie o możliwość wykonania pełnych testów. Dodatkowym utrudnieniem może okazać się brak woli podjęcia dostatecznej współpracy z firmą bądź co bądź konkurencyjną, która produkuje odrębny element. Formułując wymagania, klient powinien przedłożyć specyfikację metody komunikacji pomiędzy programami. Metoda wymiany danych w postaci plików jest mniej problematyczna, o ile jesteśmy w po-

32

Testowanie oprogramowania. Podręcznik dla początkujących

siadaniu szczegółowej dokumentacji. Spreparowanie plików niezbędnych do importu może okazać się trudne, pracochłonne i nużące, ale nie musi wymagać umiejętności stricte programistycznych. Znacznie trudniej wykonać testy obszarów, które wymagają komunikacji z innym systemem w czasie rzeczywistym, np. web service. Testy opcji wysyłającej żądanie do web service oraz modułów zależnych nie będą możliwe bez zaślepienia wtyczki. Zmaterializowanie się owej potrzeby wymusza stworzenie dodatkowego narzędzia, jakim jest symulator. Symulator to autonomiczny program, który imituje działanie właściwego programu lub urządzenia. Zwykle logika biznesowa takiego programu jest mocno ograniczona, choć powinien on zabezpieczyć w pełni aspekt techniczny, np. odbierać i wysyłać żądania web service zgodnie ze specyfikacją pliku WSDL. Symulator automatycznie staje się częścią środowiska testowego. Powinien on być modyfikowany i serwisowany równolegle z implementacją zmian w aplikacji, którą wspiera. Fakt istnienia zaślepek zmniejsza obszary martwe, w których testy ze względu na luki w środowisku nie mogłyby być wykonane. Zagęszczenie sita kontroli wpłynie dodatnio na końcową jakość produktu. Kompetencje symulatora mogą odnosić się do wielu różnych dziedzin. Może on podszywać się pod jakieś urządzenie, pojedynczy czujnik, web service etc. Zadaniem testera jest wykrycie na etapie planowania testów konieczności przygotowania zaślepki. Ujawnienie zapotrzebowania na takie rozwiązanie powinno być oficjalnie sformułowane i przekazane do osoby kompetentnej. Fakt identyfikacji problemu nie rozwiązuje go. Zespół kontroli jakości może mieć duże problemy z uzyskaniem zaślepki od działu programistycznego ze względu na napięty harmonogram lub dodatkowe koszty. Mogą się też pojawić trudności w wypracowaniu wspólnego stanowiska co do funkcjonalności samego symulatora. Z punktu widzenia testów zakres działania symulatora może okazać się niesatysfakcjonujący, choć dla działań programistycznych będzie on optymalny. Rozwijanie podstawowych kompetencji programistycznych wydaje się właściwym torem prowadzącym do uzyskania niezależności i poprawy skuteczności testów. Stworzenie zaślepki własnymi siłami (zespół testów) zagwarantuje elastyczność w trakcie serwisowania systemu. Niewątpliwie ten stan rzeczy nie ujdzie uwadze w zespole deweloperskim i rzuci cieplejsze światło na kompetencje testera, który do tego momentu mógł być postrzegany jako kompletny laik w obszarze programowania. Generatory danych Systemy informatyczne gromadzą i przetwarzają dane, które pochodzą z rzeczywistego środowiska funkcjonowania aplikacji. Dane magazynowane są w bazach danych. Cyfrowe zbiory danych są bazowym komponentem systemów informatycznych. Elementarną zasadą jest gromadzenie rzeczywistych, spójnych i poprawnych danych. Pojedyncza baza danych może być zasobem, który jest współdzielony przez wiele autonomicznych interfejsów (aplikacji). Nierzadko skomplikowane procesy biznesowe angażują wiele różnych podsystemów, które z kolei mogą sięgać po dane rezydujące w szeregu innych baz danych. Skomplikowana logika poparta znacznym rozproszeniem danych jest bardzo podatna na błędy związane z wadami danych. W życiu

Rozdział 1.  Ogólna teoria testowania

33

społecznym funkcjonuje wiele różnorodnych ciągów znaków, które służą za identyfikatory osób, rzeczy lub stanowią referencję do pewnych sytuacji lub obiektów wirtualnych. Zwykle każdy z owych identyfikatorów posiada ściśle zdefiniowaną strukturę, która oprócz założonych cech, jak np. unikalność, może nieść ze sobą konkretne informacje, np. płeć w numerze PESEL. Oficjalny standard danych pozwala na przetwarzanie zbiorów danych przez różne systemy, które uprzednio były przygotowane do ich obsługi według specyfikacji. W celu utrzymania standardu danych implementuje się mechanizmy walidacyjne. Mogą one być zaszyte w interfejsach użytkownika (GUI) lub w algorytmach importu danych z plików zewnętrznych. W systemach heterogenicznych powszechnie stosuje się język XML, na przykład tworząc opis usługi sieciowej WSDL (ang. Web Services Description Language). Algorytmy walidacyjne danych wejściowych mogą być skomplikowane. Ręczne przygotowanie rzeczywistych danych, np. numeru PESEL, REGON czy NIP, może okazać się kłopotliwe. Rzeczą naturalną wydaje się konieczność posłużenia się stosownym generatorem, który przygotuje poprawne dane. Wartością programów wytwarzających dane jest ich szybkość działania oraz możliwość produkcji hurtowej ilości wsadu na przykład do pliku importu. Internet obfituje w darmowe narzędzia generujące powszechne dane, tj. PESEL, REGON, NIP, nr dowodu osobistego, IBAN oraz wiele innych typów. Zdarzają się sytuacje, kiedy przypadek testowy wymaga użycia danych o ściśle zdefiniowanym standardzie, lecz niebędących w powszechnym (publicznym) użyciu. Uprzednio wywołana konieczność rozwijania elementarnych umiejętności programistycznych w tym miejscu ponownie znajduje uzasadnienie. Rzeczywiste dane zbliżają testy do naturalnej obsługi systemu, w konsekwencji czego możliwa jest obserwacja zachowania aplikacji w okolicznościach, do jakich została zaprojektowana. Realne dane wzmacniają skuteczność testów logiki oraz warstwy prezentacji systemu. Posługiwanie się naturalnie występującymi danymi zwiększa efektywność testowania raportów i interfejsów graficznych na przykład w okolicznościach:  podawania więcej niż jednego imienia i/lub dwuczłonowego nazwiska, np.

Maria Salomea Skłodowska-Curie (ur. 7 listopada 1867, zm. 4 lipca 1934);  podawania łączonych nazw miejscowości, np. Janów Lubelski;  podawania nazw własnych, które używają znaków specjalnych, np. Fundacja

„Jaś i Małgosia”;  podawania adresów korespondencyjnych używanych w Polsce i na świecie;  podawania telefonów w sieciach krajowych lub zagranicznych.

Środowisko testowe służy również jako zaplecze do prac dokumentalistów, na przykład podczas tworzenia instrukcji użytkownika. Zwykle owe dokumenty zawierają zrzuty ekranów z GUI. Rzeczywiste dane testowe uwiarygodniają jakość systemu oraz wzbudzają zaufanie do zawartości merytorycznej dokumentu. Tester ma prawo oraz obowiązek nieco zgrzeszyć. Kontrola jakości nie może opierać się jedynie na idealnie poprawnych zestawach danych. Interfejsy użytkownika powinny być sprawdzone pod kątem wprowadzania przypadkowych i nienaturalnych danych

34

Testowanie oprogramowania. Podręcznik dla początkujących

z dużym naciskiem na znaki, które są w sposób szczególny traktowane przez języki programowania, np. %, &, ! itp. Niemniej jednak baza danych nie powinna być uzupełniana jedynie losowymi i abstrakcyjnymi danymi.

1.7. Dokumentowanie testów Dokumentowanie wykonania testów jest zagadnieniem budzącym kontrowersje oraz mieszane uczucia. Testerowi trudno dostrzec wartości płynące z tego działania, gdyż często odbiera on ten wymóg jako formę sprawowania kontroli nad jego pracą i sprawdzania, czy i w jaki sposób wykonuje powierzone zadania. Nienegowanym faktem jest, że powstała dokumentacja może posłużyć do tego celu. Niemniej jednak nie jest to jej kluczowe zadanie i nie powinno być ono priorytetem w trakcie oceny i analizy materiału. Przebieg testów dokumentuje się w celu:  ewentualnego przedłożenia klientowi raportu z zakresu, metody i powodzenia

testów;  potwierdzenia wykonania testów w założonym zakresie;  zebrania szczególnie trudnych i istotnych warunków wejściowych w celu

zabezpieczenia możliwości ponownego odtworzenia sytuacji w przyszłości;  stworzenia repozytorium wskazówek, które mogą pomóc innym w wykonaniu

analogicznego testu;  wyłączenia odpowiedzialności lub odparcia zarzutów w sytuacjach spornych.

Dokumentacja wykonania testów odgrywa szczególną rolę w podnoszeniu efektywności procesu testowania, a tym samym jakości finalnego produktu. W oparciu o ów materiał można przeprowadzić rewizję pokrycia aplikacji testami oraz ich skuteczności. Zgromadzony materiał „dowodowy” powinien wskazać ewentualne luki w scenariuszach lub wręcz potwierdzić trafność zaprojektowanych testów. Taką ocenę można wykonać poprzez merytoryczne porównanie błędów zgłoszonych w fazie odbioru oprogramowania (klient) z przypadkami wykonanymi w firmie. Wysunięte wnioski powinny wyznaczyć kierunek do korekty i udoskonalenia testów. Dokumentacja wykonania testów powinna zawierać:  informacje o środowisku, takie jak nazwa bazy danych, wersje aplikacji,

identyfikator scenariusza/przypadku, którego dotyczy;  zrzuty ekranu z momentu wykonywania i przygotowywania testu;  wyciągi danych z bazy;  opis istotnych warunków, parametrów wejściowych itp.

Proces dokumentowania testów nie może przesłonić idei wykonywania testów. Zbieranie materiału powinno odbywać się w tle i nie determinować kreowania testu pod kątem zrobienia dobrego zrzutu. Za patologię należy uznać dokumentowanie wszystkich

Rozdział 1.  Ogólna teoria testowania

35

czynności, które odbywają się poprzez analogię do siebie, np. wielokrotne zrzuty tego samego obszaru w bardzo zbliżonych okolicznościach obsługi. Sporządzony materiał tester powinien traktować jako wartość, która z założenia ma wpłynąć pozytywnie na jego kompetencje i efektywność pracy.

1.8. Kontrola wersji oprogramowania Efektywny proces produkcji oprogramowania obligatoryjnie powinien być wspierany przez system kontroli wersji (ang. version control system). Owo oprogramowanie służy do śledzenia zmian w kodzie źródłowym, pomaga również w zachowaniu spójności repozytorium. Nowoczesna specyfika i kultura pracy korporacji charakteryzują się decentralizacją zasobów ludzkich. Oznacza to, że nad wspólnym przedsięwzięciem mogą pracować osoby rezydujące w różnych lokalizacjach geograficznych. Powyższe okoliczności ujawniają pierwszą konieczność zastosowania systemu, który pozwoli na łączenie i przechowywanie zmian dokonanych przez różnych programistów (współdzielenie zasobów — plików). Kolejnym zadaniem wspomnianego oprogramowania jest gromadzenie historii zdarzeń poczynionych na kodzie źródłowym. Metryka modyfikacji źródła wykorzystywana jest do analizy okoliczności zmian w kodzie. Na tej podstawie możliwe jest odtworzenie sekwencji zdarzeń, wskutek których wprowadzona została badana implementacja. Namierzenie momentu, w którym naniesione zostały krytyczne (negatywne) lub istotne (ważne) zmiany w produkowanej aplikacji, pozwoli na dotarcie do genezy zagadnienia. Repozytorium to element, który skupia wszystkich zainteresowanych w obrębie jednego projektu. Fakt ten wymusza zachowanie najwyższej ostrożności i odpowiedzialności za bazę danych, tak aby cieszyła się ona maksymalnie dużym zaufaniem, a jej zasoby nie budziły żadnych wątpliwości (dobro wspólne). Dużym zagrożeniem dla tego celu jest skłonność programistów do opieszałej publikacji materiału lub wręcz dogrywanie plików po fakcie zbudowania emisji (kompilacji) ze wskazanego źródła. Popularnym systemem kontroli wersji jest Subversion (SVN). Cechą charakterystyczną oprogramowania SVN jest dokonywanie transakcji atomowych. Oznacza to, że operacja przyniesie skutek, tylko jeżeli wszystkie pliki w sposób poprawny zostaną skopiowane. Takie rozwiązanie zabezpiecza przed sytuacją częściowej modyfikacji danych. Strona domowa projektu: http://subversion.apache.org/. Kolejnym ciekawym i równie popularnym rozproszonym systemem kontroli wersji jest GIT. Oprogramowanie zostało wydane w oparciu o licencję GNU GPL. Strona domowa projektu: http://git-scm.com/. System kontroli wersji nie rozwiązuje wszystkich problemów związanych z produkcją i emisją oprogramowania. Pożądanym uzupełnieniem lub wręcz podbudową do wdrożenia aplikacji typu SVN jest stosowanie tak zwanych dobrych praktyk korporacyjnych. Normatywy regulują nazewnictwo plików, zasady podnoszenia numeru wersji, opisy źródła modyfikacji etc. Tworzą podstawowe ramy współpracy. Żelazne podstawy kooperacji gwarantują niezbędne minimum w komunikacji pomiędzy działami i osobami uczestniczącymi w projekcie.

36

Testowanie oprogramowania. Podręcznik dla początkujących

Do dobrej praktyki powinno należeć:  wersjonowanie obiektów,  spójne nazewnictwo i oznaczanie plików,  opisywanie zmian (metryka),  opisywanie kodu.

Złożone systemy informatyczne składają się z wielu modułów, których współdziałanie coraz częściej wykracza poza relację jeden do jednego. Architektura wielowarstwowa (ang. multi-tier architecture) rozdziela interfejs użytkownika oraz obszar składowania i przetwarzania danych na osobne warstwy. Wielomodułowość komplikuje proces zarządzania wersjami poszczególnych elementów. Standardem jest numerowanie i „podbijanie” wersji dla całych aplikacji powstałych w wyniku kompilacji kodu (EXE, WAR, EAR). Niestety często zaniedbywane pod tym względem są luźne obiekty wchodzące w skład całego systemu, tj. pliki JavaScript (JS), CSS, HTML, biblioteki etc. W przypadku ujawnienia się wątpliwości odnośnie do zawartości merytorycznej tylko autor kodu poprzez jego analizę będzie w stanie ustalić, czy przekazany plik jest właściwy. Luki w wersjonowaniu obiektów wygenerują koszty w postaci dodatkowych zapytań do programisty, zwielokrotnienia obsługi administracyjnej tego samego materiału. Następstwem ograniczonego zaufania do elementów, które nie posiadają wersji, będzie wzmożenie czynności mających na celu weryfikację aktualności otrzymanych plików. Dojrzałym sposobem praktycznej realizacji wersjonowania jest pisanie dedykowanych funkcji, które zwracają identyfikator obiektu. W sytuacji kiedy zastosowana technologia utrudnia takie rozwiązanie, należy oznaczyć plik poprzez komentarz w kodzie. Listing nr 1.4 prezentuje funkcję zwracającą wersję pakietu w bazie Oracle. Wynik działania tej funkcji prezentuje rysunek 1.8. Listing 1.4. Pakiet w bazie Oracle z zaimplementowaną funkcją zwracającą jego wersję create or replace package tbook_pakiet is -- Author : RAFAL PAWLAK -- Created : 2013-10-27 22:09:12 -- Purpose : Pakiet służy do realizacji dyspozycji doładowania telefonu. Księguje. -- Zmienna: przypisuje wersję pakietu wersja Varchar2(10) :='1.0.0'; -- Deklaracja funkcji function daj_wersje return Varchar2; end tbook_pakiet; / create or replace package body tbook_pakiet is -- Funkcja zwracająca wersję pakietu tbook_pakiet function daj_wersje return Varchar2 is

Rozdział 1.  Ogólna teoria testowania

37

begin return wersja; end; end tbook_pakiet; /

Rysunek 1.8. Wywołanie funkcji zwracającej wersję pakietu Oracle

Szczególnie uciążliwy jest brak wersjonowania obiektów bazodanowych. W takich okolicznościach serwisowanie tego modułu jest bardzo trudne i kosztowne. Ustalenie, w jakim kodzie („wersji”) został wykryty problem, wymaga odwołania się bezpośrednio do środowiska (kodu w bazie). Niewątpliwie przełoży się to na trudności w replikacji błędu w środowisku testowym, a co za tym idzie, wykonana poprawka może okazać się nieskuteczna. Pożądaną praktyką jest zagnieżdżanie w plikach metryki, która poświadczy pochodzenie i cechy obiektu. Ów zabieg najczęściej stosuje się do materiałów przygotowanych w języku znaczników lub językach skryptowych, o ile nie wpłynie to negatywnie na bezpieczeństwo systemu (czytanie kodu źródłowego przez osoby postronne). Wspomniana metryka powinna zawierać informacje o autorze kodu, dacie i przyczynie ostatniej modyfikacji, historię zmian, zależności od innych obiektów, wersję itp. Przykład metryki dla pliku SQL przedstawia listing nr 1.5. Listing 1.5. Metryka pakietu Oracle create or replace package tbook_pakiet is -- Author : RAFAL PAWLAK -- Created : 2013-10-27 22:09:12 -- Purpose : Pakiet służy do realizacji dyspozycji doładowania telefonu. Księguje. /* Metryka zmian 1.2.1 - Poprawa literówki w komunikacie błędu by R.Pawlak 2013-10-27 1.2.0 - Dodanie nowej funkcji sprawdz_status_realizacji by R.Kowalski 2013-09-21 1.1.0 - Dodanie nowej funkcji anuluj_dyspozycje by R.Kowalski 2013-09-01 1.0.2 - Zwiększenie zakresu zmiennej ABC 1.0.1 - Poprawa obsługi wyjątków 1.0.0 - Powołanie pakietu na potrzeby funkcjonalności: doładowania telefonów */

38

Testowanie oprogramowania. Podręcznik dla początkujących -- Zmienna: przypisuje wersję pakietu wersja Varchar2(10) :='1.2.1'; -- Deklaracja funkcji function daj_wersje return Varchar2; end tbook_pakiet;

Zwyczaj opisywania kodu źródłowego (ang. source code) w trakcie prac programistycznych może okazać się zbawienny w momencie konieczności szybkiej analizy problemu, niedostępności programisty lub zakończenia współpracy z autorem. Komentowanie kodu stanowi również ułatwienie dla testera, który nierzadko musi pochylić się nad instrukcjami języka programowania. Przykład opisywania kodu źródłowego przedstawia listing 1.6. Listing 1.6. Przykład opisywania kodu źródłowego create or replace package body tbook_pakiet is -- Funkcja zwracająca wersję pakietu tbook_pakiet function daj_wersje return Varchar2 is begin return wersja; end; -- Funkcja weryfikuje kwotę doładowania, tj. odrzuca zlecenie, jeżeli kwota > 1000 zł function sprawdz_kwote(kwota in Number) return Varchar2 is begin -- Blok warunkowy, weryfikacja kwoty if kwota > 1000 then kwota_odp := 'za dużo'; else kwota_odp := 'ok'; end if; return kwota_odp; end; end tbook_pakiet; /

Podnoszenie wersji obiektu nie powinno stanowić jedynego ułatwienia w zarządzaniu repozytorium. Tam, gdzie to możliwe, trzeba dodatkowo wprowadzić system administracji nazwami plików. Obszarem, który idealnie się do tego nadaje, jest zbiór skryptów bazodanowych. Z uwagi na to, że skrypty są wykonywane na bazie i na skutek takiej akcji dopiero są powoływane (modyfikowane) obiekty lub dane, powinno się takie pliki SQL odpowiednio (unikatowo) oznaczać. Algorytm nadawania niepowtarzalnych nazw plikom skryptowym pozwoli na przechowywanie wszystkich plików w jednym miejscu, zabezpieczy możliwość powrotu do poprzedniej wersji obiektu, np. pakietu, ułatwi stworzenie całościowej instrukcji wgrania. Podmienianie plików jest bardzo niepokojącym zjawiskiem, które stwarza zagrożenie utraty kontroli nad przebiegiem

Rozdział 1.  Ogólna teoria testowania

39

procesu wytwórczego. Rysunek nr 1.9 prezentuje przykład nagannej obsługi pliku SQL, który implementuje zmiany w pakiecie w bazie danych. Rysunek nr 1.10 obrazuje prawidłowy algorytm postępowania. Rysunek 1.9. Naganna obsługa przekazywania zmian w kodzie

Rysunek 1.10. Prawidłowa obsługa przekazywania zmian w kodzie

Mechanizm oznaczania (nazywania) plików zawsze powinien być dopasowany do kultury pracy i wymogów przedsiębiorstwa bez względu na to, czy odnosi się to do obiektów o trwałej nazwie, czy skryptów wykonywanych na bazie. Celem wprowadzenia takiego standardu jest zachowanie spójności, przejrzystości i optymalizacja kosztów obsługi administracyjnej repozytorium. Obowiązkiem członków zespołu jest przestrzeganie wypracowanych zasad.

1.9. Obsługa zgłoszeń Efektywne zarządzanie i obsługa zgłoszeń dotyczących funkcjonowania sytemu stanowią podstawę w podnoszeniu jakości oprogramowania. Ów warunek dotyczy równorzędnie zarówno zgłoszeń wewnętrznych, jak i zewnętrznych. Najczęściej do raportowania i dalszej administracji błędami wykorzystuje się dedykowane w tym celu oprogramowanie, takie jak:  Bugzilla wyprodukowane przez Fundację Mozilla (ang. The Mozilla Foundation)

na licencji Mozilla Public License (MPL), strona domowa projektu: http://www.bugzilla.org/.  Mantis Bug Tracker na licencji GNU General Public License (GNU GPL),

strona projektu: http://www.mantisbt.org/.  JIRA firmy Atlassian na licencji komercyjnej.

Źródło, z którego napływa zgłoszenie, determinuje pewne modyfikacje podstawowego algorytmu obsługi błędów. Błędy zewnętrzne, tj. takie, które zostały wykryte i udokumentowane poza środowiskiem testów producenta, wymagają zwiększonej liczby

40

Testowanie oprogramowania. Podręcznik dla początkujących

kroków w procesie obsługi niż błędy wewnętrzne. Niemniej jednak większość reguł postępowania dla obu typów zgłoszeń ma charakter wspólny. Oba typy błędów powinny być obsługiwane w tym samym systemie z zastrzeżeniem niejawności zgłoszeń wewnętrznych dla osób postronnych (klientów). Błędy zewnętrzne pochodzą również ze środowiska produkcyjnego, dlatego też klienci są żywo zainteresowani śledzeniem bieżących postępów prac. Budowanie trwałych i stabilnych relacji z potencjalnymi klientami wymaga dużej elastyczności w postępowaniu, choć nie ma najmniejszej potrzeby dzielić się problemami, które zostały wykryte po stronie producenta. Rysunek 1.11 przedstawia podstawowy algorytm obsługi błędów zgłoszonych przez klienta.

Rysunek 1.11. Podstawowy proces obsługi błędów zewnętrznych

Ciąg czynności związanych z obsługą błędu zewnętrznego składa się z następujących kroków: 0. Wykrycie błędu, skierowanie problemu do producenta. 1. Wstępne przyjęcie zgłoszenia. 2. Wprowadzenie do systemu, skierowanie do produkcji lub odrzucenie. 3. Analiza przez programistę, podjęcie zgłoszenia lub jego odrzucenie. 4. Realizacja, przekazanie do testów. 5. Testy jakościowe, akceptacja lub cofnięcie do produkcji. 6. Emisja. 7. Koniec procesu.

Rozdział 1.  Ogólna teoria testowania

41

Krok 0. Na podstawie uwag płynących bezpośrednio od użytkowników systemu lub spostrzeżeń własnych zespołu testów klienta popartych dokumentacją i doświadczeniem formułowane jest zgłoszenie. Zgromadzony materiał przekazywany jest do producenta oprogramowania. Krok 1. Przesłany materiał jest formalnie przyjmowany, a następnie wstępnie analizowany pod kątem treści i załączników. Wkład do zgłoszenia powinien dokładnie opisywać i dokumentować problematyczną sytuację oraz zawierać wszelkie powiązane logi (w tym komunikaty błędów etc.). Krok 2. Zgłoszenie jest wprowadzane do systemu. Po wstępnym potwierdzeniu występowania błędu w oparciu o przesłaną dokumentację zgłoszenie jest kierowane do produkcji. Odrzucenie następuje gdy:  zachowanie systemu jest zgodne z wymaganiami (dokumentacją zamówienia);  istota problemu wykracza poza zakres zamówienia (nie jest to błąd,

lecz próba modyfikacji systemu);  brakuje załączników (logów, zrzutów itp.) lub treść jest niejasna

(nie można zrozumieć problemu). Krok 3. Programista szczegółowo analizuje problem, identyfikuje jego przyczynę, a następnie podejmuje decyzję o odrzuceniu lub realizacji zgłoszenia. Powodami odrzucenia mogą być:  błędy danych — np. będące skutkiem sztucznej ingerencji w bazie danych

lub działania innej aplikacji — które spowodowały niepożądane działanie systemu (sytuacja wykracza poza zamówiony poziom walidacji);  błąd operatora/użytkownika, który złamał procedury obsługi określonej sytuacji;  system zachował się poprawnie;  nie udało się odtworzyć błędu i zidentyfikować przyczyny (również

w środowisku produkcyjnym);  błąd wystąpił na skutek problemów ze środowiskiem, za które producent

oprogramowania nie bierze odpowiedzialności, lub klient nie zachował minimalnych wymogów. Krok 4. Wynikiem realizacji zgłoszenia jest przygotowanie poprawki i skierowanie jej do kontroli jakości. Krok 5. Poprawka weryfikowana jest pod kątem skuteczności. Tester akceptuje modyfikację lub kieruje problem do ponownego rozpatrzenia (błąd w dalszym ciągu występuje). Krok 6. Modyfikacja zostaje zatwierdzona i przygotowana do emisji. Emisja następuje w sposób określony w umowie. Oprogramowanie pudełkowe zwykle jest naprawiane poprzez wydanie oficjalnej aktualizacji. Krok 7. Obsługa zgłoszenia zostaje zakończona.

42

Testowanie oprogramowania. Podręcznik dla początkujących

W powyższym algorytmie pominięto fazę testów wewnętrznych podejmowanych przed przekazaniem zmian do etapu testów jakościowych. Chcąc rozszerzyć tę sekwencję, należy wstawić dodatkowy krok testów wewnętrznych przed etap testów jakościowych. Decyzyjność dla obu etapów jest identyczna, tj. zwrot do produkcji lub akceptacja i przekazanie produktu do kolejnego kroku. W przypadku błędów wewnętrznych sekwencja zdarzeń jest nieco krótsza i mniej skomplikowana: 1. Wykrycie, udokumentowanie i zarejestrowanie błędu w systemie. 2. Analiza problemu przez programistę, odrzucenie lub skierowanie do produkcji. 3. Realizacja i skierowanie do weryfikacji. 4. Testy, przywrócenie do produkcji lub akceptacja. 5. Zatwierdzenie modyfikacji i zamknięcie zgłoszenia.

Rysunek 1.12 pokazuje algorytm postępowania z błędami własnymi (wykrytymi u producenta). Błędy wewnętrzne najczęściej pojawiają się w momencie podjęcia nowego projektu testowego, tzn. modyfikacji funkcjonalności systemu lub opracowywania zupełnie nowej aplikacji. Oczywiście zdarzają się sytuacje wykrycia błędów przy okazji przeglądu aplikacji lub wykonywania innych testów, ale to są z reguły sytuacje sporadyczne. Zatwierdzona poprawka oczekuje na emisję. Zwykle dzieje się to w ramach emisji nowej wersji programu lub przekazania w całości zamówionego modułu. Błędy wewnętrzne nie wymagają dodatkowej weryfikacji formalnej i merytorycznej. Naturalnie żaden tester nie posiada nieomylności w portfelu własnych kompetencji, niemniej jednak większość zarejestrowanych problemów powinna być poprawiona. Chcę przez to powiedzieć, że zdarza się, iż tester zgłosi niezasadny błąd, który zostanie odrzucony przez programistę. Niemniej jednak takie przypadki zwykle stanowią niewielki odsetek. Wykonanie ponownego testu (retest) nie stanowi problemu z uwagi na brak konieczności zagłębiania się w istotę problemu, tak jak ma to miejsce przy obsłudze błędów zewnętrznych.

Rysunek 1.12. Podstawowy proces obsługi błędów wewnętrznych

Rozdział 1.  Ogólna teoria testowania

43

Zarejestrowany błąd wędruje pomiędzy poszczególnymi etapami, zmienia również opiekuna. Dzieje się to niezależnie od etapu, na którym się znajduje. Zmiana osoby odpowiedzialnej determinowana jest potrzebą skierowania problemu do osoby bardziej kompetentnej, np. w celu przeprowadzenia analizy, modyfikacji kodu lub wykonania testów. W ramach jednej fazy błąd może kilkakrotnie wędrować pomiędzy członkami zespołu, zanim zostanie ostatecznie obsłużony. Treść i zawartość wprowadzonego do systemu zgłoszenia powinny pozwolić na zrozumienie i identyfikację problemu. Niestety często wartość merytoryczna oraz jakość załączników nie odpowiada temu założeniu. Niewystarczający lub niejednoznaczny materiał wymusza podjęcie dodatkowych czynności polegających na wyjaśnianiu i ustalaniu stanu faktycznego. Dodatkowa praca zwykle generuje koszty, które obciążają budżet producenta. Płynąca konkluzja uwiarygodnia konieczność dbania o wysoką jakość wprowadzanych zgłoszeń. Prawidłowo zarejestrowane zgłoszenie powinno minimalnie zawierać:  sygnaturę scenariusza i/lub numer przypadku testowego, w trakcie którego

wykonywania został ujawniony błąd (przypadek nie zakończył się poprawnie);  informacje o środowisku testowym: namiar na bazę danych, serwer aplikacji itp.;  zestaw danych wejściowych niezbędnych do odtworzenia (podglądu) sytuacji;  zrzuty ekranu z opisem problemu;  treść komunikatów błędów (o ile one wystąpiły np. w momencie

przechwycenia wyjątku);  pliki logów dokumentujące problematyczne zdarzenie;  wersję systemu, pakietów bazodanowych, bibliotek itp., na których wykonywane

były testy;  klasę błędu powiązaną z priorytetem realizacji  błędy krytyczne muszą być

poprawiane natychmiast, natomiast usterki mogą zaczekać (warunki obsługi definiuje umowa z klientem lub procedury wewnętrzne producenta);  opcjonalnie referencję do innych obszarów, na których działanie raportowany

błąd ma istotny wpływ.

1.10. Testowanie obsługi wyjątków w kodzie Obsługa i logowanie wyjątków to terminy, które nie powinny być zarezerwowane jedynie dla programisty. Obsługa zdarzeń wyjątkowych w językach programowania jest realizowana na poziomie semantyki i składni owego języka. Wraz ze wzrostem kodu rośnie prawdopodobieństwo pojawienia się wyjątku (ang. exception). Programiści dokładają wszelkich starań, aby obsłużyć jak najwięcej sytuacji mogących wygenerować błędy, choć eliminacja wszystkich zagrożeń jest niemożliwa. Błędy występują w każdej

44

Testowanie oprogramowania. Podręcznik dla początkujących

aplikacji. Uszczelnianie programu przed niepożądanymi sytuacjami diametralnie zwiększa ilość kodu, który zamazuje właściwą logikę. Oczywiście duży zbiór problemów musi zostać obsłużony przy świadomej ingerencji programisty, np. walidacji pól w interfejsie użytkownika. Niemniej jednak wszystkie pozostałe obszary programu powinny być zabezpieczone przez mechanizm przechwytywania wyjątków. Podniesiony wyjątek musi zostać przechwycony (ang. catch), a następnie obsłużony. Każda aplikacja posiada zdefiniowaną strategię obsługi wyjątków. Tester ma obowiązek znać tę politykę. Wszelkie odchylenia od ustalonego standardu należy traktować jako błąd w kodzie. Strategia obsługi wyjątków musi być konsekwentnie wdrażana i kontynuowana dla nowych obszarów systemu. Równolegle z procesem weryfikacji wymagań biznesowych należy zbadać mechanizm przechwytywania wyjątków. Przechwycony wyjątek należy obsłużyć zgodnie z założeniami (realizacja kodu). Równie istotną kwestią jest prawidłowe logowanie (raport) incydentu do dziennika zdarzeń (plik logu). Obsługa i funkcjonowanie plików dziennika również leżą w potencjalnym obszarze zainteresowań testera. Kontroler jakości powinien sprawdzić, czy incydent został odnotowany, oraz zweryfikować zawartość wpisu (wartość merytoryczna). Kolejnym krokiem jest zwrócenie uwagi na czas przyrostu pliku. W przypadku kiedy logi przyrastają w szybkim tempie, należy pochylić się nad przyczyną tego stanu rzeczy. W dzienniku zdarzeń dane mogą być zapisywane w sposób podwójny (duplikaty), mogą być zbierane zbyt szczegółowe dane lub aplikacja może „rzucać wyjątkami” w bardzo krótkich odstępach czasu. Szybki wzrost rozmiaru plików może w niekontrolowany sposób skonsumować wolne zasoby pamięci masowej i obniżyć wydajność środowiska. Niedopuszczalną sytuacją jest przechwycenie wyjątku i zaniechanie dalszej jego obsługi. Listing 1.7 prezentuje fragment kodu, w którym sekcja dotycząca obsługi wyjątku jest martwa. W takich okolicznościach w momencie wystąpienia sytuacji krytycznej zostanie ona przejęta, ale operator nie zostanie o tym fakcie powiadomiony. Również nie zostanie odpowiednio zakończony proces biznesowy, tj. procedura wykona się do końca, ale z błędami, które nie zostaną obsłużone. Niestety często zdarzają się skrypty, ba, nawet procedury, dla których obsługa wyjątku jest na minimalnym poziomie, tj. na takim, aby kompilator „przepuścił” kod. Listing 1.7. Błędna obsługa wyjątku w PL/SQL DECLARE BEGIN -- Logika commit; EXCEPTION WHEN OTHERS THEN -- przechwycenie wyjątku null; -- brak obsługi wyjątku (logiki wyjątku) END;

Rozdział 1.  Ogólna teoria testowania

45

Listing 1.8 prezentuje minimalną obsługę wyjątku na przykładzie języka PL/SQL, gdzie problem jest raportowany operatorowi. Stwarza to szansę do późniejszej analizy problemu. Listing 1.8. Minimalny poziom obsługi wyjątku na przykładzie języka PL/SQL set serveroutput on; DECLARE BEGIN -- Logika execute immediate 'select * from test@dblink'; dbms_output.put_line('Blok wykonał się poprawnie'); EXCEPTION WHEN OTHERS THEN -- przechwycenie wyjątku + raport problemu null; dbms_output.put_line('Wystąpił problem - zbadaj go: '); dbms_output.put_line('SQLERRM: ' || SQLERRM); dbms_output.put_line('SQLCODE: ' || SQLCODE); end; /

Rysunki 1.13 oraz 1.14 prezentują efekt wykonania wcześniej przytoczonego kodu (listing 1.7 oraz listing 1.8) odpowiednio bez obsługi logowania wyjątków i z obsługą. Pierwsza próba zakończyła blok PL/SQL, ale nie wyświetliła informacji o problemie. Tester wykonujący kod może w pierwszej chwili mniemać, że procedura zadziałała poprawnie. Druga próba, która zrealizowana jest w oparciu o poszerzoną obsługę wyjątków (ponowne wykonanie kodu z udoskonaloną obsługą wyjątków), również zakończyła się poprawnym wykonaniem procedury przy jednoczesnym zwróceniu informacji o problemie z łączem bazodanowym. Rysunek 1.13. Wykonanie kodu PL/SQL bez logowania wyjątków

46

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.14. Wykonanie kodu PL/SQL z logowaniem wyjątków

Dojrzałe aplikacje oprócz strategii zarządzania wyjątkami wyposażone są w dedykowany kod przechwytywania sytuacji niepożądanych w miejscach, które zostały uznane za wymagane. Kontrola jakości obejmuje również kod wyprodukowany do zadań specjalnych. Wymownym przykładem są autonomiczne skrypty bazodanowe, które wykonywane są jednorazowo w celu migracji lub modyfikacji danych. Praca z dużym wolumenem danych zawsze jest kłopotliwa i ryzykowna. Wystąpienie niespodziewanych sytuacji podczas wykonywania kodu jest niemalże pewne. Zatem skrypt powinien być odpowiednio zabezpieczony przed spodziewanymi wyjątkami. Potwierdzenie implementacji obsługi wyjątków nie kończy pracy testera. Pozytywna opinia kontroli jakości może być wydana tylko wtedy, kiedy skrypt wykona się do końca, zrealizuje założenia biznesowe, wszystkie wyjątki zostaną obsłużone oraz sposób ich obsługi będzie zgodny ze sztuką. Fakt wystąpienia wyjątku w fazie testowania nie oznacza niczego złego. Kluczowe jest, aby wszelkie incydenty były odpowiednio logowane i obsługiwane pod względem logicznym (zachowanie kodu w momencie podniesienia wyjątku). Przechwytywane incydenty powinny być raportowane w sposób optymalny, to znaczy taki, który umożliwi dostateczną analizę sytuacji i podjęcie ewentualnych kroków naprawczych. Na miano „fuszerki” zasługują algorytmy, które po przechwyceniu wyjątku pomijają problem, tj. pracują dalej bez logowania i obsługi problemu (listing 1.7). Prawidłowo przygotowany skrypt wygeneruje listę problemów, która zostanie poddana szczegółowej analizie. Tester na podstawie zebranych danych może zbadać zaangażowanie procentowe liczby wyjątków do pełnego wolumenu danych (odsetek błędów w stosunku do wszystkich przetwarzanych danych). Ów wskaźnik określi ryzyko i skuteczność skryptu. Zbyt duża liczba przechwyconych błędów może świadczyć o niskiej jakości kodu, niezrozumieniu wymagań, wadliwych danych.

Rozdział 1.  Ogólna teoria testowania

47

Testowanie pod kątem obsługi wyjątków to również zadanie leżące w gestii kontrolera jakości. Zapewne programiści, którzy właśnie trzymają w ręku tę książkę, co najmniej lekko uśmiechają się pod nosem. „Co może wiedzieć tester o obsłudze wyjątku w programowaniu”. Zapewne mniej niż programista. A co może wiedzieć programista o realnym wyniku pracy własnego kodu, skoro go nie uruchomił na uczciwych i rzeczywistych danych? Mało efektywna obsługa i logowanie wyjątków przysporzy tylko kłopotów i zdaje się, że więcej ich przypadnie na osobę programisty niż na testera. Kontroler jakości uruchomi kod, stwierdzi błąd i go opisze, ale nie wskaże, w którym obszarze instrukcji jest problem i jaki jest aspekt merytoryczno-techniczny. W takich sytuacjach może nieco zelżeć dobry humor programisty, gdyż błąd jest ewidentny, a materiał do podjęcia analizy jest skromny. Z dużym prawdopodobieństwem tester zostanie poproszony o ponowienie testu, tj. pomoc w odtworzeniu błędu. Niemniej jednak bywa, że powtórzenie testu jest utrudnione i czasochłonne ze względu na długą ścieżkę przygotowania lub „zepsucie” głównego wolumenu danych za pierwszym podejściem. Oczywiście profesjonalny tester powinien być przygotowany na podjęcie drugiej próby, co równoznaczne jest z zabezpieczeniem środowiska pod kątem jednorazowego wykorzystania danych, ale w ostatecznym rozrachunku przysparza dodatkowych kosztów pracy. Rozważmy poniższy przykład w celu uwiarygodnienia powyższych rozważań. Klient zamówił program, który wykona migrację danych teleadresowych z kartoteki klienta i numerów telefonów zamieszczonych w niej osób i dokona konsolidacji danych. Wspomniana konsolidacja polegać ma na przeniesieniu danych z dwóch do jednej tabeli przy uwzględnieniu ich reorganizacji, tj. połączeniu danych z kilku kolumn, dodaniu informacji „ekstra” i wstawieniu takiego ciągu do jednej z kolumn. Rysunek 1.15 prezentuje opis trzech tabel, które posłużą do wykonania testu. Rysunek 1.15. Opis tabel wykorzystanych do testu migracji danych

48

Testowanie oprogramowania. Podręcznik dla początkujących

Dane z tabeli TB_KARTOTEKA oraz TB_KONTAKTY powinny być stosownie złączone i przeniesione do tabeli TB_KARTOTEKA_NOWA. Pozwolę sobie pominąć szczegółowy opis algorytmu procedury, który można wyczytać z listingu 1.9. Niemniej jednak główny ciężar logiki skupia się na kolumnie UWAGI w tabeli TB_KARTOTEKA_NOWA. Do wskazanej kolumny powinny trafić dane: numer telefonu, preferowane godziny kontaktu, data ostatniego kontaktu, tj. sysdate minus liczba dni od ostatniego kontaktu, oraz ekstraopis według instrukcji IF (listing 1.9). Listing 1.9. Kod PL/SQL, który odpowiada za migrację danych z logowaniem problemów set serveroutput on; declare z1 tb_kartoteka.imie%type; z2 tb_kartoteka.nazwisko%type; z3 tb_kartoteka.adres%type; b1 tb_kontakty.nr_tel%type; b2 tb_kontakty.godz_kontak%type; b3 tb_kontakty.ostatni_kontak%type; sql_stmt varchar2(200); t number := 1; alarm varchar2(50); c1 varchar2(300); CURSOR kursor is select t.imie, t.nazwisko, t.adres, n.nr_tel, n.godz_kontak, n.ostatni_kontak from tb_kartoteka t, tb_kontakty n where t.id = n.id; begin OPEN kursor; LOOP FETCH kursor INTO z1, z2, z3, b1, b2, b3; EXIT WHEN kursor%notfound; if b3 > 60 then alarm := 'Klient wymaga pilnego kontaktu. Rano 8-10, wieczorem 19-22. Klient jest bardzo wymagający, zadaje dużo pytań i trudno jest go zachęcić do zakupu.'; else alarm := 'OK'; end if; c1

:= 'Nr tel.: ' || b1 || '| Preferowane godziny kontaktu: ' || b2 || '| Ostatni kontakt: ' || (sysdate - to_number(b3)) || ' | ' || alarm;

Rozdział 1.  Ogólna teoria testowania

49

sql_stmt := 'INSERT INTO tb_kartoteka_nowa VALUES (:1, :2, :3, :4, :5)'; execute immediate sql_stmt using t, z1, z2, z3, c1; commit; t := t + 1; dbms_output.put_line(z1 || ' ' || z2 || ' = OK'); END LOOP; CLOSE kursor; exception WHEN OTHERS THEN dbms_output.put_line('==================='); dbms_output.put_line('Pozostałe błędy - raport'); dbms_output.put_line('Problem z klientem: ' || z1 || ' ' || z2); dbms_output.put_line('SQLERRM: ' || SQLERRM); dbms_output.put_line('SQLCODE: ' || SQLCODE); dbms_output.put_line('==================='); end;

Rysunek 1.16 przedstawia wynik zapytania SQL pobranego bezpośrednio z kursora procedury oraz wynik pobrania danych z nowej tabeli, do której zostały przeniesione dane po wykonaniu powyższego zestawu instrukcji.

Rysunek 1.16. Wynik zapytania SQL z kursora oraz pobranie danych z tabeli docelowej

Przyglądając się dokładnie, nietrudno dostrzec, że kursor wybrał 4 wiersze, a tylko 3 zostały wstawione do tabeli migracyjnej. Zatem coś musiało się stać, gdyż jeden wiersz został pominięty. Z kodu nie wynika, aby dane pobrane do kursora podlegały dodatkowej selekcji przed migracją. Rysunek 1.17 przedstawia wynik wykonania procedury z listingu 1.9, ale bez logowania wyjątków. Ewidentnie operator skryptu nie ma podstaw podejrzewać, że wystąpiły trudności, gdyż nie otrzymał żadnego sygnału „z kodu”. Rysunek 1.18 pokazuje rezultat wykonania tego samego kodu, ale wzbogaconego o instrukcje logowania problemów. W tym przypadku oprócz raportu poprawnie przeniesionych klientów operator otrzymuje relację o zaistniałych „po drodze” problemach. Omawiany przykład zawiera sztucznie spreparowany (założony) błąd polegający na tym, że dla jednego klienta zostanie przekroczony zakres pola. Takie sytuacje mogą często występować w momencie przepisywania danych przy jednoczesnym łączeniu

50

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.17. Rezultat wykonania kodu bez logowania błędów

Rysunek 1.18. Rezultat wykonania kodu z logowaniem błędów

ich oraz dopisywaniu jakichś danych według algorytmu. Programiście jest bardzo trudno przewidzieć wszystkie sytuacje. Skuteczność kodu weryfikowana jest w trakcie jego użycia na dużym wolumenie danych o jakości zbliżonej do rzeczywistych. Przyzwoite przechwytywanie wyjątków i logowanie problemów stanowi podstawę do ewentualnych modyfikacji kodu i uskuteczniania założonego celu.

Rozdział 1.  Ogólna teoria testowania

51

1.11. Narzędzia wsparcia pracy testera Tester oprogramowania w celu poprawienia efektywności, skuteczności oraz miarodajności testów wspomaga się szeregiem narzędzi, którymi uskutecznia czynności. Poniżej przedstawiam listę (niezbędnik) narzędzi osobistego wsparcia pracy testera: SoapUI — narzędzie umożliwiające testowanie usług sieciowych (web service). Strona domowa projektu: http://www.soapui.org/. Notepad++ — edytor tekstu z podświetlaniem składni języka programowania; http://notepad-plus-plus.org/. NetBeans IDE — środowisko programistyczne; https://netbeans.org/. Eclipse — środowisko programistyczne; http://www.eclipse.org/. Apache JMeter — narzędzie do przeprowadzania testów wydajnościowych, obciążania systemu, np. bazy danych, web service czy stron WWW; http://jmeter.apache.org/. Fiddler2 — lokalne proxy wykorzystywane np. do debugowania stron WWW; http://fiddler2.com/. Firebug — wtyczka do Firefoksa pozwalająca np. na debugowanie stron; http://getfirebug.com/. TCPMon — monitor TCP (bez obsługi SSL); http://ws.apache.org/tcpmon/. Membrane Monitor — monitor HTTP i SOAP; http://www.membrane-soa.org/soap-monitor/. Wireshark — analizator ruchu sieciowego; http://www.wireshark.org/. VNC — narzędzie do zdalnego logowania, np. http://www.realvnc.com/. PL/SQL Developer, TOAD — klient bazy danych. WinSCP — klient z interfejsem graficznym SFTP, FTP; http://winscp.net/eng/ docs/lang:pl. PuTTY — klient SSH; http://www.putty.org/. Generatory certyfikatów SSL (KeyStore) — np. KeyStore Explorer. Generatory sumy kontrolnej — np. dla algorytmów SHA1 i MDA5; http://onlinemd5.com/. Menedżer plików  np. Total Commander przydatny przy przeszukiwaniu zasobów i porównywaniu zawartości plików etc. Narzędzia do robienia zrzutów ekranu (Prnt Scrn) — rynek oferuje liczne aplikacje o różnym stopniu zaawansowania. Każdy z pewnością znajdzie odpowiedni program dla siebie.

52

Testowanie oprogramowania. Podręcznik dla początkujących

Edytory tekstu — niezbędne do sporządzania dokumentacji z przeprowadzonych testów. Arkusze kalkulacyjne — wykorzystywane np. do obliczeń manualnych według algorytmu (sprawdzenie wyniku logiki systemu), generowanie danych testowych. Emulatory — zadaniem tego rodzaju oprogramowania jest „udawanie” innego programu, urządzenia lub jednej z funkcji innego systemu. Przykładem oprogramowania symulującego działanie mobilnej wersji przeglądarki Opera jest Opera Mobile Emulator. Nadmieniony emulator umożliwia wykonanie pretestów aplikacji dedykowanych na urządzenia mobilne na tradycyjnych desktopach. Walidatory — np. walidator wyrażeń regularnych. Programy do identyfikacji i zmiany kodowania znaków w pliku Serwer poczty (SMTP/POP3/IMAP), który można skonfigurować lokalnie — np. hMailServer; http://www.hmailserver.com/. Narzędzia do automatyzacji testów  np. Oracle OpenScript (http://www.oracle.com), SoapUI, Apache JMeter, Selenium (http://www.seleniumhq.org/). Generatory danych testowych — np.: http://www.generatedata.com/.

1.12. Presja czasu Na pierwszych kartach publikacji poruszyłem przykry aspekt postrzegania działu testów jako czynnika blokującego, a nawet opóźniającego wydanie wersji oprogramowania klientowi. Projekt jest przedsięwzięciem, które osadzone jest w ściśle zdefiniowanych ramach czasu. Posiada początek i koniec. Data końca projektu stanowi punkt krytyczny, którego przekroczenie, tj. niewykonanie zadania w terminie, zwykle niesie za sobą znaczne konsekwencje. W ostatecznej formie owe konsekwencje mogą objawić się poprzez pogorszenie wyniku finansowego, co nigdy nie ujdzie uwagi najwyższych władz w przedsiębiorstwie. Problem, skutek, wina, kara. To cztery kroki zwykle podejmowane po „obsuwie” terminu lub całkowitym „położeniu” projektu. Faktem oczywistym jest, że nikt nie chce dopuścić, aby wspomniany scenariusz się ziścił. Etap kontroli jakości stanowi ostatnie ogniwo w procesie produkcji oprogramowania. Zatem zespół testów narażony jest na niebywałą presję, której efektem ma być wydanie oprogramowania w terminie. Oczywiście zamknięcie projektu w terminie bez uszczerbku na jakości finalnego produktu. Przyjrzyjmy się rysunkowi 1.19. Niezależne testy jakościowe, tj. takie, które odbywają się w autonomicznym środowisku (innym niż deweloperskie), faktycznie rozpoczęły się ze znacznym opóźnieniem w stosunku do planowanego harmonogramu. Faza programowania została zakończona w momencie, w którym powinny już odbywać się ostateczne testy jakościowe (punkt B). Testy modułowe w wyniku opóźnienia implementacji również zakończono znacznie później. To bardzo niekomfortowa sytuacja dla zespołu testów jakościowych. W oczach PM projekt ma jeszcze kilka dni na osiągnięcie gotowości. Niemniej jednak w rzeczywistości przy zachowaniu planowanego wysiłku testów (intensywności) nie ma

Rozdział 1.  Ogólna teoria testowania

53

Rysunek 1.19. Hipotetyczny przebieg realizacji projektu produkcji oprogramowania

realnej szansy na zakończenie go w terminie. Niestety racje i argumenty zespołu testów nie mają szans przebicia się przez strach i widmo konsekwencji przesunięcia daty końcowej projektu. Oprogramowanie ma zostać wydane w terminie! Co to oznacza dla kontroli jakości? Problem! Wykonanie założonego planu testów w skonsolidowanym oknie czasu, tj. wykonanie zakresu testów rozpisanego np. na 12 dni w 5. Zważyć należy na brak zwolnienia z odpowiedzialności za jakość. Ugięcie się pod naciskami PM nie gwarantuje rozgrzeszenia w przypadku pogorszenia jakości. Niemniej jednak jeżeli zapadnie decyzja o wydaniu produktu w planowanym terminie, należy zintensyfikować wysiłek testerski i przeprowadzić maksymalnie wiele założonych testów (rysunek 1.20).

Rysunek 1.20. Zwiększenie intensywności testów, tak aby dotrzymać terminu dostawy

54

Testowanie oprogramowania. Podręcznik dla początkujących

Intensyfikacja procesu testów jest często stosowaną praktyką w celu dotrzymania terminu dostawy lub minimalizowania strat. Zwykle jest to rozsądna i dobra droga, choć obarczona wadami i ryzykiem. Wzmożenie wysiłku wymaga zaangażowania dodatkowych osób i/lub zwiększenia zaangażowania już pracujących przy projekcie. Nowe osoby wymagać będą wprowadzenia do roli i z pewnością nie będą „czuły” oprogramowania w takim samym stopniu jak osoby pracujące od początku. Niesie to ze sobą ryzyko przeoczenia błędów, które z dużym prawdopodobieństwem zostałyby wykryte przy normalnym przebiegu ścieżki testów. Zmęczenie również nie pozostaje bez skutku na percepcję, a wydłużenie godzin pracy sprzyja zmniejszeniu sprawności testera. Niemniej jednak przy uwzględnieniu korzyści i ryzyka bilans takowego rozwiązania można uznać za dodatni. Kluczem do sukcesu jest, aby wyjątek nie potwierdzał reguły. Równomierne obciążenie pracą sprzyja efektywności i skuteczności testów. Po intensywnym, lecz krótkotrwałym wysiłku powinien nastąpić okres regeneracji. Niewskazane jest obciążanie testera „nową akcją ratunkową” już w kolejnym dniu po świeżo zamkniętym projekcie. Więcej informacji o aspektach zmęczenia pracą zamieściłem w rozdziale poświęconym „syndromowi zniechęcenia testami”. Kolejna prawda o testowaniu brzmi: Nie istnieje opcja zwolnienia działu testów z odpowiedzialności za ostateczną jakość produktu.

1.13. Profil profesjonalnego testera Praca w charakterze testera oprogramowania może dostarczyć wiele satysfakcji, lecz także wymaga pewnych predyspozycji, umiejętności i zaangażowania w powierzone zadania. Jest to praca odpowiedzialna i specyficzna pod względem wymagań stawianych potencjalnym kandydatom. Z jednej strony pożądany jest szereg kompetencji twardych, z drugiej strony równie istotne są umiejętności społeczne. Kompetencje twarde, np. umiejętność czytania kodu JAVA, stanowią podstawę do wykonywania powierzonych zadań. Praca testera oprogramowania wymaga szerokich umiejętności twardych, ale niekoniecznie głęboko wnikających w dany aspekt. Pożądane jest, aby tester potrafił czytać kod, przygotować własne fragmenty instrukcji, ale nie musi on posiadać równie biegłej umiejętności programowania co koderzy. Analogiczne podejście można zastosować do ogółu technologii zastosowanych w architekturze systemu i jego kodzie. Warto znać serwer bazy danych, ale nie trzeba już się martwić o wiedzę z zakresu strojenia i administracji. Zgoła inaczej sytuacja wygląda w kwestii umiejętności posługiwania się narzędziami stricte testerskimi, np. SoapUI, JMeter, OpenScript. Aplikacje poddawane testowaniu mogą dotyczyć najróżniejszych dziedzin życia, które czasami okazują się zupełnie obce dla testera. Zatem szeroka wiedza ogólna jest niezmiernie ważna, aby prawidłowo wykonywać testy, tj. rozumieć aplikację. Reasumując: tester powinien dysponować ugruntowanymi umiejętnościami twardymi ściśle związanymi z wykonywaniem testów i np. specjalistycznymi narzędziami, mieć szeroką wiedzę ogólną oraz znać w przynajmniej podstawowym zakresie zastosowane technologie. Szczegółowe testy niefunkcjonalne, np. wydajnościowe, mogą wymagać większego zainteresowania się aspektami technologicznymi, np. zgłębienia zasad działania i możliwości serwera bazy danych.

Rozdział 1.  Ogólna teoria testowania

55

Pożądane kompetencje twarde w pracy testera oprogramowania (ogólne ujęcie minimum):  umiejętność czytania kodu, podstawy programowania (np. w celu napisania

robota generującego dane i zasilania nimi bazy);  znajomość narzędzi wspomagających testy, np. SoapUI, JMeter, klient serwera

bazy danych, Fiddler, OpenScript, środowiska programistyczne, np. Eclipse, itp.;  znajomość wykorzystywanych technologii, np. serwer WWW, serwer bazy

danych, system operacyjny, np. Android;  znajomość szeregu technologii pobocznych do głównego nurtu wykonania

aplikacji, np. XML, SOAP, REST, CSS, HTTP itp.;  umiejętność obsługi urządzeń końcowych, np. nawigacji GPS, urządzeń

mobilnych (sprzęt, na którym uruchamiane jest oprogramowanie);  znajomość narzędzi do zarządzania testami i raportowania błędów;  umiejętność projektowania i wykonywania testów;  wiedza z zakresu metodyki testowania;  wiedza z zakresu projektowania i wytwarzania oprogramowania, np.

podstawowa znajomość wzorców projektowych;  wiedza z obszaru, jakiego dotyczy oprogramowanie, np. telekomunikacji,

gastronomii itp. Wbrew pozorom zbyt bogate kompetencje z zakresu jednej technologii mogą przeszkadzać w wykonywaniu testów funkcjonalnych. Należy pamiętać, że końcowi użytkownicy zwykle nie mają żadnej wiedzy technicznej i skupiają się jedynie na użytkowaniu systemu. Wydatnym przykładem są sytuacje, w których programiści podejmują się pracy w roli testera oprogramowania. Trudność polega na tym, że nie są oni zwykle w stanie porzucić myślenia programistycznego i analizowania funkcjonalności poprzez wyobrażanie sobie kodu. Zbyt daleko idąca wnikliwość w aspekty techniczne może przesłonić istotne problemy funkcjonalne, np. no tak, inaczej nie mógł tego zrobić. Może mógł, może nie. Wątpliwość powinna być zgłoszona i poddana analizie, może udałoby się wypracować jakieś lepsze rozwiązanie. Dochodzimy do momentu, w którym należy postawić pytanie: Czy programista może być dobrym testerem oprogramowania?. W mojej opinii nie. Nie wynika to z braku chęci czy kompetencji technicznych, ale z naleciałości, jakie odziedziczył podczas pracy w dziale deweloperskim. Owo dziedzictwo zwykle nie stanowi wartości dodanej, lecz stanowi przeszkodę w odnalezieniu się w roli testera. Czy tester może być dobrym programistą? Może, ale to jest bilet w jedną stronę. Doświadczenie zdobyte w testach może wpłynąć na rozwiązania zastosowane w kodzie z uwagi na to, że świeżo upieczony programista oczami wyobraźni widzi efekt działania kodu i może wyczuwać pewne problemy. Natomiast dużą przeszkodą może okazać się brak doświadczenia w programowaniu. Tegoż elementu nie da się nadrobić w krótkim czasie. Z jednej strony doświadczenie w testach pozwoli uniknąć pewnych błędów funkcjonalnych, a z drugiej strony brak doświadczenia w programowaniu spowoduje intensyfikację typowych błędów programistycznych (np. składnia kodu).

56

Testowanie oprogramowania. Podręcznik dla początkujących

Kompetencje psychospołeczne są równie istotne jak doświadczenie i wiedza merytoryczna. Umiejętności miękkie są niezwykle ważne w trakcie wykonywania powierzonych zadań w zawodzie testera oprogramowania. Tester musi skutecznie radzić sobie ze stresem, efektywnie zarządzać czasem, wykazywać się elastycznością w działaniu, mieć inicjatywę, współpracować w grupie oraz sprawnie komunikować się. Niektóre z umiejętności interpersonalnych, np. empatia, życzliwość, zdolności komunikacyjne, ułatwiają kształtowanie pozytywnych relacji z pozostałymi członkami zespołu. Równie istotne są cechy, które odpowiadają za budowanie społecznej pozycji testera, takie jak pewność siebie, umiejętność pokonywania trudności, zdolność do prezentacji i obrony poglądów, asertywność. Umiejętności miękkie należy nieustannie rozwijać i pielęgnować. Wydawać by się mogło, że zawód informatyka nie wymaga nadzwyczaj rozwiniętych wyżej wymienionych zdolności. Praca testera wymaga innowacyjności, dynamiki w działaniu, kooperacji. Istotą pracy testera jest weryfikacja jakości oprogramowania. Można by więc powiedzieć, że powinien usiąść i „przeklikać” aplikację, zakończyć zadanie i przekazać produkt. Kłam temu podejściu zadaje szereg czynności pobocznych, które tester musi wykonać, aby właściwie zrealizować powierzone zadanie. Prawdą jest, że samo wykonanie testu opiera się głównie na kompetencjach merytorycznych. Niemniej jednak zanim do tego dojdzie, tester z reguły musi zmierzyć się z szeregiem problemów, których przezwyciężenie wymaga wysoko rozwiniętych kompetencji społecznych. Przytoczę poniżej kilka sytuacji (przykładów), które powinny uwiarygodnić powyższy wywód.  Tester powinien wykazać się innowacyjnością i kreatywnością np. na potrzeby

symulacji błędu albo opracowania specyficznego scenariusza testów (łamanie szablonów).  Tester powinien potrafić publicznie wygłaszać swoje opinie, uzasadniać je oraz

bronić ich w sytuacji, kiedy uzna, że coś działa wadliwie lub może nie spełniać oczekiwań klienta. Niepożądane jest przystawanie na propozycje, które nie są poparte żadnymi argumentami, typu „tak zrobiłem i tak zostanie”.  Tester powinien poczuwać się do współodpowiedzialności za produkt i nie

ulegać presji (socjotechnika współpracowników). Wszelkiej maści naciski ujawniają się w momencie zagrożenia terminu.  Tester powinien rozumieć uczucia innych, ale nie na zasadzie wyrzeczenia się

dobrych praktyk i standardów. Porozumienie zawsze jest możliwe. Niemniej jednak nie można pracować jako kontroler jakości w poczuciu, że wykonuje się gorszą pracę niż programista czy projektant.  Tester bardzo dużo rozmawia i komunikuje się z klientem wewnętrznym

i zewnętrznym. Zatem niezmiernie ważne są zdolności komunikacyjne, poczucie humoru, konsekwencja i śmiałość.  Niezmiernie ważne jest zachowanie wyważonej asertywności, tak by nie

zaburzyć równowagi pomiędzy chęcią pomocy innym a byciem wykorzystywanym. Idealny zespół testerów to taki, w którym wszyscy członkowie dysponują zróżnicowanym poziomem poszczególnych kompetencji miękkich. Oczywiście punktem wyjścia jest spełnienie ogólnie zdefiniowanych wymagań minimalnych (kompetencje

Rozdział 1.  Ogólna teoria testowania

57

twarde i miękkie). Niemniej jednak u podstaw siły zespołu leży zróżnicowanie umiejętności interpersonalnych. Projekty są różnorodne pod względem złożoności technologicznej, trudności merytorycznej, potrzeb komunikacji i specyfiki testów. Osoby odgrywające wiodącą rolę w projekcie powinny być dobierane pod względem predyspozycji, które są kluczowe do zrealizowania zadań. Osoby, które są niezmiernie skrupulatne, potrafią długotrwale pracować nad jednym zadaniem, idealnie odnajdą się w zadaniach testowych wymagających żmudnej, długotrwałej pracy, zanim osiągnie się ostateczny rezultat. Jednostki mniej cierpliwe mogą próbować iść na skróty i pominąć kilka ważnych kroków, które mogą okazać się istotne w aspekcie jakości oprogramowania. Z kolei projekty wymagające wytężonego wysiłku — nazwijmy to negocjacyjnego — nie powinny być powierzane osobom zbyt nieśmiałym, uległym i szybko się irytującym (w roli lidera). Efektywne zarządzanie projektem wymaga, aby przy każdym nowym przedsięwzięciu formować grupę według predyspozycji i zadań. Dobrze dobrany zespół połączony administracyjnie zwykle dysponuje odpowiednim potencjałem do utworzenia grupy niejako uszytej na miarę projektu. Ktoś, kto jest liderem w projekcie A, w kolejnym przedsięwzięciu B może odgrywać inną rolę, co nie oznacza, że mniej ciekawą i mniej odpowiedzialną. To jest kolejna cecha, jaką powinien wykazywać się tester. Mianowicie elastyczność i łatwość w dostosowywaniu się do nowych ról. Osoby pyszne, władcze i czułe na krytykę będą miały trudności w podejmowaniu działań w roli wykonawcy zadań (bez wpływu na elementy organizacyjne). Praca testera może być postrzegana jako zbędny generator kosztów, gdyż w mniemaniu zarządzających w wyniku owego procesu nie powstaje żadne wymierne dobro (produkt). Wiadomo, programista koduje i na zakończenie pracy „zostawia” aplikację. A tester? Cóż, siedzi, czepia się, stuka w klawiaturę, marudzi i w dodatku nie pozwala na przekazanie aplikacji klientowi (czytaj: sprzedaż). Na szczęście ten wielce niesprawiedliwy trend ulega odwróceniu. Doświadczeni testerzy są bardzo cenieni na rynku pracy. Producenci oprogramowania dostrzegają związek pomiędzy wysoką jakością swoich produktów a realnym przełożeniem tego wskaźnika na renomę i długofalowy sukces firmy. Tester to osoba, która dokonuje pierwszego uruchomienia aplikacji, nowego modułu wnoszącego dodatkowe funkcjonalności do systemu. Sprawdza również zgodność implementacji z wymaganiami klienta w oparciu o dostarczoną dokumentację. To tester jest osobą, która decyduje o uznaniu aplikacji za zdatną do przekazania klientowi. Tester sprawdza, czy analiza, projekt oraz jego implementacja mają rację bytu w rzeczywistości. Jest takie powiedzenie „papier przyjmie wszystko”, ale klient już tego nie uczyni w odniesieniu do gotowego produktu. Certyfikaty i szkolenia Profesja testera oprogramowania znajduje coraz mocniejsze uznanie wśród osób, które są w przededniu wyboru zawodu, swojej ścieżki kariery. Analizując oferty pracy, można dostrzec, że specjaliści w tej dziedzinie są coraz bardziej poszukiwani i doceniani. Trudno wyobrazić sobie proces wytwarzania oprogramowania, w którym nie realizuje się jego weryfikacji pod względem jakości. Obecnie testowanie oprogramowania wymaga zaangażowania osób o ściśle pożądanych kompetencjach miękkich i twardych.

58

Testowanie oprogramowania. Podręcznik dla początkujących

Nie mogą to być ludzie przypadkowi, którzy nie do końca mają zamiar utożsamiać się z tym zawodem. Profesjonalny tester oprogramowania powinien legitymować się przynajmniej podstawowym certyfikatem ISTQB (http://www.istqb.org/). Płyną z tego dwie korzyści. Po pierwsze: taki dokument uwiarygodnia jednostkę w oczach pracodawcy. Po drugie: zwiększa jej szanse na rynku pracy. Merytoryczna wartość dodana z ukończenia owego kursu jest kwestią dyskusyjną. Można by polemizować, na ile treść kursu może być przeniesiona na warunki realne. Niemniej jednak ten certyfikat nie niesie ze sobą nic negatywnego. Systemy informatyczne stają się coraz bardziej skomplikowane i wyrafinowane. Często szyte są na miarę potrzeb poszczególnych organizacji. Wymaga to od testera dużego zaangażowania merytorycznego. Zatem niezmiernie ważne jest odbywanie regularnych szkoleń technicznych, np. z narzędzi wspomagających testy i technologii, w której wytwarzane jest oprogramowanie. Rzecz jasna kluczem jest świadomość konieczności samokształcenia się i doskonalenia własnych kompetencji również samodzielnie. Anegdota Niegdyś miałem okazję uczestniczyć w szkoleniu poświęconym testowaniu oprogramowania. Trener wyraził twardą opinię, że tester jest w stanie zweryfikować każde oprogramowanie, o ile ma stosowną do tego dokumentację. W mojej ocenie to stwierdzenie nie jest prawdziwe. Rzeczą oczywistą jest, że doświadczony tester może zweryfikować wiele różniących się od siebie aplikacji, ale nie wszystkie. Trudno mi sobie wyobrazić, aby ktoś, kto nie ma najmniejszego pojęcia o lotnictwie, kolejnictwie, medycynie lub kosmonautyce, mógł przeprowadzić testy dedykowanego oprogramowania tylko w oparciu o otrzymaną dokumentację. Załóżmy, że otrzymamy do weryfikacji program służący do korygowania trajektorii lotu pocisku balistycznego. Siadamy, bierzemy do ręki setki, tysiące stron dokumentacji z wzorami i opisem… i co dalej? Oczywiście to są skrajne rozważania. Dążę do tego, aby powiedzieć, że istnieją pewne obszary, pewne oprogramowanie, do testów którego wymagana jest należyta podstawa merytoryczna, tzn. „pozatesterska”. Warunkiem koniecznym jest rozumienie tego, co się robi, oraz poczucie sensu i istoty obszaru, którego dotyczy oprogramowanie. Wspominam o tym, aby przestrzec przed zbytnią wiarą w elastyczność pracy jako tester, gdyż może być to zgubne dla samego testera i dla oprogramowania. Przystosowanie do testów nowego systemu może być bardzo łatwe, czasem nieco bardziej wymagające lub wręcz szalenie trudne.

1.14. Testowanie w oknie czasu Czas jest elementarnym składnikiem organizacji życia społecznego, zatem odgrywa on również istotną rolę w procesie wytwarzania oprogramowania. Wiemy już, że sam projekt jest osadzony w ramach czasu, tj. ma początek i koniec. Niemniej jednak w tym rozdziale zastanowimy się nad aspektami czasu w funkcjonującej produkcyjnie aplikacji. Czasami stosuje się zabieg, w którym z premedytacją przerywamy proces po upływie ściśle zdefiniowanego czasu (ang. time out). Ograniczenie czasu dla danego zdarzenia pozwoli uniknąć sytuacji nieskończonego oczekiwania na odpowiedź, a w konsekwencji blokady procesu. Drugim klasycznie stosowanym przykładem jest wylogowanie użytkownika z systemu po upływie określonego czasu bezczynności. Oczywiście jest to robione z uwagi na względy bezpieczeństwa i poniekąd nie ma potrzeby utrzymywania

Rozdział 1.  Ogólna teoria testowania

59

„martwych” sesji. Są to przypadki kiedy to my, tj. nasza aplikacja, sterujemy czasem. Testy powyższych założeń nie powinny sprawić wielkich trudności. Sytuacja diametralnie ulega komplikacji w przypadku, kiedy zainicjowany proces bezwzględnie musi zakończyć się w założonym oknie czasu, np. trwać od 22:00 do 23:35, tj. 01:35 minut. Wspomniane założenie nie może podlegać żadnym formom uelastycznienia z uwagi na to, że na przykład o godzinie 23.40 rozpoczyna się kolejny proces, który korzysta z wyników pracy pierwszego. Ten drugi proces powinien zakończyć się przed 23.55, bo o godzinie 00:00 następuje „przejście przez dzień” i „wszystko zaczyna się od początku”. Testy takich procesów mogą okazać się kłopotliwe i nie zawsze uda się ocenić w pełni skuteczność zmian. Główną przeszkodą jest brak porównywalnych zasobów sprzętowych (serwerów) oraz wolumenu danych (nie tylko ilość, ale i jakość). Przetwarzanie porównywalnego zakresu danych w środowisku testowym może trwać znacznie dłużej niż analogiczna czynność procesowana w środowisku docelowym. Nadmieniłem, że jest to związane z różnicami w potencjale obliczeniowym obydwu środowisk oraz specyficznej konfiguracji np. serwera bazy danych. Ostateczne testy zapewne będą odbywać się w środowisku docelowym przy współpracy z klientem. Niemniej jednak zanim zostanie wykonana taka próba, należy poczynić trud wstępnej weryfikacji w oparciu o zasoby działu testów. Po pierwsze: tester może zweryfikować, czy logika biznesu działa prawidłowo. Po drugie: można zbadać, czy założenia odgrywające kluczową rolę dla wydajności, np. ilość powołanych wątków w przetwarzaniu, funkcjonują. Po trzecie: można spróbować sztucznie napełnić danymi system i uruchamiać procesy na podwyższonej ilości danych. Jeżeli jest to modyfikacja wydajnościowa już istniejącego rozwiązania, warto sprawdzić, czy nowe podejście nie działa gorzej, np. wolniej, od dotychczasowego, na identycznym zestawie danych (okolicznościach). Zapewne będzie trudno odnotować zauważalną poprawę wydajności, gdyż w dużych systemach takie zmiany projektuje się bezpośrednio pod środowisko docelowe, ale jeżeli się nie pogorszy i biznes będzie funkcjonował poprawnie, można rozpocząć przygotowania do weryfikacji w środowisku ostatecznym. Kolejnym utrudnieniem w trakcie wykonywania testów są ograniczenia aplikacji w postaci używania pewnych funkcji w określonym czasie i dacie, np. ograniczenie tylko do bieżącej daty lub przetworzenie danych jedynie do dwóch dni wstecz itp. Nie zawsze istnieje możliwość manipulowania datami i czasem w testach. Takie okoliczności zmuszają do skrupulatnego planowania testu, tak aby nie stracić szansy na zweryfikowanie wyniku  np. inicjalizujemy jakiś proces, który zostanie automatycznie dokończony w nocy, a wynik prezentowany w raporcie  kolejnego dnia. Bywa, że w sposób bezpieczny można podrasować środowisko testów, tak aby pewne ograniczenia zdjąć. Niemniej z takich trików należy korzystać z najwyższą ostrożnością. Nadmierne uelastycznianie systemu na potrzeby testów może być zgubne, gdyż sztucznie poszerzone możliwości systemu po pewnym czasie mogą „wejść w krew” i zostać przez zespół testów traktowane jako normalna cecha aplikacji produkcyjnej. Wykonywanie testów „na skróty” niesie ryzyko przepuszczenia istotnych błędów, które miałyby szanse zostać wykryte w trakcie wykonywania „uczciwej” ścieżki.

60

Testowanie oprogramowania. Podręcznik dla początkujących

1.15. Jak wygląda realizacja projektu w praktyce? Najwięcej trudności w trakcie realizacji projektu IT nastręcza różnica w interpretacji i zrozumieniu oczekiwań biznesowych. Często się zdarza, że jeden zapis interpretowany jest zgoła odmiennie przez każdą z zainteresowanych stron.. Ciekawym zjawiskiem jest, że dla każdego jego interpretacja jest właściwa. W praktyce poszczególni zainteresowani, np. programista, tester, klient, mogą uznać, że wykonali pracę solidnie i zgodnie z oczekiwaniem, a finalnie wystąpią np. problemy z integracją aplikacji z innym systemem lub wdrożone rozwiązanie nie zaspokaja rzeczywistych potrzeb zamawiającego. Wszystko to teoretycznie zgodnie z zapisami! Bardzo lubię przy okazji powyższych rozważań przytaczać piktogramy udostępnione w projekcie How IT Projects Really Work (version 1.5) — Polish (http://www. projectcartoon.com/). Obrazują one w humorystyczny sposób, jak projekt jest rozumiany na poszczególnych etapach (cykl życia — rysunki od 1.21 do 1.24). Zachęcam do odwiedzenia tejże strony. Umożliwia ona stworzenie własnego zestawu grafik bez wnoszenia żadnych opłat.

Rysunek 1.21. How IT Projects Really Work (version 1.5) — Polish A

Rozdział 1.  Ogólna teoria testowania

Rysunek 1.22. How IT Projects Really Work (version 1.5) — Polish B

Rysunek 1.23. How IT Projects Really Work (version 1.5) — Polish C

61

62

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.24. How IT Projects Really Work (version 1.5) — Polish D

1.16. Testowanie w cyklu życia oprogramowania Cykl życia oprogramowania to okres od zmaterializowania się koncepcji jego powstania do momentu, kiedy zostanie ono wycofane z użytku. W zależności od etapu, na którym znajduje się oprogramowanie, nieco inaczej podchodzi się do problematyki testowania. W najprostszym ujęciu oprogramowanie podlega następującym fazom:  określenie wymagań,  projektowanie,  implementacja,  testy,  utrzymanie (pielęgnacja produkcyjna), ewolucja.

Określenie wymagań polega na zebraniu i zrozumieniu oczekiwań, na podstawie których powstanie projekt opisujący już konkretne rozwiązania. Projekt stanowi podstawę do wykonania implementacji i późniejszych testów funkcjonalnych. Pewną różnicę stanowi to, czy aplikacja jest pisana od podstaw, czy wnoszona zmiana jest jej ewolucją. Modyfikacje rozwojowe oprócz konieczności oceny jakości implementacji wymuszają często testy regresywne już istniejących obszarów aplikacji. Systemy, które nie są już rozwijane, a jedynie utrzymywane produkcyjnie, są testowane tylko w przypadku poprawek doraźnych (eliminujących błędy) oraz związanych testów regresywnych.

Rozdział 1.  Ogólna teoria testowania

63

Z uwagi na brak zmian rozwojowych zaangażowanie testerów w utrzymanie takich programów jest nikłe. Największej uwagi pod względem testów wymagają projekty powołujące nowe oprogramowanie oraz projekty rozwijające jeszcze niedojrzałe systemy, które podlegają nieustannemu rozwojowi. Kluczowy wpływ na jakość produkowanego oprogramowania ma obrany model jego wytwarzania. Model kaskadowy (rysunek 1.25) włącza testy w bardzo późnej fazie produkcji. Cechą charakterystyczną tego modelu jest sztywny podział procesu wytwórczego na bardzo hermetyczne etapy. Dopiero zakończenie jednej fazy umożliwia uruchomienie kolejnej, np. kończymy kodowanie, rozpoczynamy testy. Taka kolej rzeczy może okazać się problematyczna, gdyż faza testów, np. integracyjnych, może wykryć poważne problemy analityczne/projektowe, a zatem wymusi to ponowne zaangażowanie projektanta, który w tym czasie zapewne będzie pracował nad kolejnym zadaniem. W praktyce oznacza to ewentualną zmianę w projekcie, modyfikację dokumentacji i powtórne testy. Nie jest to efektowne i optymalne rozwiązanie. Rysunek 1.25. Model kaskadowy

Model V stwarza bardzo dogodne warunki do zapewnienia wysokiej jakości dostarczanego produktu dzięki wczesnemu włączeniu testowania w etap wytwórczy (rysunek 1.26). Więcej na temat modelu V można znaleźć na stronie http://www.v-modell-xt.de/. Wspomniany model umożliwia wczesny udział testerów w procesie wytwórczym. Już na etapie formułowania wymagań tester ma okazję wyrazić opinię, czy proponowane rozwiązanie jest możliwe do wykonania i czy obrana droga rokuje sukces. Weryfikacji podlegają już pierwsze fragmenty oprogramowania (moduły), później interfejsy, tzn. integracja komponentów w całość. Następnie aplikacja testowana jest w ujęciu całościowym. Wczesne testowanie umożliwia wychwycenie np. błędów projektowych, błędów w założeniu, które w kaskadowym modelu wytwarzania oprogramowania wykryte byłyby znacznie później. Przyjmijmy, że tester przed rozpoczęciem procesu

64

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 1.26. Model V

implementacji zgłosi szereg uwag merytorycznych do projektu, których część znajdzie uzasadnienie. Taki stan rzeczy przyniesie realny zysk, gdyż te same problemy (zgłoszone do projektu) zapewne byłyby podniesione już w stosunku do zmaterializowanego produktu. Zatem koszty ponownego procedowania (powrót do projektu i ewentualna modyfikacja kodu) byłyby znacznie wyższe niż wniesienie zmian na etapie projektowania. W tym podrozdziale chciałem zasygnalizować, że nie bez znaczenia dla jakości oprogramowania jest to, w jakim cyklu życia jest ono wytwarzane. Z punktu widzenia testera dobrze jest rozpoznać, w jakim modelu się uczestniczy, oraz znać jego mocne i słabe strony. Jestem przekonany, że w publikacjach dotyczących bezpośrednio inżynierii oprogramowania znajdą Państwo znacznie więcej informacji na temat cyklu życia i wytwarzania oprogramowania. Szczególnie rekomenduję zapoznanie się w pierwszej kolejności z Manifestem Zwinnego Wytwarzania Oprogramowania (ang. Agile Manifesto, Manifesto for Agile Software Development). Wspomniany manifest zawiera zasady, które powinny być przestrzegane w metodykach zwinnego wytwarzania oprogramowania. Strona domowa AGILE: http:// agilemanifesto.org/iso/pl/manifesto.html. Polecam zwłaszcza pogłębienie swojej wiedzy z zakresu metodyk:  Programowanie ekstremalne (ang. eXtreme Programming, XP).  Scrum – iteracyjna metoda prowadzenia projektów. Więcej informacji o Scrumie

można znaleźć pod tym adresem: https://www.scrum.org/scrum-guide.  Kanban.

Rozdział 2.

Poziomy wykonywania testów Proces wytwarzania oprogramowania podzielony jest na fazy, w których wykonuje się specyficzne dla każdego z etapów testy. Testy dzieli się na poziomy:  testy modułowe (jednostkowe),  testy integracyjne wewnętrzne,  testy systemowe,  testy integracyjne zewnętrzne,  testy akceptacyjne (odbiorcze).

Rysunek 2.1 przedstawia piramidę poziomu testów. Testy wykonywane są zgodnie z oddolną interpretacją infografiki, tj. od testów modułowych aż po testy akceptacyjne. Rysunek 2.1. Piramida poziomu testów

66

Testowanie oprogramowania. Podręcznik dla początkujących

2.1. Testy modułowe Testy modułowe wykonuje się na etapie wytwarzania kodu. Uczestnikami tego rodzaju testów są najczęściej programiści, którzy w fazie implementacji poddają weryfikacji własny kod. Wspomniane testy muszą odnosić się do niewielkich i ściśle wyizolowanych fragmentów kodu. Polegają one na wykonywaniu wybranego fragmentu instrukcji w celu zweryfikowania, czy realizuje ona swoją funkcję zgodnie z założeniami. Programista przygotował metodę, która konwertuje numer NRB (Numer Rachunku Bankowego) na IBAN (ang. International Bank Account Number — pol. Międzynarodowy Numer Rachunku Bankowego). Wspomniana metoda będzie wielokrotnie używana w różnych miejscach systemu. Test modułowy polegać będzie na wywoływaniu owej metody z podaniem jako parametru wejściowego numeru NRB i weryfikacji otrzymanego wyniku. Wywołanie metody może odbywać się z poziomu środowiska deweloperskiego bez zastosowania GUI. Na tym koniec. Testy modułowe powinny odnosić się do małych podmiotów, a ich wynik powinien być zależny od innych elementów, które potencjalnie mają znaleźć się w gotowej aplikacji. Testy modułowe nie powinny wnikać w szczegóły procesu biznesowego. Analizie i ocenie podlega jedynie mały i wyizolowany fragment kodu. W przypadku kiedy nabierzemy zaufania do testowanych fragmentów kodu, możemy rozpocząć składanie ich do postaci gotowego produktu lub większego komponentu. Testy modułowe wykonuje się zwykle w środowisku deweloperskim z dostępem do kodu źródłowego. Wymaga to od testera — o ile nie jest programistą — umiejętności czytania i wykonywania kodu oraz pisania własnych skryptów (fragmentów kodu). Bywa, że przetestowanie pewnego modułu wymaga zaangażowania elementów pobocznych, np. symulatora usługi sieciowej. Dlaczego należy wykonywać testy modułowe? Błędy wykryte we wczesnej fazie produkcji oprogramowania kosztują znacznie mniej niż poprawa oraz usuwanie ich skutków w kolejnych fazach wytwarzania lub użytkowania produkcyjnego aplikacji. Wykryte problemy usuwane są natychmiast, przez co minimalizuje się ryzyko propagacji negatywnego wpływu wadliwego kodu na pozostałe moduły np. poprzez odziedziczenie wady. Błędy wykryte w tej fazie zwykle nie znajdują odzwierciedlenia w postaci formalnego zgłoszenia, co przyspiesza udoskonalanie kodu i nie niesie ze sobą ryzyka krytycznych uwag osób trzecich. Jest to bardzo bezpieczna i efektywna forma testów własnego kodu. Kompilator nie weryfikuje intencji programisty. Zatem pomimo że kod został skompilowany, nie można go uznać za poprawny bez przeprowadzenia testu jednostkowego. Odłożenie procesu testowania do momentu złożenia w całość wszystkich elementów niesie ze sobą znaczne ryzyko, że nie uda się uruchomić aplikacji lub pewnych funkcjonalności za pierwszym razem. Błędy, które zostaną ujawnione, mogą mieć przyczynę głęboko ukrytą w kodzie. Przeszukiwanie „rozdmuchanych” źródeł w celu wyizolowania przyczyny problemu będzie czasochłonne i zapewne irytujące. Testy modułowe pozwalają na wyeliminowanie typowych problemów w podstawowej ścieżce obsługi systemu. Im mniej problemów zostanie przeniesionych z fazy implementacji do fazy formalnych testów, tym szybciej gotowy produkt zostanie przekazany do odbiorcy, a jego jakość wzrośnie.

Rozdział 2.  Poziomy wykonywania testów

67

2.2. Testy integracyjne U podstaw testów integracyjnych leży opieranie jednego procesu biznesowego na wielu różnych systemach, podsystemach i aplikacjach. Heterogeniczność ostatecznie oferowanego użytkownikowi końcowemu rozwiązania wymusza przeprowadzenie pełnego testu biznesu w oparciu o scalone środowisko. Aby było to możliwe, uprzednio należy zweryfikować interakcję pomiędzy poszczególnymi modułami. Zakończenie tych prac z oczekiwanym skutkiem stanowi podstawę do traktowania wszystkich modułów jako jeden system. Niezachwiane przekonanie o spójności systemu otwiera drogę do pełnych testów funkcjonalnych w całościowym ujęciu obsługi biznesu. Testy integracyjne to szczególny rodzaj działań podejmowanych w celu zbadania kooperacji oraz wzajemnego oddziaływania dwóch lub więcej modułów systemu. Przez desygnat „moduł” należy rozumieć zupełnie autonomiczny produkt lub fragment kodu, który ostatecznie jest ściśle spojony z główną architekturą systemu. Testy integracyjne mają wykazać:  czy moduły poprawnie współpracują, tj. czy nie wystąpiły przeszkody natury

technologicznej oraz czy wzajemnie świadczone usługi spełniają oczekiwania (logika);  jak zachowują się poszczególne elementy w sytuacji awarii, błędów

lub niestabilnej pracy w przypadku dysfunkcji jednego z nich (wzajemne oddziaływanie);  czy kojarzone wzajemnie elementy realizują założony proces biznesowy

(logika biznesu);  czy infrastruktura techniczna zapewnia optymalne warunki pracy

dla skomplikowanego systemu (wielomodułowego);  czy nie ma luk w logice biznesu, tj. czy nie ujawniły się problemy i/lub potrzeby,

które nie zostały przewidziane na etapie projektowania rozwiązania. Prace integracyjne rozpoczynają się już w momencie łączenia kodu dwóch lub więcej modułów tej samej aplikacji (integracja wewnętrzna). Komponenty te mogą być przygotowane przez zupełnie niezależnych programistów, a finalnie podlegają integracji. Wymienione elementy mogą w mniejszym lub większym stopniu ulegać integracji. Rolą testera jest zweryfikowanie relacji pomiędzy nimi. Zdarza się, że programista jest przekonany, iż integrowane elementy mają śladowy wpływ na pracę pozostałych fragmentów kodu. Niemniej jednak w ujęciu całościowym błąd w jednym z nich poważnie rzutuje na pracę całego systemu. Iluzoryczne przekonanie o nikłej zależności lub wręcz przezroczystości kodu integrowanego z dotychczasowymi funkcjonalnościami aplikacji bywa zgubne w skutkach. Omawiane prace należy podejmować jak najbliżej kodu, tj. na etapie prac programistycznych i testów wewnętrznych. Rysunek 2.2 przedstawia szkolny schemat blokowy modułów jednej aplikacji. Programista X przygotował blok B. Chcąc wykonać jego testy, musi zintegrować go z blokiem A lub zasymulować jego działanie. Programista Y wykonał blok A, który z założenia ma pracować z blokami B i C. Modułowe testy integracyjne polegają na łączeniu ze sobą fragmentów kodu, które

68

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 2.2. Schemat wzajemnego oddziaływania bloków jednej aplikacji

wchodzą w bezpośrednią interakcję. Programista lub tester uruchomi blok A i sprawdzi, czy to, co „wysyła” do elementu C, jest poprawne. Integrując bloki A, B i C, weryfikujemy ich wzajemną kooperację, mimo że nie realizują one pełnej funkcjonalności z punktu widzenia użytkownika końcowego. Kolejnym momentem w procesie produkcji oprogramowania, w którym wykonywane są testy integracyjne, jest zestawianie odrębnych modułów na etapie weryfikacji funkcjonalnej. Jest to arcyciekawa sytuacja z uwagi na to, że kod odpowiadający za odrębne fazy obsługi biznesu może być zrealizowany przez zupełnie niezależne podmioty. Zwykle jako podstawa do przygotowania „wtyczki” dla obcego systemu musi wystarczyć dokumentacja dostarczona przez potencjalnego integratora, tj. klienta, który będzie „spinał wszystko w całość”. Nadmieniony materiał może zawierać definicję plików wymiany danych (np. TXT, XML, CSV itp.), opis usługi sieciowej, np. WSDL, schemat udostępnionych obiektów na bazie danych w postaci perspektyw, przykłady wywołania upublicznionych procedur etc. Niezależnie od wybranego rozwiązania dostęp do niego bywa znacznie utrudniony lub wręcz niemożliwy. Oczywiście taka sytuacja już na etapie kodowania może być źródłem problemów, lecz prawdziwe trudności ujawniają się dopiero w momencie testów jakościowych. Rozważmy hipotetyczną sytuację w oparciu o rysunek 2.3. Pełny proces biznesowy wymaga zaangażowania trzech aplikacji. Testy integracyjne będą odbywały się na styku produktu C i B oraz B i A. Biznes (funkcjonalność) natomiast będzie weryfikowany przy użyciu wszystkich bloków A, B oraz C. Aplikacja C stanowi graficzny interfejs użytkownika, posiadający nikłą logikę biznesową, a zarazem bardzo rozbudowane możliwości prezentacji i obsługi danych. Główny mechanizm realizacji zadanego biznesu spoczywa na programie B. Niemniej jednak mogą wystąpić sytuacje, w których podsystem B będzie musiał posiłkować się wsparciem programu A. Reasumując, w realizację logiki mogą być zaangażowane bloki B oraz A. Natomiast aplikacja C jest inicjatorem przetwarzania, a zarazem konsumentem wyniku, tj. prezentacji rezultatu. Systemy o złożonej architekturze najczęściej poddawane są realnej próbie dopiero w środowisku testowym zamawiającego. Odbiorca oprogramowania ma przewagę nad wykonawcami poszczególnych komponentów w postaci nieskrępowanego dostępu do wszystkich elementów. Drugą kwestią jest posługiwanie się danymi bardzo zbliżonymi do rzeczywistych np. poprzez włączenie w proces testowania kopii bazy z produkcji. Powyższe zabiegi urealniają testy funkcjonalne (pełnego biznesu) poprzez posługiwanie się niemalże faktycznymi danymi zgromadzonymi w dużym wolumenie. Obsługa pełnego

Rozdział 2.  Poziomy wykonywania testów

69

Rysunek 2.3. Schemat interakcji trzech niezależnych aplikacji

procesu biznesowego w fazie prac integracyjnych lub ostatecznych testów odbiorczych może ujawnić luki w opracowanej koncepcji. Zdarza się, że w wyniku testów zidentyfikowane są problemy, które nie były przewidziane w projekcie. Problemy te mogą przełożyć się na zmianę rozwiązania. Dokładniej rzecz ujmując: na jego dopracowanie, na przykład poprzez dopisanie drobnych funkcjonalności, modyfikację założeń wstępnych etc. Testy integracyjne stanowią pierwszy etap weryfikacji, czy przyjęte rozwiązanie oraz jego implementacja zaspakajają potrzeby biznesowe (w całościowym ujęciu procesu). Pierwszym, a zarazem najmniej kłopotliwym scenariuszem jest integrowanie modułów (całych programów), które wykonane są przez jednego producenta. W teorii wskazane testy powinny być wykonane bez większych problemów, a ewentualne przeszkody w postaci błędów w implementacji lub niejasności w dokumentacji rozwiązane wewnątrz organizacji. Praktyka dowodzi, że niestety również ten scenariusz naznaczony jest pewnymi zakłóceniami w trakcie realizacji projektu testowego. Wynika to głównie z organizacji pracy oraz nie do końca jasnej i zdrowej rywalizacji pomiędzy zespołami wewnątrz korporacji. Wskazałem korporację, gdyż pojęcie klienta zewnętrznego oraz wewnętrznego jest szeroko stosowane w dużych przedsiębiorstwach. Pojęcie samo w sobie nie kryje niczego złego, choć zauważalne są problemy w relacjach pomiędzy zespołami wynikające z zawiłej, a bywa, że i naciąganej interpretacji tego terminu. Idealizując, można przyjąć, że funkcjonujące procedury formalne w pełni zabezpieczają potrzebę kooperacji różnych zespołów w celu osiągnięcia ostatecznego rezultatu. Nieco mniej przyjemną sytuacją jest nieposiadanie jednego lub więcej komponentów „na własność”, kiedy są one udostępnione grzecznościowo przez klienta. Jest to stosunkowo dobre rozwiązanie, chociaż uzależnione od dobrej woli współpracy. Pewną trudnością dla procesu integracji mogą być ewentualne niedociągnięcia w kodzie po drugiej stronie, ale właśnie po to się wykonuje testy integracji, aby te problemy wy-

70

Testowanie oprogramowania. Podręcznik dla początkujących

eliminować. Generalnie „my” czy partner po przeciwnej stronie musimy czekać na ewentualne modyfikacje kodu w celu kontynuowania procesu integracji. Niemniej jednak jest to dobra droga. Wymaga ona zaangażowania zasobów klienta jako pośrednika, lecz uzyskujemy wartość dodaną w postaci wczesnego rozminowania pola. Bujna wyobraźnia podpowiada, co może się stać, jeżeli podejmiemy próbę zintegrowania dwóch programów bez wcześniejszego „docierania” ich razem. Najtrudniejszą sytuacją jest konieczność wyprodukowania modułu klienckiego do obcego systemu bez dostępu do tego zasobu. Większość trudności ujawni się w momencie uruchamiania kodu i testów funkcjonalnych. Dużo zależy od roli, jaką dla testowanej funkcjonalności odgrywa zewnętrzny system. Bywa, że brak dostępu do drugiego programu w minimalnym stopniu ograniczy testy. Bywa również, że zupełnie je uniemożliwi. Załóżmy, że testowana funkcjonalność realizuje jakiś biznes zdefiniowany w 10 logicznych krokach, gdzie kroki 2., 5. oraz 8. wymagają połączenia z usługą sieciową w celu pobrania/przeliczenia jakichś danych. W takich okolicznościach przetestujemy tylko krok nr 1. Drugi już wymaga interakcji z web service (WS). W tym momencie wyklarowała się potrzeba zaślepienia wywoływanej metody WS przez naszą aplikację, tak aby mogła ona przejść do kroku nr 3 itd. Trzeba wyprodukować symulator systemu zewnętrznego. Temat zaślepiania żądań do zewnętrznych aplikacji będzie omówiony szerzej w dalszej części książki. Do typowych problemów (błędów) wykrywanych w procesie integracji należy zaliczyć:  Niespójność wysyłanego schematu XML z oczekiwaniami klienta lub serwera

np. w wyniku przygotowania klienta WS w oparciu o nieaktualny opis usługi (WSDL) lub błąd w kodzie.  Treść danych przekazywana w pliku wymiany danych zawiera znaki specjalne,

na które negatywnie reaguje druga aplikacja. Przykładowa nazwa jakiejś organizacji, fundacji może zawierać cudzysłów, który eksportowany jest do treści pola. Dokumentacja opisująca pliki wymiany może nie opisywać szczegółów lub wyjątków.  Zderzenie dwóch technologii lub nawet różnych wersji tych samych serwerów

może powodować problemy natury integracyjnej.  Bywają sytuacje, że w jakichś okolicznościach jedna ze stron będzie przekraczała

czas odpowiedzi na żądanie (ang. time out).  Wyobrazić można sobie sytuację, że obrót danymi odbywa się za pomocą pliku

wymiany danych, w którym koniec linii ustalony jest na znak LF, a w wyniku pośredniego działania jakiegoś programiku, który kopiuje plik z miejsca A do B, zajdzie niejawna konwersja znaku końca linii na CRLF. Próba odczytania takiego zbioru zakończy się niepowodzeniem. Czy taki scenariusz można przewidzieć w testach funkcjonalnych? W praktyce jedna aplikacja wygeneruje poprawnie LF, a druga poprawnie odrzuci znak CRLF. Gdzie jest błąd?! Może w jakimś „starym” programiku, którego nie braliśmy pod uwagę na etapie testów?  Testy integracyjne stanowią dobrą okazję do masowego wczytywania plików,

wymiany danych przez funkcje WS itp. Zdarza się, że błędy, np. przepełnienie bufora, przekroczenie zakresu zmiennej itp., ujawniają się dopiero przy „ogromnej” ilości danych.

Rozdział 2.  Poziomy wykonywania testów

71

 Integracja systemu ujawnia wszelkie różnice w rozumieniu funkcji, jakie ma

pełnić każdy z elementów, w tym po której stronie mają być obsługiwane określone wyjątki.  Luki w opracowanej koncepcji rozwiązania, na podstawie której była wykonana

implementacja.  Wiele innych problemów…

2.3. Testy systemowe Przedmiotem testów systemowych jest cała aplikacja lub jej samodzielny fragment, który znajduje odwzorowanie w projekcie, tj. wchodzi w zakres projektu. Najodpowiedniejszą formą testów funkcjonalnych jest zastosowanie techniki czarnoskrzynkowej. Niemniej jednak technika białej skrzynki z powodzeniem może być wykorzystywana jako uzupełnienie głównego wątku testów. Kluczem do sukcesu jest prowadzenie testów w środowisku testowym jak najbardziej zbliżonym do produkcyjnych warunków funkcjonowania aplikacji. Weryfikacja aplikacji w warunkach możliwie wiernie odwzorowujących docelowe środowisko pracy zmniejsza ryzyko przeoczenia błędów i problemów, które mogą wynikać z różnic w specyfice obu środowisk. Więcej informacji na temat środowiska testów znajduje się w rozdziale poświęconym owej tematyce w niniejszej książce. Równie istotne jest to, kto wykonuje testy systemowe. Najlepiej byłoby, aby czynił to niezależny zespół testerów, tzn. taki, który zachowuje dużą autonomiczność wobec zespołu programistycznego w aspekcie środowiska testów oraz zasobów ludzkich. Testy systemowe (ang. system testing) powinny ujmować wymagania zarówno niefunkcjonalne, jak i funkcjonalne. Jest to faza, w której ocenia się globalnie produkt bez nadmiernego nacisku na zgłębianie wewnętrznej architektury i instrukcji kodu aplikacji. Testy systemowe odnoszą się do aplikacji w ujęciu całościowym. Oznacza to, że zobowiązani jesteśmy do weryfikacji wszystkich funkcji wraz z analizą korelacji pomiędzy nimi. Załóżmy, że otrzymaliśmy do testów aplikację lub nowy moduł, który administruje fakturami VAT. Testy systemowe wymagają zweryfikowania wszystkich ścieżek obsługi owego dokumentu, m.in. wystawienia FV, korekty, wydruku, filtrowania, generowania do pliku PDF, akceptacji etc. Tego rodzaju testy mogą ujawnić potrzebę zastosowania zaślepek lub symulatorów zewnętrznych systemów, z którymi nasza aplikacja będzie wchodzić we współzależność lub w których będzie występować jedynie jako klient. Specyfika i zakres testów systemowych stawiają ten rodzaj weryfikacji w roli bardzo dobrego kandydata do automatyzacji czynności (testów). Testy systemowe to:  testy funkcjonalne,  testy wydajnościowe,

72

Testowanie oprogramowania. Podręcznik dla początkujących  testy regresywne,  testy ergonomii,  testy instalacji,  testy bezpieczeństwa.

2.4. Testy akceptacyjne Testy odbiorcze wykonywane są bezpośrednio przez zamawiającego w oparciu o własne zasoby lub poprzez zlecenie prac niezależnemu zespołowi testerskiemu. Testy te mają potwierdzić zgodność weryfikowanego produktu z zapisami w umowie i obowiązującymi przepisami prawa. Celem testów akceptacyjnych jest weryfikacja i potwierdzenie, czy wszystkie zapisy w kontrakcie zostały zrealizowane w sposób zaspokajający oczekiwania. Pozytywne zamknięcie fazy testów odbiorczych stanowi podstawę do finansowego rozliczenia kontraktu. Testy akceptacyjne powinny odnosić się do formalnie spisanych kryteriów odbioru oprogramowania. Wspomniane kryteria zwykle uzgadniane są na etapie negocjacji kontraktu. Opisywana tu sytuacja odnosi się do oprogramowania pisanego na zamówienie. Nieco inaczej wygląda sytuacja w przypadku aplikacji „pudełkowych”. Oprogramowanie z półki może być poddane dwóm typom testów akceptacyjnych: alfa i beta. Testy alfa są wykonywane wewnątrz organizacji, która wyprodukowała oprogramowanie, choć weryfikacji dokonuje niezależny zespół, tj. zespół, który nie brał udziału w procesie wytwarzania. Testy beta realizowane są poza organizacją wykonującą kod przez grupę użytkowników docelowych. Firmy produkujące oprogramowanie pudełkowe są żywo zainteresowane uzyskaniem informacji zwrotnej (potwierdzeniem) o wysokiej jakości własnego produktu przed oficjalnym wprowadzeniem go na rynek. Niezależnie od typu oprogramowania (pudełkowe, na zamówienie) powinno ono również być poddane testom akceptacyjnym w aspekcie obowiązujących przepisów prawa.

Rozdział 3.

Typy testów Testy dzieli się według przyczyny ich wykonywania. W celu zweryfikowania poziomu bezpieczeństwa lub wydajności realizuje się testy niefunkcjonalne. Ocenę funkcji opiera się na testach funkcjonalnych. W sytuacji kiedy chcemy potwierdzić, że wprowadzona modyfikacja nie wpłynęła negatywnie na pozostałe obszary systemu, wykonuje się testy regresywne. W tym rozdziale przedstawię podział testów według celu ich wykonywania. Dodatkowo dla testów niefunkcjonalnych zrealizujemy kilka przykładów praktycznych.

3.1. Testy funkcjonalne Testy funkcjonalne, jak sama nazwa wskazuje, odnoszą się bezpośrednio do funkcji, jakie realizuje system lub jego moduł. Podstawą dla tego typu prac jest specyfikacja funkcjonalna, której zapisy mówią o możliwościach, cechach i właściwościach systemu. Niestety może ona (dokumentacja) nie odnosić się w pełni do ostatecznej implementacji, tj. pewne funkcje i zachowania mogą być nieudokumentowane. Wspomniany stan rzeczy może wynikać z kilku powodów:  z jakichś przyczyn nie zadbaliśmy o pełną i aktualną dokumentację;  dokumentacja jest mało precyzyjna i niskiej wartości merytorycznej;  w fazie implementacji i wstępnych testów ujawniła się konieczność rozbudowy,

poszerzenia zakresu prac o aspekty, które nie zostały jeszcze ujęte w projekcie (brak aktualizacji);  pewne cechy i właściwości systemu mogą być wykonywane poprzez analogię

do poprzednich funkcji lub na bazie „zwyczajowości”. Zważywszy na powyższe zagrożenie, tester musi umieć radzić sobie w sytuacji niedostępności pełnej i szczegółowej dokumentacji. Istotą testów funkcjonalnych jest zrozumienie wymagań, jakie ma spełniać finalny produkt.

74

Testowanie oprogramowania. Podręcznik dla początkujących

Testy funkcjonalne odpowiadają na pytanie: co robi system (umożliwia doładowanie karty telefonu, wyświetla historię operacji, generuje potwierdzenie przelewu etc.), w odróżnieniu do testów niefunkcjonalnych, które poszukują odpowiedzi na pytanie: jak działa system (jak szybko przygotuje billing dla 50 000 połączeń, jak obsłuży 20 000 użytkowników jednocześnie itp.). Przyjmijmy, że serwis ogłoszeń internetowych ma zostać rozbudowany o filtr ułatwiający przeszukiwanie pozycji. Filtr ma umożliwiać wybieranie danych według następujących kryteriów:  województwo, z którego pochodzi ogłoszenie;  data zamieszczenia;  rodzaj ogłoszenia (sprzedaż/kupno).

Wykonując testy funkcjonalne, szukamy potwierdzenia, czy system robi to, czego od niego oczekujemy (odpowiedź „co robi”, czy umożliwia przeszukiwanie zasobów według założenia). Natomiast testy niefunkcjonalne będą polegać na sprawdzeniu działania filtra przy dużym zakresie danych np. 300 000 ogłoszeń. Sprawdzać będziemy, „jak” zachowuje się opcja przeszukiwania dla dużego wolumenu danych. Markerem może być czas odpowiedzi dla pojedynczego zapytania. Możemy również poddać próbie mechanizm dla 50 równolegle wysyłanych żądań przeszukania danych.

3.2. Testy niefunkcjonalne Testy niefunkcjonalne (ang. non-functional), jak sama nazwa wskazuje, nie odnoszą się bezpośrednio do funkcjonalności produktu. Tego rodzaju testy mają na celu ustalenie charakterystyki oprogramowania, tj. zachowania systemu w określonych okolicznościach. W skład podstawowych testów właściwości systemu wchodzą:  testy wydajnościowe, obciążeniowe i przeciążeniowe;  testy bezpieczeństwa;  testy ergonomii;  testy przenośności kodu (instalacji).

3.2.1. Testy wydajności Badanie wydajności systemu ma dać odpowiedź, jak system zachowuje się przy określonym obciążeniu. Tego rodzaju testy należą do grupy niefunkcjonalnych i są szczególne w całościowym ujęciu terminu testowania oprogramowania. Wyniki pomiaru systemu służą do zbudowania charakterystyki oprogramowania, np. zależności pomiędzy zachowaniem systemu a jednoczesną liczbą użytkowników. Uzyskany wykres przedstawiający te relacje powinien być porównany z oczekiwanym (założonym) rezultatem.

Rozdział 3.  Typy testów

75

Przez takie porównanie uzyskamy informację, czy system spełnia oczekiwania. W grupie wspomnianych oczekiwań należy wymienić weryfikację, czy:  system działa stabilnie dla założonej maksymalnej grupy użytkowników;  czasy odpowiedzi są na akceptowalnym poziomie dla normalnego trybu pracy,

a tym bardziej nie przekraczają przewidzianego limitu czasu dla operacji (ang. time-out);  logika biznesowa spełnia swoją rolę;  system „nie gubi” danych przy dużych obciążeniach;  w razie przeciążenia system uruchomi procedury awaryjne.

Uzyskanie informacji o rzeczywistych parametrach wydajnościowych systemu otwiera drogę do konstruktywnego planowania dalszego rozwoju oprogramowania. W przypadku kiedy wyniki będą niezadowalające, zainicjowana zostanie procedura naprawcza. Niedopuszczalne są zaniedbania polegające na zaniechaniu testów wydajnościowych. Podmiot odpowiedzialny (najczęściej właściciel) powinien znać możliwości systemu, jego słabe i mocne strony i mieć przygotowane scenariusze na wypadek ewentualnych problemów. Pewnym novum dla testerów może okazać się fakt, że istnieje prawdopodobieństwo ujawnienia się błędów w logice przy dużym obciążeniu, pomimo iż przewidziane testy funkcjonalne zakończyły się sukcesem. Wyróżnia się trzy podstawowe typy testów:  testy wydajnościowe (ang. performance testing),  testy obciążeniowe (ang. load testing),  testy przeciążeniowe (ang. stress testing).

Testy obciążeniowe polegają na symulacji obciążenia systemu dużą — zbliżoną do maksymalnej — liczbą użytkowników przez zadany zakres czasu (utrzymywanie obciążenia) przy realizacji wybranego scenariusza testów, np. realizowania transakcji. Wymuszenie, aby system funkcjonował na pograniczu założonej maksymalnej liczby sesji, służy do zmierzenia czasu odpowiedzi na pojedyncze żądanie. Oprócz liczby jednoczesnych użytkowników dla testów obciążeniowych istotne jest, jakie obszary systemu analizujemy. Inaczej będzie reagował system dla 1000 sesji, w których jedynie pobieramy treść statyczną, a inaczej, kiedy tyleż samo klientów będzie realizowało poprzez aplikację internetową transakcje na bazie danych lub wykonywało import dużej ilości danych. Planując testy, należy opracować scenariusze tak, aby obowiązkowo zweryfikowały najbardziej newralgiczne miejsca aplikacji. Testy obciążeniowe mają za zadanie potwierdzić, czy system sprosta stawianym mu wymogom pod względem wydajności. Testy przeciążeniowe wykonuje się w celu zbadania, jak aplikacja zachowuje się w momencie przekroczenia jej dopuszczanego obciążenia. Taka sytuacja może się zdarzyć, zatem kluczowa dla podmiotu jest informacja, jak system reaguje na takie zdarzenie. W przypadku przeciążenia system powinien pracować w trybie awaryjnym, tj. w taki sposób, jak to zostało założone. Przeciążenia systemu można dokonać poprzez zbyt dużą liczbę użytkowników, hurtową ilość danych (np. masowe zasilenia z plików) i/lub ograniczenie (konsumpcję) zasobów systemowych.

76

Testowanie oprogramowania. Podręcznik dla początkujących

Celem testów przeciążeniowych jest ustalenie:  czy aplikacja zareagowała zgodnie z założeniami;  czy przeciążenie systemu nie wpływa na bezpieczeństwo;  czy poprawnie działa procedura powrotu do normalnego trybu pracy;  czy nie nastąpiła utrata danych lub ich spójności w wyniku przejścia w stan

awaryjny;  czy w wyniku przeciążenia nie ujawniły się dodatkowe błędy.

Testy wydajnościowe mają potwierdzić, że system zachowa się poprawnie w sytuacji niskiego i znacznego obciążenia przez użytkowników. Kluczowe funkcje aplikacji powinny być wykonywane w akceptowalnym czasie. Tego typu testy mają dać odpowiedź, jak wzrost liczby jednocześnie obsługiwanych użytkowników wpływa na czas przetwarzania żądania. W odróżnieniu od testów obciążeniowych nie jest wymagane długotrwałe utrzymywanie wysokiego obciążenia systemu. Istotą testów wydajnościowych jest zbadanie czasu odpowiedzi (przetwarzania) dla poszczególnych funkcji w różnych okolicznościach (przy akceptowalnym obciążeniu). Często zdarza się, że większość funkcji serwisu działa sprawnie, z wyjątkiem jednego lub dwóch miejsc, gdzie następuje tzw. przytkanie, czyli wąskie gardło. Namierzenie tego miejsca pozwoli na eliminację potencjalnego zagrożenia i wzrost wydajności całej aplikacji. Przeprowadzenie testów wydajnościowych wymaga od testera dużego doświadczenia, znajomości systemu, a czasami kunsztu w tej materii. Tego rodzaju prace są specyficzne, a zebrane podczas nich doświadczenie stanowi cenne know-how testera. W celu praktycznego wprowadzenia do testów posłużymy się narzędziem Apache JMeter. JMeter to program napisany w języku Java, który umożliwia symulowanie obciążenia aplikacji internetowej lub API (ang. Application Programming Interface) oraz mierzenie ich wydajności. Narzędzie można pobrać ze strony domowej projektu: http:// jmeter.apache.org/. Do najważniejszych opcji programu należy zaliczyć możliwość testowania:  baz danych przez JDBC Driver;  SOAP, HTTP i HTTPS;  SMTP, POP3 oraz IMAP.

Szczegółowa instrukcja wraz z wymaganiami instalacji dostępna jest na stronie internetowej dostawcy programu. Po uruchomieniu narzędzia dostępny jest GUI (rysunek 3.1). Testowanie bazy danych Zdarza się, że w projekcie technicznym przy opisie perspektywy lub tabeli projektant zastrzega, że wszelkie przeszukiwanie musi być wykonywane obligatoryjnie z klauzurą WHERE. Ma to bezpośredni związek z wydajnością oraz liczbą danych, jaka jest przechowywana/prezentowana w wymienionych obiektach. Zatem tester powinien się pokusić o sprawdzenie, jak będzie reagowała baza danych przy masowym wykonywaniu zaprojektowanej instrukcji SQL. W celu poznania narzędzia JMeter posłużymy się trzema przykładami. Pierwszy z instrukcją INSERT INTO, drugi z warunkiem WHERE, a trzeci z wywołaniem procedury składowanej w bazie danych.

Rozdział 3.  Typy testów

77

Rysunek 3.1. Apache JMeter 2.9 — GUI

Apache JMeter napisany jest w Javie, a zatem w celu połączenia się z bazą danych należy użyć biblioteki sterowników JDBC. Odpowiednia biblioteka JAR musi zostać skopiowana do katalogu JMETER_HOME/lib lub JMETER_HOME/lib/ext. Sterownik JDBC musi być odpowiedni dla bazy danych, z którą chcemy nawiązać połączenie. W przypadku braku sterownika lub innych problemów z importem plików class JMeter odnotuje błąd w logu (rysunek 3.2).

Rysunek 3.2. JMeter — błąd sterownika JDBC

78

Testowanie oprogramowania. Podręcznik dla początkujących

Pierwszym krokiem jest zmiana domyślnej nazwy projektu na bardziej przystającą do planowanych czynności. W drugiej kolejności należy utworzyć nową grupę wątków, w której zdefiniujemy liczbę użytkowników, jaką będziemy operować (rysunek 3.3, rysunek 3.4). Rysunek 3.3. JMeter — zakładanie nowej grupy wątków (menu)

Rysunek 3.4. JMeter — formatka nowej grupy wątków

Najważniejsze pola formularza Grupy wątków to:  Number of Threads (users) — definiujemy, ilu użytkowników (ile wątków)

ma uczestniczyć w teście.  Ramp-Up Period (in seconds) — określamy, w jakim czasie mają zostać

„wystartowani” wszyscy użytkownicy, tj. jeżeli mamy 5 wątków i podamy czas 5 sekund, to JMeter „wystartuje” pojedynczego użytkownika co 1 sekundę (5 użytkowników na 5 sekund = 1 użytkownik na sekundę). Natomiast jeżeli podamy wartość 0, to wszyscy użytkownicy zostaną „wypuszczeni” niezwłocznie. W ten sposób można sterować obciążeniem serwera (aplikacji).  Loop Count — w polu określamy, ile razy ma zostać powtórzony test (liczbę pętli).

Rozdział 3.  Typy testów

79

Następnie należy przygotować element konfiguracyjny sterownika JDBC (rysunek 3.5). Na formularzu (rysunek 3.6) konfiguracji sterownika należy skonfigurować dane połączenia bazy danych (URL, port, nazwa bazy), wskazać plik class sterownika JDBC pasujący do naszej bazy danych, uzupełnić hasło i login użytkownika do bazy danych. Wymieniony rysunek prezentuje konfigurację dla serwera MySQL. Tabela 3.1 stanowi zestawienie parametrów konfiguracyjnych dla innych popularnych serwerów baz danych. Rysunek 3.5. JMeter — dodawanie elementu konfiguracji JDBC (menu)

Rysunek 3.6. JMeter — formatka konfiguracji połączenia JDBC

80

Testowanie oprogramowania. Podręcznik dla początkujących

Tabela 3.1. Zestawienie parametrów konfiguracyjnych JDBC Baza danych

Driver Class

Database URL

PostgreSQL

org.postgresql.Driver

jdbc:postgresql:{dbname}

Oracle

oracle.jdbc.OracleDriver

jdbc:oracle:thin:@//host:port/service LUB jdbc:oracle:thin:@(description=(address= (host={mc-name})(protocol=tcp)(port= {port-no}))(connect_data=(sid={sid})))

com.microsoft.sqlserver. SQL Server (MS JDBC driver) jdbc.SQLServerDriver

jdbc:sqlserver://host:port;DatabaseName=dbname

Po przygotowaniu komponentu sterującego połączeniem z bazą danych można przystąpić do definiowania instrukcji SQL (rysunek 3.7), którą będziemy wykorzystywać do testowania bazy danych.

Rysunek 3.7. JMeter — wywołanie opcji konfiguracji zapytania SQL

Instrukcja INSERT INTO Załóżmy, że aplikacja okresowo będzie musiała sobie poradzić ze zwiększonym natężeniem importu dużej ilości danych przez wielu użytkowników. Taką sytuację możemy zasymulować poprzez odpowiednie wysterowanie liczby żądań (formatka grupy wątków) oraz zdefiniowanie interesującej nas instrukcji SQL. Na potrzeby naszych rozważań będzie to bardzo prosta instrukcja wstawiająca przykładowe dane do tabeli.

Rozdział 3.  Typy testów

81

Rysunek 3.8 obrazuje strukturę tabeli, jaką będziemy wykorzystywać do symulacji testu.

Rysunek 3.8. Struktura tabeli wykorzystana do testu za pomocą narzędzia JMeter

Tabela jest bardzo prosta. Pierwsza kolumna jest uzupełniana automatycznie przez serwer bazy danych i stanowi klucz główny, kolejne dwie kolumny będziemy zasilać poprzez akcje z JMeter, ostatnia pozycja to kolumna techniczna, która będzie przechowywała stempel czasu (może się przydać do analizy wydajności). Przystąpmy do przygotowania zadania typu JDBC Request. Rysunek 3.9 przedstawia formularz, na którym zdefiniowana została instrukcja insert into tb_jmeter(imie,nazwisko) values(?,?);. Znaki „?” zostaną zastąpione wartościami pobranymi z pola Parameter values tegoż formularza. Przyglądając się uważnie, zauważymy, że wykorzystałem dostępne funkcje w JMeter do określenia wartości parametrów. Funkcja ${__Random String(10, doLOSOWANIA)} wylosuje dziesięcioznakowy ciąg, a funkcja ${__sampler Name} zwróci nazwę zadania. Narzędzie „wyposażone” jest obficie w różnorodne funkcje do ustawiania wartości parametrów, w tym ładowanie ich z plików zewnętrznych. Wszystkich zainteresowanych odsyłam do szczegółowej instrukcji programu opublikowanej na stronie producenta (http://jmeter.apache.org/usermanual/functions.html).

Rysunek 3.9. JMeter JDBC Request — przygotowanie instrukcji SQL

82

Testowanie oprogramowania. Podręcznik dla początkujących

Do analizy niezbędne będą wyniki z wykonanej akcji. W tym celu zdefiniujemy „słuchaczy”: drzewo odpowiedzi oraz graficzny wykres (rysunek 3.10).

Rysunek 3.10. JMeter — wybór opcji konfiguracji „słuchaczy”

Tak przygotowany projekt uruchamiamy (Run/Start). Po zakończeniu akcji należy odwołać się do podanych wyżej słuchaczy, tj. obejrzeć rezultat przetwarzania. Rysunki 3.11 i 3.12 prezentują odpowiednio drzewo odpowiedzi oraz graficzny wykres wyniku. Po ocenie i analizie rezultatów można się odwołać bezpośrednio do tabeli z bazy danych. Sprawdźmy, jaki skutek przyniosły powyższe zadania. Rysunki 3.13 oraz 3.14 pokazują stan bazy po przeprowadzeniu powyższego testu. Patrząc na wartości timestamp, widzi się, że baza danych poradziła sobie całkiem dobrze. Warunki wykonania nie były jednak szczególnie trudne: 50 użytkowników, 5 sekund czasu uwalniania, 10 powtórzeń pętli.

Rozdział 3.  Typy testów

Rysunek 3.11. JMeter — drzewo wyników wykonania instrukcji SQL

Rysunek 3.12. JMeter — wykres czasu wykonywania instrukcji SQL

83

84

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.13. Stan bazy po wykonaniu testu — liczba wierszy

Rysunek 3.14. Stan bazy po wykonaniu testu

Instrukcja z warunkiem WHERE Do naszego projektu testowego dodajmy nowe zadanie, które będzie wykonywało zapytanie na tabeli tb_jmeter (rysunek 3.15). Test będzie polegał na przeszukiwaniu tabeli z uwzględnieniem warunku where i przy równoczesnym wypełnianiu jej nowymi danymi (zadanie JDBC Request — Insert). Warunki przetwarzania to: 70 użytkowników, 5 sekund czas wypuszczania, 100 powtórzeń. Rysunek 3.16 przedstawia czas wykonania pojedynczego zapytania. Rysunek 3.17 ujmuje zrzut z wynikiem wykonania pojedynczego zadania, ale w warunkach obciążenia bazy opisanych powyżej. Pierwsze zapytanie wykonało się w 30 milisekund, drugie w 49 milisekund. Różnica w czasie jest zauważalna. Widać, że przyjęty scenariusz obciążył system, co wpłynęło na wydłużenie czasu odpowiedzi. Wywoływanie procedury Bazy danych pozwalają na realizowanie logiki systemu poprzez kod, który przechowywany jest w postaci pakietów, funkcji i procedur. Doskonałym przykładem takich możliwości jest Oracle Database z proceduralnym SQL (PL/SQL). Należy więc zająć także masowym obciążaniem wymienionych powyżej obiektów. Na potrzeby naszych rozważań powołałem bardzo prostą procedurę (rysunek 3.18), którą będziemy wielokrotnie wywoływać za pomocą narzędzia JMeter (rysunek 3.19). Rysunek 3.20 pokazuje wynik przeprowadzonego testu. Przytoczony przykład jest niezwykle prosty. Logika procedury również nie jest efektowna, jednak materiał, którym się posłużyliśmy, wystarcza do zobrazowania możliwości narzędzia Apache JMeter.

Rozdział 3.  Typy testów

Rysunek 3.15. JMeter — konfiguracja nowej instrukcji SQL Rysunek 3.16. Czas wykonania pojedynczego zapytania bezpośrednio na bazie MySQL

Rysunek 3.17. JMeter — czas wykonania pojedynczego zapytania w warunkach obciążenia bazy

85

86

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.18. Budowa procedury oraz jej wywołanie z wiersza poleceń

Rysunek 3.19. JMeter — wywołanie procedury

Rysunek 3.20. JMeter — wynik przeprowadzonego testu, wielokrotne wywołanie procedury

Testowanie aplikacji internetowej Testowanie aplikacji internetowej pod względem wydajności wymaga uważnego zaprojektowania testu i przełożenia przyjętej koncepcji na zapis akcji w programie JMeter. Narzędzie umożliwia wykonywanie testów na pojedynczym i wyselekcjonowanym obiekcie, np. servlecie, lub wykonanie analizy w oparciu o pełną ścieżkę, np. począwszy od logowania, poprzez wykonanie transakcji i wylogowanie się z serwisu.

Rozdział 3.  Typy testów

87

Rozpatrzmy podstawowy test bez autoryzacji użytkownika. Standardowo tworzymy nowy projekt oraz zakładamy grupę wątków. Z opcji w menu ADD/Config Element wybieramy HTTP Request Defaults (rysunek 3.21). Zatrzymując się dłużej w miejscu wyboru elementu konfiguracyjnego, można dostrzec, że JMeter oferuje kilka opcji dla protokołu HTTP (w tym autoryzację i pliki cookie). Rysunek 3.22 prezentuje formatkę konfiguracyjną badanego serwisu. Rysunek 3.21. JMeter — menu, dodawanie elementu konfiguracji dla HTTP

Rysunek 3.22. JMeter — formularz konfiguracji testu dla aplikacji internetowej

88

Testowanie oprogramowania. Podręcznik dla początkujących

W kolejnym kroku definiujemy żądania, jakie mają być zrealizowane podczas wykonywania testu. W naszym przykładzie podamy dwa. Pierwsze z nich wywoływać będzie stronę główną aplikacji, a drugie będzie wywoływać servlet z podanymi parametrami. Posłużymy się testowym servletem TestServlet.do, który przyjmuje trzy parametry. Nazwy i wartości tych parametrów należy określić na formularzu HTTP Request (rysunek 3.23).

Rysunek 3.23. JMeter — konfiguracja żądania do aplikacji internetowej

JMeter powinien „wypuścić” żądanie analogiczne do poniższego: localhost:8080/TestBook/ TestServlet.do?nrRach=102030405000009&nrTelefonu500-500-500=&kwotaDoladowania=99,99 Chcąc zweryfikować i jednocześnie uwiarygodnić skuteczność oraz powodzenie testu, posłużyłem się dodatkowym logowaniem na konsolę zdarzeń, jakie obsługuje aplikacja. Rysunek 3.24 prezentuje fragment logu. Wynika z niego, że aplikacja odebrała żądanie, prezentowane są wartości parametrów, jakie zostały przekazane, oraz odnotowane jest otwarcie i zamknięcie połączenia z bazą danych. Zapis logu potwierdza, że aplikacja odebrała identyczne wartości parametrów co zdefiniowane w samplerze JMeter. Rysunek 3.25 przedstawia graficzny wynik wykonania testu. Rysunek 3.24. JMeter test — zapis logu testowanej aplikacji internetowej

Rozdział 3.  Typy testów

89

Rysunek 3.25. JMeter — graficzna prezentacja wyniku testu aplikacji internetowej

Testowanie usług sieciowych Testowanie wydajności usług sieciowych odbywa się w analogiczny sposób jak testy bazy danych czy aplikacji internetowej. Przygotowanie projektu należy rozpocząć od zdefiniowania grupy wątków. W kolejnym kroku dodajemy sampler WebService(SOAP) Request (DEPRECATED). Rysunek 3.26 przedstawia formatkę, na której definiujemy parametry testu. W polu WSDL URL należy podać URL do pliku WSDL opisującego usługę. Najbezpieczniej jest wpisać adres punktu końcowego i za pomocą dopisku ?WSDL pobrać plik bezpośrednio z serwera. W oparciu o prawidłowo zaimportowany plik WSDL JMeter automatycznie uzupełni listę dostępnych metod (pole Web Methods). Po wybraniu interesującej nas metody przyciskiem Configure przeniesiemy niezbędną konfigurację do pozostałych pól. Ostatnim krokiem jest zdefiniowanie wiadomości XML, którą chcemy wysyłać w trakcie wykonywania testów (pole WebService message). Oczywiście poza merytorycznym sformułowaniem testu należy umożliwić zebranie statystyk z przebiegu procesu, tj. dodać listenery. Rysunek 3.27 prezentuje drzewo wyników z przeprowadzonego testu usługi sieciowej.

90

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.26. JMeter — testy WebService

Rysunek 3.27. JMeter — testy usługi sieciowej, wyniki przetwarzania

Rozdział 3.  Typy testów

91

3.2.2. Testy bezpieczeństwa aplikacji Testy bezpieczeństwa wykonuje się w celu zweryfikowania i ustalenia odporności systemu na niepożądane, choć zamierzone działania. Celem inicjacji owych zabiegów może być próba ingerencji w dane (modyfikacja, usunięcie), destabilizacja i unieruchomienie systemu, uzyskanie poufnych informacji etc. Bardzo niebezpiecznymi działaniami są czynności, wskutek których system może zmienić swoje zachowanie. Luki w oprogramowaniu o takim charakterze powinny być usuwane z najwyższym priorytetem. System powinien być na tyle hermetyczny, aby nikt w sposób niekontrolowany nie mógł wymusić zmiany w obsługiwanym procesie. Wszelkie upublicznione incydenty lub zdarzenia, które bezpośrednio dotknęły użytkowników, podważą zaufanie do oprogramowania oraz jego producenta. Uzyskanie 100-procentowego poziomu bezpieczeństwa jest oczywiście niemożliwe, jednak należy dołożyć wszelkich starań, aby współczynnik kosztów ataku w stosunku do potencjalnych zysków był jak najwyższy lub wręcz nieopłacalny. Niepodważalnie najsłabszym ogniwem polityki bezpieczeństwa jest człowiek. Z tego powodu najczęściej stosowane są ataki oparte na socjotechnice. Niemniej jednak nie można zapominać o kodzie aplikacji, w którym mogą ujawnić się luki mające zasadniczy wpływ na obniżenie bezpieczeństwa systemu. Tester powinien umieć zweryfikować aplikację pod kątem występowania typowych błędów naruszających bezpieczeństwo. Aplikacje internetowe szerokim frontem wkraczają niemalże do każdej dziedziny życia, dlatego też dalsze rozważania o bezpieczeństwie systemów zostaną oparte na praktycznych przykładach. Podstawowe testy bezpieczeństwa powinny pomóc w ustaleniu odporności systemu na:  zdradzanie nadmiarowych danych, np. nazwy i wersji serwera aplikacji;  wywoływanie błędów HTTP 300, HTTP 400 w celu uzyskania informacji

o strukturze katalogów serwera WWW;  nawiązywanie równoległych połączeń przy użyciu tych samych danych

użytkownika, np. login/hasło;  ataki typu SQL Injection;  ataki typu XSS (ang. Cross Site Scripting) — osadzenie obcego skryptu;  przekazywanie wrażliwych informacji w adresie URL;  podmienianie wartości parametrów przekazywanych w adresie URL;  modyfikacje treści strony w aplikacji internetowej;  wymuszanie kodów błędów (odpowiedzi) HTTP 500 (wewnętrzny błąd serwera);  wysyłanie masowo wiadomości za pomocą automatów przy wykorzystaniu

funkcjonalności strony, np. formularza kontaktu;  próby obejścia założeń ujętych w wymaganiach, jak np. blokada konta po trzech

nieudanych próbach logowania etc.

92

Testowanie oprogramowania. Podręcznik dla początkujących

Testy bezpieczeństwa ICT (ang. Information and Communication Technologies) nie powinny być utożsamiane z audytem bezpieczeństwa infrastruktury informatycznej, tj. sieci teleinformatycznej i serwerów, na których osadzona jest aplikacja biznesowa. Dużą rolę odgrywa również jakość kodu, którego wykonanie determinuje określone zachowanie systemu. Każda aplikacja potencjalnie może zawierać wady, luki, które po ujawnieniu mogą zostać wykorzystane do wymuszenia niepożądanego zachowania systemu. W tym rozdziale zostały przytoczone jedynie elementarne aspekty bezpieczeństwa aplikacji internetowych. Niemniej jednak po przestudiowaniu tego materiału z pewnością można wkroczyć na ścieżkę dalszego samokształcenia w materii bezpieczeństwa systemów informatycznych. W niniejszym rozdziale zostaną przytoczone i omówione przykłady pozwalające zrozumieć istotę problemu. Są to rozważania na poziomie akademickim i trudno doszukiwać się w tej książce szczegółowego omówienia wyrafinowanych i skutecznych ataków. Kończąc czytać ostatnie frazy niniejszego rozdziału, powinieneś wiedzieć, Drogi Czytelniku, jakie są podstawowe typy ataków oraz w jaki sposób zweryfikować odporność testowanej aplikacji na próby ich przeprowadzenia. Blokowanie wysyłania spamu oraz podstawowe wymagania dotyczące bezpieczeństwa Powszechnie stosowanym elementem w aplikacjach internetowych jest formularz kontaktowy. Aplikacja powinna być odporna na zwielokrotnienie wysyłanych wiadomości przy wykorzystaniu mechanizmu powtarzania zapytania do serwera np. poprzez odświeżanie strony lub przygotowany do tego celu automat. Tego typu luka może być wykorzystana do zablokowania tej funkcjonalności systemu w wyniku zasypania serwera spamem. Pewna część zagadnień związanych z bezpieczeństwem aplikacji znajduje swoje odwzorowanie w wymaganiach funkcjonalnych. W trakcie testów systemowych zostaną one poddane weryfikacji. Aspekty bezpieczeństwa są bardzo istotne i wykonanie dodatkowych testów wpłynie in plus. Często w wymaganiach funkcjonalnych wymieniane są założenia:  okresowa zmiana hasła wspierana algorytmem długości ciągu znaków,

składni (alfanumeryczne, duże i małe litery, znaki specjalne itp.) oraz unikalność/niepowtarzalność w stosunku do poprzedniego lub poprzednich haseł;  szczegółowa walidacja pól na formularzach wprowadzania danych, np.

wykluczenie znaków specjalnych;  blokowanie konta użytkownika po określonej liczbie nieudanych prób logowania;  czas wygaśnięcia sesji (wylogowanie) po upływie zdefiniowanego czasu

bezczynności użytkownika;  wprowadzenie dodatkowego potwierdzania przy transakcjach o wysokich kwotach;  itp.

Dostęp do funkcjonalności aplikacji zwykle wymaga potwierdzenia tożsamości. W tym celu użytkownikowi przyznawany jest indywidualny identyfikator wsparty mechanizmami uwierzytelniającymi (ang. authentication). Aplikacje powinny być odporne na próby wykonania równoległego połączenia przy użyciu tych samych danych, tj. po-

Rozdział 3.  Typy testów

93

dwójne logowanie. Jest to szczególnie istotne w serwisach transakcyjnych, gdzie trudno znaleźć uzasadnienie równoległego logowania się do serwisu. Oczywiście zawsze istnieją wyjątki od reguły. Tego typu odstępstwa od zasady powinny być poparte stosowną dokumentacją, która dla testera stanowi punkt wyjścia do oceny i weryfikacji systemu. Aplikacje internetowe narażone są na wystąpienie błędów związanych z protokołem HTTP. Bezpieczeństwo systemu zależy od poprawnej obsługi takich sytuacji. Kluczowe jest zabezpieczenie systemu przed wyciekiem informacji np. o strukturze katalogów w momencie wystąpienia kodu błędu HTTP, np. 503, 400, 403 itp. Żądanie HTTP może zwracać błędy, w następstwie których mogą pojawić się możliwości przechwycenia wrażliwych danych przez potencjalnego agresora. Zatem system powinien być odporny na niekontrolowany wyciek informacji w momencie pojawienia się takiej sytuacji. Potencjalni agresorzy mogą z premedytacją próbować wywołać jeden z wyżej wymienionych błędów z nadzieją, że w ten sposób zdobędą informacje pomocne w dalszej fazie ataku. Wszystkie powyższe zagadnienia mają wpływ na bezpieczeństwo danych i stanowią uzupełnienie dla zabezpieczeń, jakie aplikacja powinna mieć zaimplementowane w kodzie. Przypadki testowe projektowane stricte pod testy bezpieczeństwa często zahaczają o wymogi funkcjonalne. Bezpieczeństwo aplikacji nie może być oparte jedynie na zapisach w wymaganiach funkcjonalnych. Każda aplikacja powinna być poddana niezależnym testom bezpieczeństwa, które zweryfikują odporność systemu na ataki.

3.2.2.1. Atak typu Cross Site Scripting (XSS) Atak typu XSS polega na dopisaniu (osadzeniu) własnego kodu w dokumencie HTML w celu zmiany wyglądu strony i/lub przechwycenia sesji użytkownika (ciasteczka). Do wstrzyknięcia dodatkowego kodu — najczęściej JavaScript (JS) — wykorzystuje się pola formularzy udostępnione w aplikacji internetowej. Język JavaScript jest wykonywany (interpretowany) przez przeglądarkę, co daje duże możliwości przeprowadzenia ataku typu XSS. Plan wykonania testów bezpieczeństwa aplikacji internetowej powinien uwzględniać próbę wykonania obcego kodu JS, wstrzykniętego poprzez pole formularza. W celu zobrazowania możliwości oraz potencjalnych skutków takiego ataku posłużymy się poniższym przykładem. Rysunek 3.28 prezentuje formularz doładowania telefonu, który może być częścią większej aplikacji, np. bankowości elektronicznej. Użytkownik za pomocą listy rozwijanej wybiera rachunek obciążany, tj. rachunek, z którego zostanie wykonany przelew na rzecz operatora, podaje numer telefonu oraz kwotę zasilenia konta karty SIM. Ostatecznie po wprowadzeniu danych użytkownik za pomocą przycisku akcji DOŁADUJ inicjuje proces. De facto dane z formularza przesyłane są do serwera aplikacji, gdzie są procedowane. Względy bezpieczeństwa wymagają również, aby wprowadzone dane były jeszcze raz zweryfikowane i potwierdzone. Rysunek 3.29 prezentuje kolejny krok (drugi) z procesu doładowania, tj. prezentację uprzednio wprowadzonych danych. Odporność formularza na atak XSS można sprawdzić poprzez próbę przekazania skryptu JS jako wartości pola. Analizę należy rozpocząć od przyjrzenia się źródłu wygenerowanej strony HTML z podsumowaniem doładowania (bez próby ataku XSS). Kod HTML przedstawiony na rysunku 3.30 nie wykazuje cech ataku typu XSS.

94

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.28. Formularz doładowania telefonu

Rysunek 3.29. Formularz doładowania telefonu — potwierdzenie danych

Rysunek 3.30. Źródło strony — potwierdzenie doładowania

Rozdział 3.  Typy testów

95

Pole Nr telefonu posłuży do przeprowadzenia ataku. W tym polu wstawimy numer telefonu oraz ciąg znaków wstrzykiwanego skryptu (rysunek 3.31). Pole przyjmie wartość: 100-200-300 , gdzie pomiędzy znacznikami , który jest wynikiem ataku typu XSS.

96

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.34. Źródło strony „Podsumowanie doładowania” po wstrzyknięciu kodu JS

Z punktu widzenia testów bezpieczeństwa nie ma najmniejszego znaczenia, jakie instrukcje wykona wstrzyknięty kod. Kluczowe jest, aby w ogóle nie było możliwe przeprowadzenie takiego ataku. Aplikacja internetowa powinna rozpoznać (zablokować) próbę zagnieżdżenia obcego kodu w wartości przekazywanego parametru z pola formularza.

3.2.2.2. Atak metodą SQL-Injection Popularnym, a zarazem bardzo groźnym typem ataków jest SQL-Injection. Polega on na manipulowaniu (wstrzykiwanie) instrukcjami SQL w celu kradzieży, modyfikacji lub usunięcia danych. W skrajnych przypadkach możliwe jest wykonanie instrukcji systemu operacyjnego, a w konsekwencji przejęcie kontroli nad serwerem. Metoda ta wykorzystywana jest w celu modyfikacji instrukcji SQL, która jest wykonywana przez serwer bazy danych. Błędy SQL-Injection wynikają z braku obsługi (kodowania) znaków specjalnych oraz ze słabej walidacji danych wprowadzanych w polach formularzy (parametrów przekazywanych przez użytkowników). Najczęściej wykorzystywanymi obszarami aplikacji do przygotowania tego typu ataków są wszelkie filtry, na których definiowane są reguły prezentowania danych. Fakt występowania filtru podnosi prawdopodobieństwo, że zapytanie SQL wykorzystuje słowo kluczowe WHERE i/lub ORDER BY. Warianty metody SQL-Injection obejmują:  wykorzystanie sekcji WHERE, modyfikację warunków w celu uzyskania większej

ilości danych;  zastosowanie konstrukcji UNION SELECT w celu wykonania dodatkowego

(własnego) zapytania na bazie danych;  wykorzystanie słowa kluczowego ORDER BY.

Do omówienia problematyki tego rodzaju ataków posłużymy się bazą danych MySQL oraz prostym interfejsem internetowym (rysunek 3.37) „pracującym” na tabeli o nazwie tb_zlec_doladowania. Rysunek 3.35 prezentuje strukturę tabeli wyświetloną za pomocą polecenia DESC w konsoli. Rysunek 3.36 ujmuje wynik zapytania o wszystkie dane zawarte w tabeli tb_zlec_doladowania.

Rozdział 3.  Typy testów

97

Rysunek 3.35. Struktura tabeli tb_zlec_doladowania (zlecenia doładowań)

Rysunek 3.36. Listing wszystkich danych z tabeli tb_zlec_doladowania

Standardowa obsługa formularza filtru nie powinna stanowić żadnego zagrożenia dla systemu. Rysunek 3.38 prezentuje wynik działania funkcjonalności wyświetlania historii doładowań odfiltrowanych według kryterium podanego na filtrze (rysunek 3.37). Listing 3.1 prezentuje składnię zapytania SQL, jakie zostało zrealizowane na bazie danych w celu zwrócenia pożądanych danych. Rysunek 3.37. Interfejs pozwalający na zdefiniowanie warunku filtrowania danych

98

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.38. Lista operacji zwróconych przy uwzględnieniu warunków z filtra Listing 3.1. Składnia zapytania SQL uwzględniająca parametry filtra SELECT FROM WHERE and and

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania rach_obc = '102030405000009' nr_telefonu = '100-200-300' kwota >= 10;

Modyfikacja sekcji WHERE zapytania Jedną z możliwości ataku SQL-Injection jest próba modyfikacji sekcji WHERE zapytania. Spróbujmy w polu Podaj nr telefonu wprowadzić modyfikator, wpisując ciąg znaków 100-200-300' or nr_telefonu='100-600-900 (rysunek 3.39). Na rysunku 3.40 widnieje wynik takiego zapytania. Listing 3.2 prezentuje instrukcję SQL wykonaną na bazie danych. Przyglądając się dostępnym opcjom filtra, można wnioskować, że założeniem projektanta było ograniczenie zwracanych wyników jedynie dla jednego numeru telefonu. Test wykazał podatność aplikacji na wstrzykiwanie zewnętrznych warunków do sekcji WHERE. W analizowanym przypadku udało się uzyskać dane dla dodatkowego numeru telefonu 100-600-900. Luka w aplikacji jest tym bardziej zatrważająca, że istnieje prawdopodobieństwo pobrania danych dla numeru telefonu należącego do osób trzecich (nie tylko w obrębie jednego klienta sieci telefonii komórkowej). Promień możliwych informacji do pobrania zależy od konstrukcji zapytania. W tym przypadku atakujący nie ma dostępu do fragmentu składni przed słowem kluczowym WHERE. Rezultat wykonania zmodyfikowanego zapytania SQL bezpośrednio na bazie danych prezentuje rysunek 3.41. Wynika z niego, że dane wyświetlone w interfejsie internetowym idealnie odpowiadają zamierzeniu atakującego. Rysunek 3.39. Przygotowanie do ataku metodą SQL-Injection

Rozdział 3.  Typy testów

99

Rysunek 3.40. Historia operacji po ataku SQL Injection Listing 3.2. Instrukcja SQL — warunek WHERE zmodyfikowany za pomocą SQJ-Injection SELECT FROM WHERE and or and

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania rach_obc = '102030405000009' nr_telefonu = '100-200-300' nr_telefonu = '100-600-900' kwota >= 10;

Rysunek 3.41. Wykonanie zmodyfikowanego zapytania (warunek WHERE) bezpośrednio na bazie danych

W tym momencie nasuwa się pytanie, w jaki sposób spreparować parametr w filtrze? Nie jest to pytanie filozoficzne, ale i odpowiedź nie może być jednoznacznie oczywista. W istocie testy polegają na próbie włamania się do systemu. Z pewnością pomocna okaże się dobra znajomość języka SQL, spostrzegawczość i doświadczenie. Nazwy kolumn tabeli można próbować odszyfrować po nazwach przekazywanych parametrów w łączu URL, np. http://localhost:8080/TestBook/HistoriaDoladowan.do?nrRach =102030405000009&nrTelefonu=100-200-300&znKwota=%3D&kwotaDoladowania=90. Zastosowanie UNION SELECT Operator UNION pozwala na złączenie wyników dwóch zapytań. Atak SQL-Injection przy wykorzystaniu tej konstrukcji polega na dopisaniu drugiego (własnego) zapytania do składni pierwotnej (zastosowanej przez programistę). Załóżmy, że operacje na wskazanym formularzu zwracane są po wykonaniu instrukcji select nazwisko, imie from tb_uzytkownicy. Atak polegać będzie na zmodyfikowaniu owego zapytania do przykładowej postaci select nazwisko, imie from tb_uzytkownicy UNION select kolumnaA, kolumnaB from tb_tabela.

100

Testowanie oprogramowania. Podręcznik dla początkujących

Operator UNION wymaga, aby oba zapytania zwracały taką samą liczbę kolumn o identycznych typach danych. Problem dobrania typu danych może okazać się mniej istotny z uwagi na to, że niektóre bazy danych dokonują automatycznej ich konwersji. Natomiast dobranie i ustalenie liczby kolumn może być problematyczne. Za pomocą dobrze znanego już filtra operacji doładowań podejmiemy próbę złączenia dwóch zapytań. Konstrukcja filtrów pozwala przypuszczać, że pole kwota stanowi ostatni warunek zapytania, dlatego też od tego miejsca należy podjąć próbę. Rysunek 3.42 prezentuje przygotowanie formularza do ataku SQL-Injection z użyciem klauzuli UNION. Wykonanie tak spreparowanego formularza (docelowo zapytania) zakończyło się błędem, co zostało odnotowane jako wyjątek na serwerze: java.sql.SQLException: The used SELECT statements have a different number of columns. Analizując komunikat, można się spodziewać, że zapytanie (listing 3.3) nie może być poprawnie zrealizowane z uwagi na różnicę w liczbie zwracanych kolumn pomiędzy łączonymi zapytaniami (rysunek 3.43). Rysunek 3.42. Przygotowanie filtra do ataku z użyciem UNION

Listing 3.3. Składnia zapytania SQL, które zostało wykonane — użycie UNION SELECT nr_telefonu, kwota, rach_obc, data FROM tb_zlec_doladowania WHERE rach_obc = '102030405000009' and nr_telefonu = '100-200-300' and kwota >=10 UNION select 1, 2 from dual;

Rysunek 3.43. Wykonanie zapytania SQL bezpośrednio na bazie MySQL — błąd UNION

Wykonanie polecenia SELECT z klauzulą UNION wymaga ustalenia liczby kolumn wykorzystywanych w oryginalnym zapytaniu. Ten cel można osiągnąć poprzez zwiększanie o jeden liczby kolumn w każdej kolejnej próbie, np. UNION select null, null from tb_tabela, przy następnym podejściu należy użyć składni UNION select null, null, null from tb_tabela i tak dalej, aż do wykonania zapytania przez serwer. Drugim sposobem jest zastosowanie operatora ORDER BY, który za pomocą cyfry pozwala określić, po której kolumnie ma się odbywać sortowanie. Ta metoda polega na

Rozdział 3.  Typy testów

101

poszukiwaniu ostatniej kolumny. W przypadku gdy SELECT z ORDER BY 12 zakończy się błędem, tabela składa się z 11 lub mniejszej liczby kolumn. Przyjrzyjmy się danym wprowadzonym na ćwiczebnym formularzu (rysunek 3.44). Uprzednie założenie, że wartość z kolumny kwota zamyka sekcję WHERE zapytania, znalazło potwierdzenie w komunikacie błędu zwróconym przez serwer. Przy założeniu, że dysponujemy wiedzą, ile kolumn jest wykorzystywanych w zapytaniu, wprowadźmy następującą sekwencję jako wartość pola: –1 union select version(), null, null, null. Skutek tak przygotowanego ataku pokazano na rysunkach 3.45 i 3.46. Analizując zapytanie (listing 3.4), jakie powstało w wyniku modyfikacji instrukcji SQL, należy dostrzec, że w wyniku zastosowanej sztuczki pierwsze zapytanie prawdopodobnie nie zwróci żadnych danych. Warunek kwota doładowania = –1 PLN wydaje się bardzo abstrakcyjny. Ów zabieg pozwala na odrzucenie prezentacji atakującemu zupełnie niepotrzebnych mu danych, gdyż jak wynika z konstrukcji drugiego zapytania, napastnika interesuje jedynie wersja serwera bazy danych MySQL (select version()). Zgodnie z wcześniejszymi rozważaniami brakującą liczbę kolumn do zapytania po słowie UNION uzupełniono sekwencjami NULL, tak aby cała składnia miała szansę zostać prawidłowo zinterpretowana i wykonana przez serwer bazy danych. Z pewnością pokazany przykład nie jest wyrafinowany i spektakularny w konsekwencjach. Niemniej jednak taki test jest w stanie obnażyć słabość aplikacji na ataki typu SQL-Injection z zastosowaniem SELECT UNION. Rysunek 3.44. Przygotowanie filtra do ataku metodą UNION — prawidłowa liczba kolumn

Rysunek 3.45. Wykonanie zmodyfikowanego zapytania przy użyciu UNION bezpośrednio na bazie MySQL Rysunek 3.46. Wynik wykonania zmodyfikowanego zapytania przy użyciu UNION

102

Testowanie oprogramowania. Podręcznik dla początkujących

Listing 3.4. Składnia zapytania SQL, które zostało wykonane — użycie UNION w polu kwota SELECT FROM WHERE and and union select

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania rach_obc = '102030405000009' nr_telefonu = '100-200-300' kwota = -1 version(), null, null, null;

Wykorzystanie ORDER BY Fraza ORDER BY jest nieodzownym elementem wszystkich zapytań pobierających dane na rzecz list artykułów, operacji bankowych, faktur VAT, zgłoszeń serwisowych itp. Generalizując, jest potrzebna wszędzie tam, gdzie zwracana jest duża ilość danych i jednocześnie użytkownik ma mieć możliwość prezentowania danych w wygodnej dla siebie formie (sortowanie). W związku z faktem występowania takiego słowa kluczowego w składni zapytania istnieje również możliwość wykorzystania tego elementu do ataku metodą SQL-Injection. Posłużmy się dobrze już nam znanym zapytaniem z dodanym elementem ORDER BY na jego końcu (listing 3.5). Opierając się jedynie na teoretycznych rozważaniach, można postawić hipotezę, że atak metodą SQL-Injection jest niemożliwy, gdyż ORDER BY zasadniczo kończy całą składnię zapytania. Niemniej jednak konstrukcja ORDER BY może zostać wykorzystana do określenia liczby kolumn w tabeli np. w celu przygotowania ataku przy użyciu SELECT UNION. Ponadto filtry operacji nie muszą zawsze idealnie pokrywać się z możliwościami sortowania, jakie daje tabela. Z poziomu interfejsu użytkownika filtrowanie według części kryteriów może być zwyczajnie niedostępne. Manipulowanie numerem kolumny w opcji sortowania może dać dostęp do informacji, które z założenia projektanta mają być niedostępne lub wyświetlane w ściśle monitorowanych warunkach. Listing 3.5. Zapytanie SQL z operatorem ORDER BY SELECT nr_telefonu, kwota, rach_obc, data FROM tb_zlec_doladowania WHERE rach_obc = '102030405000009' and nr_telefonu = '100-200-300' ORDER BY 1 DESC;

Blind SQL-Injection Idea metody Blind SQL-Injection jest bardzo prosta. Rezultatem tego typu ataku ma być czysta informacja, która jest logiczną odpowiedzią na zadane pytanie (TAK/NIE). Cechą tej techniki jest brak literalnej prezentacji wyników np. w postaci dodatkowych wierszy z bazy danych. Atakujący uzyskuje odpowiedź na wprowadzone pytanie, np. czy długość wersji (sygnatury) bazy danych przekracza 10 znaków (AND (SELECT length(version())) > 10). W analogiczny sposób można zapytać: czy 3. znak (spodziewamy się cyfry) z sygnatury wersji serwera jest większy od 4 (AND (SELECT substr(version(),3,1)) > 4). Listingi 3.6 i 3.7 prezentują składnię zapytań realizujących kolejno przytoczone powyżej próby ataku. Rysunek 3.47 przedstawia próbę odszyfrowania, z ilu znaków składa się sekwencja opisująca wersję serwera. Wcześniej

Rozdział 3.  Typy testów

103

Rysunek 3.47. Atak typu Blind SQL-Injection — sprawdzanie długości sygnatury wersji

wykonane testy oraz przytoczona na rysunku 3.38 zawartość tabeli tb_zlec_doladowania sugerują, że powinniśmy otrzymać kilka wierszy dla warunku kwota. Zatem zwrócenie pustej tabeli (rysunek 3.48) oznacza, że nieprawdziwy okazał się dopisany warunek AND (SELECT length(version())) > 10. Atakujący otrzymuje więc informację zwrotną, że długość identyfikatora wersji serwera bazy danych nie przekracza 10 znaków. Rysunek 3.49 przedstawia przygotowaną próbę odgadnięcia, jaki znak znajduje się na wybranej pozycji zwracanej przez funkcję version() bazy MySQL. Analizę i zrozumienie przykładów z pewnością ułatwi rysunek 3.50, który prezentuje wynik zapytania o wersję i podciąg z tej sygnatury.

Rysunek 3.48. Atak typu Blind SQL-Injection — sprawdzanie długości sygnatury — prezentacja wyniku Rysunek 3.49. Blind SQL-Injection — sprawdzanie wybranego znaku z ciągu sygnatury wersji serwera bazy danych

Listing 3.6. Blind SQL-Injection — sprawdzanie długości sygnatury wersji SELECT FROM WHERE and and AND

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania rach_obc = '102030405000009' nr_telefonu = '100-200-300' kwota >= -1 (SELECT length(version())) > 10

Listing 3.7. Blind SQL-Injection — sprawdzanie, czy wybrana pozycja z sygnatury wersji jest większa od 4 SELECT FROM WHERE and and and

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania rach_obc = '102030405000009' nr_telefonu = '100-200-300' kwota >= -1 (SELECT substr(version(), 3, 1)) > 4

104

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.50. Wersja serwera bazy danych MySQL

Przesyłanie dodatkowych instrukcji Niezmiernie groźną formą ataku SQL-Injection są próby wyłączenia części instrukcji zapytania (listing 3.8). W języku SQL za pomocą dwóch myślników (--) można wprowadzać komentarze w kodzie. Oznacza to, że tak oznaczony fragment polecenia nie zostanie zinterpretowany jako kod wykonywalny i w konsekwencji będzie pominięty. Przykład opisywanego ataku prezentuje rysunek 3.51, gdzie za pomocą dopisania ciągu znaków ') –- po numerze telefonu zamknęliśmy zapytanie po warunku dotyczącym telefonu i jednocześnie wyłączyliśmy wykonywanie ostatniego warunku opisującego kwotę (listing 3.8). Wynik opisywanego ataku prezentuje rysunek 3.52, na którym jednoznacznie widać, że warunek dotyczący kwoty został pominięty przy wykonywaniu zapytania. Listing 3.8. SQL-Injection — wykluczenie wykonania części kodu SQL SELECT FROM WHERE and

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania (rach_obc = '102030405000009') (nr_telefonu = '100-200-300') -- ') and (kwota>='400')

Rysunek 3.51. SQL-Injection — wyłączenie części kodu

Rysunek 3.52. SQL-Injection — wyłączenie części kodu — wynik zapytania

Rozdział 3.  Typy testów

105

Luka w systemie pozwalająca na wygaszanie fragmentu kodu otwiera przed potencjalnym agresorem dodatkową i bardzo groźną możliwość. Jest to perspektywa wykonania własnej instrukcji kodu (listing 3.9), na przykład: DROP TABLE nazwa, DELETE FROM tabela etc. Może on na przykład próbować zrealizować następujący kod w polu Podaj nr telefonu: 100-200-300'); DROP TABLE nazwa; -- . Wprowadzona sekwencja zamyka warunek WHERE zapytania w polu numeru telefonu, następnie inicjuje wykonanie instrukcji DROP TABLE i za pomocą dwóch myślników wyklucza wykonanie pozostałej części kodu z oryginalnego zapytania. Atak tego typu polega na przesłaniu dodatkowej instrukcji SQL poprzez parametr z formularza, którego wartość wchodzi w skład warunku WHERE zapytania SQL. Listing 3.9. SQL-Injection — wykluczenie wykonania części kodu SQL z dopisaniem własnej instrukcji SELECT nr_telefonu, kwota, rach_obc, data FROM tb_zlec_doladowania WHERE (rach_obc = '102030405000009') and (nr_telefonu = '100-200-300'); DROP TABLE nazwa; -- ') and (kwota>='1');

Ataki metodą wstrzykiwania instrukcji SQL (SQL-Injection) możliwe są również w aplikacjach desktopowych. Przygotowanie takiego ataku odbywa się analogicznie jak w przypadku aplikacji internetowych. Odpowiednio manipulując danymi w jednym z pól filtra, można spreparować zapytanie SQL tak, aby zwróciło pożądane przez atakującego dane. Brak listingu z kodem źródłowym plików Java, które realizują połączenie z bazą danych, wykonanie zapytania oraz zwrot danych to celowe zabiegi. W ten sposób chcę pobudzić wyobraźnię testera, wpłynąć na inspirację do poszukiwań błędów w kodzie otwierających drogę do ataku SQL-Injection. Znajomość źródeł może spowodować zaprojektowanie testu „pod kod”. Dla tego typu testów nie jest to odpowiednia droga.

3.2.2.3. Podmienianie wartości przekazywanych parametrów Jedną z najgroźniejszych form ataków na aplikacje internetowe są próby modyfikacji wartości parametrów przekazywanych z formularzy. Na wstępie należy przyjąć, że przeglądarka i kanał komunikacyjny pomiędzy użytkownikiem a serwerem to elementy, którym nie można ufać. Protokół HTTPS nie zmienia faktu, że każdy element żądania (ang. request) lub odpowiedzi (ang. response) może zostać zmodyfikowany. Podstawowym narzędziem analizy aplikacji internetowych są narzędzia typu local proxy. Funkcjonalność wspomnianych programów pozwala na przechwycenie komunikacji pomiędzy przeglądarką internetową a serwerem oraz dowolną modyfikację przesyłanych treści (kodu). Na potrzeby naszych rozważań zaproponuję dwa narzędzia:  Fiddler (strona domowa: http://fiddler2.com/home) — rekomendowany

(rysunek 3.53).  WebScarab (strona domowa: https://www.owasp.org/index.php/Webscarab).

106

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.53. Obszar roboczy narzędzia Fiddler (wersja v2.4.5.3)

Doskonałym narzędziem deweloperskim jest Firebug (rysunek 3.54). Jest to wtyczka do przeglądarki Firefox, która pozwala na przegląd i debugowanie kodu. Narzędzie można pobrać ze strony domowej projektu https://www.getfirebug.com/.

Rysunek 3.54. Obszar roboczy narzędzia Firebug (wersja 1.12.1)

Za pomocą programu Fiddler spróbujemy przechwycić sesję oraz podejrzeć, jakie parametry przesyła żądanie. W kolejnych krokach zbadamy odporność aplikacji na podmianę wartości przekazywanych parametrów. Aby Web Debugger przechwycił sesję z przeglądarki przed wysłaniem jej do serwera, należy włączyć odpowiednią opcję (rysunek 3.55). Z poziomu menu głównego wybieramy Rules/Automatic Breakpoints/Before Requests. Tak przygotowana aplikacja przechwyci sesję i „przytrzyma” ją do czasu zwolnienia przez operatora.

Rozdział 3.  Typy testów

107

Rysunek 3.55. Konfiguracja programu Fiddler — przechwycenie żądania

Przeanalizujmy poniższy przykład przechwycenia sesji, podmiany wartości parametrów i prezentacji ostatecznego wyniku całej akcji. Użytkownik na formularzu doładowania telefonu wprowadza wszystkie dane i wciska przycisk DOŁADUJ (rysunek 3.56). Żądanie jest przechwycone przez lokalne proxy, a następnie modyfikacji podlegają wysyłane parametry: kwota i numer telefonu (rysunek 3.57). Przyciskiem Run to Completion zwalniamy sesję, tj. ostatecznie wysyłamy żądanie do serwera. Rysunek 3.56. Formularz doładowania telefonu, akcja wysyłania danych

W celu upewnienia się, że podmiana zadziałała, podejrzymy sesję powrotną, tj. response. Rysunek 3.58 prezentuje treść (kod), jaka została skierowana do przeglądarki po modyfikacji parametrów. Bezapelacyjnie widać, że serwer wygenerował treść strony przy uwzględnieniu fałszywych danych. Rysunek 3.59 prezentuje wynik interpretacji zmodyfikowanego kodu HTML oraz wartości parametrów, jakie zostały wysłane na serwer (łącze URL). Różnica pomiędzy wysyłanymi a prezentowanymi danymi wynika z ich podmiany na skutek przechwycenia sesji na linii przeglądarka – serwer.

108

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.57. Fiddler — przechwycenie sesji, modyfikacja parametrów

Rysunek 3.58. Fiddler — przechwycenie odpowiedzi z serwera Rysunek 3.59. Podsumowanie detali zlecenia doładowania — podmiana wartości parametrów

Rozdział 3.  Typy testów

109

Fundamentalną formą ochrony użytkownika systemu przed konsekwencjami ewentualnej podmiany wartości parametrów jest prezentacja detali formularza przed ostatecznym zatwierdzeniem. Wprowadzenie kroku pośredniego umożliwia ponowną weryfikację danych przez użytkownika (rysunek 3.60). Takie rozwiązanie przynosi dwie podstawowe korzyści:  użytkownik ma szansę wychwycić i poprawić własny błąd;  użytkownik może wykryć próbę nieautoryzowanej zmiany parametrów,

przerwać operację i przesłać zgłoszenie do serwisu. Rysunek 3.60. Wprowadzenie kroku pośredniego — podniesienie bezpieczeństwa

Brak kroku pośredniego pomiędzy formularzem a akcją zatwierdzania powinien być traktowany jako potencjalny punkt obniżający bezpieczeństwo aplikacji. Skuteczność takiej ochrony jest zależna od konstrukcji kodu, tj. dane przesyłane są z formularza na serwer, który „przechwytuje i przetrzymuje” wartości i równolegle odsyła je do potwierdzenia. Ewentualna podmiana wartości powinna ujawnić się w drugim kroku, kiedy użytkownikowi wyświetlane są detale podsumowania. Dopiero w trzecim kroku akcja jest ostatecznie potwierdzana, ale już bez ponownego przesyłania danych na serwer (powinny być użyte wartości otrzymane w kroku pierwszym, o ile są zgodne). Niemniej jednak aplikacje internetowe mogą podlegać atakom łączonym polegającym na podmienianiu parametrów (request) oraz modyfikacji treści strony z podsumowaniem (response). Tego typu przypadek stanowi wyrafinowaną formę ataku, która wymaga znacznego wysiłku ze strony agresora.

3.2.2.4. Przekazywanie wrażliwych informacji w adresie URL Trudno dzisiaj wyobrazić sobie aplikację internetową, która nie wchodzi w interakcję z użytkownikiem. Użytkownicy mają wpływ na ilość i formę prezentowanych informacji. Mogą również wprowadzać i modyfikować dane za pomocą udostępnionych im formularzy. Zwykle dane wprowadzone w polach formularza przekazywane są jako parametr w łączu URL (listing 3.10). Nazwa pierwszego parametru poprzedzana jest znakiem „?”, kolejnych znakiem „&”, a ich wartości podawane są po znaku „=”. Niestety takie rozwiązanie może być przyczyną wycieku wrażliwych informacji, które można wychwycić z treści ciągu URL. Problem nie dotyczy jedynie danych oczywistych, tj. takich, o których istnieniu wie zarówno użytkownik, jak i potencjalny agresor. Bardzo groźną sytuacją jest ujawnienie informacji, które nie powinny być upubliczniane, takich jak np. wewnętrzny numer klienta w systemie. W adresie URL nie muszą być przekazywane jedynie parametry, których wartość uzupełniana jest przez użytkownika.

110

Testowanie oprogramowania. Podręcznik dla początkujących

Aplikacja może w sposób niejawny pobierać dane niejako w tle, a w trakcie wysyłania żądania do serwera przekazywać je w pasku URL. Listing 3.10. Przykładowy adres URL z przekazywanymi parametrami http://localhost:8080/TestBook/HistoriaDoladowan.do?nrRach=102030405000009&nr Telefonu=100-200-300&znKwota=%3D&kwotaDoladowania=90.

Często popełnianym przez programistów błędem jest ujawnianie w pasku URL nazwy kolumny lub kolumn, po których wykonywane jest sortowanie zapytania SQL (listing 3.11). Wprawdzie operator ORDER BY zezwala na posługiwanie się numerem kolumny (pozycją, na której występuje) zamiast jej nazwą, jednak takie rozwiązanie usztywnia kod. Chcąc zagwarantować sobie elastyczność kodu, koderzy podają w sortowaniu nazwę kolumny. Jest to rozwiązanie wygodne, niemniej niebezpieczne dla aplikacji. Wygoda polega na braku konieczności modyfikowania zapytania SQL w przypadku rozszerzenia tabeli o dodatkowe kolumny. W sytuacji sortowania po numerach kolumn może się zdarzyć, że w wyniku rozbudowy tabeli interesująca nas kolumna zostanie przesunięta na inną pozycję. Zapytanie SQL może zostać wykonane poprawnie, niemniej jednak ulegnie zmianie referencja do kolumny sortowania, a tym samym otrzymamy wynik w innej postaci, niż się spodziewaliśmy. Listing 3.11. Przykładowy adres URL z przekazywaną nazwą kolumny do sortowania danych http://localhost:8080/TestBook/HistoriaDoladowan.do?nrRach=102030405000009&nr Telefonu=100-200-300&znKwota=%3E%3D&kwotaDoladowania=10&sortowanie=kwota

Rysunek 3.61 przedstawia formularz pozwalający na określenie metody sortowania danych. Wynik tej akcji pokazuje rysunek 3.62. W pasku URL dokładnie widać, że został przekazany parametr sortowanie z wartością kwota. Listing 3.12 prezentuje powstałe zapytanie z uwzględnieniem przesłanych parametrów z formularza filtra transakcji doładowań telefonów. Listing 3.12. Zapytanie SQL z sortowaniem danych według nazwy kolumny SELECT FROM WHERE and and order

Rysunek 3.61. Przykładowy formularz, który umożliwia wybór parametru sortowania

nr_telefonu, kwota, rach_obc, data tb_zlec_doladowania (rach_obc = '102030405000009') (nr_telefonu = '100-200-300') (kwota >= '10') by kwota DESC;

Rozdział 3.  Typy testów

111

Rysunek 3.62. Przekazanie w łączu URL informacji o nazwie kolumny do sortowania danych

3.2.2.5. Zdradzanie nadmiarowych informacji o systemie Zwykle atak na system informatyczny poprzedzony jest rekonesansem, który ma na celu ustalenie, z jakimi rozwiązaniami potencjalny włamywacz będzie się borykać. Ustalenie nazwy i wersji systemu operacyjnego, serwera aplikacji czy też bazy danych jest istotnym punktem podczas planowania ataku. Powszechnie wiadomo, że programy nie są wolne od wad i luk w bezpieczeństwie. Zidentyfikowanie środowiska, w którym osadzona jest aplikacja, pozwala na dobranie odpowiednich technik i narzędzi do przeprowadzenia ataku. Włamywacz po rozpoznaniu systemu operacyjnego, serwera aplikacji lub bazy danych rozpocznie przygotowywanie ataku przy wykorzystaniu powszechnie znanych, tj. wykrytych luk w tych serwerach. Niestety dość często administratorzy ignorują wydawane aktualizacje i ich nie instalują, co z kolei jest skrupulatnie wykorzystywane podczas prób włamania. Do podstawowych technik pozyskiwania informacji należy zaliczyć:  przeglądanie nagłówka HTTP, tj. informacji w nim przekazywanych;  przeglądanie treści prezentowanych przez aplikację, np. o producencie

oprogramowania;  wywoływanie błędów HTTP, np. 500, 400, 300;  metodę SQL-Injection;  socjotechnikę.

Wymuszanie kodu błędu serwera WWW ma na celu pozyskanie informacji o strukturze plików i katalogów. Bardzo często tego typu próby kończą się sukcesem na skutek błędnej konfiguracji serwera. Błędy serwera WWW można wywołać, manipulując treścią żądania, np. przy użyciu narzędzia Fiddler. Za pomocą metody SQL-Injection można „wyciągnąć” wersję serwera bazy danych. Popularną praktyką jest umieszczanie w aplikacjach informacji o producencie oprogramowania. Nierzadko tego typu treści występują jako odnośniki do stron internetowych. Systemy o dużym ciężarze odpowiedzialności, na przykład dla sektora finansowego, powinny być wolne od wszelkich śladów kierujących do wykonawcy kodu. Najsłabszym ogniwem całokształtu zabezpieczeń jest zawsze człowiek. Zanim potencjalny agresor przystąpi do frontalnego ataku, wykorzysta wszystkie możliwości do udoskonalenia planu. Nie można wykluczyć sytu-

112

Testowanie oprogramowania. Podręcznik dla początkujących

acji, kiedy to atak docelowy będzie poprzedzony włamaniem do sieci producenta oprogramowania i/lub do komputera programisty. Scenariusz zależy od motywacji, kosztów i skuteczności zabezpieczeń po stronie producenta i instytucji używającej systemu. Nie można wykluczyć, że włamywacz, chcąc poznać system, jego słabe strony i możliwości, wykradnie strategiczne informacje bezpośrednio z sieci wykonawcy kodu. Nagłówek odpowiedzi HTTP często okazuje się skarbnicą wiedzy o środowisku. Odrobina szczęścia wystarczy, aby poznać nazwę i wersję serwera WWW i/lub serwera aplikacji (rysunek 3.63). Uzyskane informacje posłużą do przygotowania planu ataku. Rysunek 3.64 przedstawia nagłówek HTTP, który zdradza nazwę i wersję serwera aplikacji. Natomiast kolejny rysunek  3.65  prezentuje nagłówek z danymi serwera WWW.

Rysunek 3.63. Podgląd nagłówka HTTP — Fiddler Web Debugger

3.2.2.6. Modyfikacja treści strony w aplikacji internetowej Bardzo groźną formą ataków są zabiegi polegające na modyfikacji treści wyświetlanej strony. Na żądanie przeglądarki serwer dostarcza odpowiednie treści. W sytuacji kiedy agresorowi uda się przechwycić ową sesję, może on próbować dokonać zmian w kodzie, który ostatecznie jest wykonywany przez przeglądarkę. Głównym celem takiego ataku jest „podstawienie” użytkownikowi fałszywych danych, np. numeru rachunku do przelewu bankowego, lub uzyskanie nieautoryzowanego dostępu do pewnych funkcjonalności systemu, np. płatnej części serwisu internetowego lub obszaru klienta strategicznego (VIP). Powodzenie powyższego ataku zależy od wdrożonych w kodzie rozwiązań. Posłużmy się dobrze już nam znanymi formularzami oraz narzędziem Fiddler do zasymulowania ataku polegającego na podmienieniu prezentowanej treści. Na skutek żądania płynącego z przeglądarki serwer wysyła odpowiedź. Naszym zadaniem jest przechwycić, a następnie zmodyfikować jej treść. W tym celu ustawimy czujność Fiddlera na „łapanie sesji powrotnej” (rysunek 3.66). Z poziomu menu głównego wybieramy Rules/Automatic Breakpoints/After Responses.

Rozdział 3.  Typy testów

Rysunek 3.64. Nagłówek HTTP — przekazywanie informacji o nazwie i wersji serwera aplikacji

Rysunek 3.65. Nagłówek HTTP — przekazywanie informacji o nazwie i wersji serwera WWW

113

114

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.66. Konfiguracja programu Fiddler — przechwycenie odpowiedzi

Rysunek 3.67 pokazuje przechwyconą sesję z serwera do przeglądarki, która jest odpowiedzią na wykonanie akcji doładowania telefonu komórkowego. Użytkownik spodziewa się, że na podsumowaniu detali zlecenia ujrzy oczekiwane informacje. Odpowiedź została przechwycona. Zmodyfikujmy jej treść, dodając kilka własnych tagów HTML (rysunek 3.68). Skutek powyższego zabiegu widoczny jest na rysunku 3.69. Użytkownikowi został wyświetlony dodatkowy wiersz zachęcający go do wykonania telefonu pod wskazany numer (słono płatny). Listing 3.13 przedstawia źródło strony wyświetlone za pomocą opcji w przeglądarce internetowej.

Rysunek 3.67. Fiddler — przechwycenie i podgląd odpowiedzi z serwera do przeglądarki

Rozdział 3.  Typy testów

115

Rysunek 3.68. Fiddler — modyfikacja kodu wysyłanego do przeglądarki, dodanie własnych tagów HTML Rysunek 3.69. Strona wyświetlająca detale operacji „wzbogacona” o obcą treść

Oparcie ograniczenia dostępu do funkcji systemu na znaczniku disabled języka HTML może przysporzyć pewne trudności w realizacji zamierzonego celu (listing 3.14, rysunek 3.70). Takie „zabezpieczenie” można obejść, posługując się analogiczną metodą, jakiej użyliśmy przy modyfikacji treści. W przechwyconym komunikacie odpowiedzi należy usunąć atrybut disabled. Przykład takiego zabiegu prezentuje listing 3.15, gdzie za pomocą narzędzia Fiddler usunięto znacznik disabled. Rysunek 3.71 prezentuje efekt działania zmodyfikowanego kodu w przechwyconej sesji — przycisk oraz pola są dostępne dla użytkownika. Warto rozważyć zasadność wysyłania obiektu akcji (przycisk) oflagowanego jako niedostępny. Nieaktywny przycisk można zastąpić komunikatem, który równie jednoznacznie poinformuje użytkownika o braku dostępności do opcji i jednocześnie nie stanowi potencjalnego celu ataku.

116

Testowanie oprogramowania. Podręcznik dla początkujących

Listing 3.13. Źródło zmodyfikowanej strony z poziomu przeglądarki



Formularz doładowań telefonu - Potwierdzenie

Potwierdź doładowanie telefonu Niestety wystąpił problem z serwerem.
Skontaktuj się z BOK pod nr. 0-700-700-700



Kwota 500 PLN

Dane poprawne?



Rysunek 3.70. Formularz z wyszarzonymi obiektami — brak dostępu do opcji

Rozdział 3.  Typy testów

117

Listing 3.14. Kod formularza z zastosowaniem opcji disabled

Tylko Klienci VIP mogą wysyłać darmowe SMS Brak dostępu

Wprowadź nr telefonu:



Treść wiadomości

Treść SMS 




Listing 3.15. Kod formularza zmodyfikowany za pomocą narzędzia Fiddler — usunięcie opcji disabled



Tylko Klienci VIP mogą wysyłać darmowe SMS Brak dostępu - na pewno?

Wprowadź nr telefonu:



treść wiadomości

Treść SMS




Rysunek 3.71. Aktywacja przycisku i edycji pól na formularzu na skutek ataku

3.2.3. Testy przenośności kodu — testy instalacji Weryfikacja przenośności kodu stanowi test niefunkcjonalny, aczkolwiek istotny z punktu widzenia powszechnego udostępnienia gotowego produktu lub uruchomienia systemu na docelowym środowisku produkcyjnym. Oprogramowanie zwykle powstaje w sposób modułowy, tj. taki, w którym fragmenty kodu pisane są przez różnych pro-

118

Testowanie oprogramowania. Podręcznik dla początkujących

gramistów. Bezspornie należy przyjąć, że obowiązkiem programisty jest uruchomić własny kod w celu podstawowej jego weryfikacji przed wgraniem do repozytorium. W tym miejscu ujawnia się pierwszy argument przemawiający za wykonaniem testów przenośności. Koderzy realizują self-test w spersonalizowanym środowisku przystosowanym do własnych potrzeb, które niekoniecznie musi odpowiadać pełnym wymaganiom środowiska produkcyjnego (różnice w konfiguracji, wersji serwera aplikacji, wersji serwera bazy danych etc.). Istnieje również duże prawdopodobieństwo, że programiści uczestniczący w projekcie mają różnorodnie przygotowane własne środowiska pracy. Zdarza się, że koderzy używają wspólnie pewnych komponentów środowiska, np. serwera WWW, bazy danych. Niemniej jednak nadal jest to środowisko deweloperskie i nie można przyjąć, że ma charakter stabilny i stały. Ostatecznie gotowy produkt budowany jest w oparciu o łączny materiał przygotowany przez wszystkich programistów. Testy przenośności kodu polegają na wdrożeniu materiału przygotowanego przez zespół produkcyjny w niezależnym środowisku, które spełnia formalne wymogi. Każda aplikacja powinna mieć wydaną rekomendację o warunkach, jakie musi spełniać środowisko, aby umożliwić uruchomienie i poprawną pracę systemu. Taka specyfikacja może być przygotowana na różnym poziomie szczegółowości w zależności od rodzaju aplikacji. Inaczej będzie ona wyglądała dla aplikacji produkowanych na urządzenia nawigacji samochodowej, inaczej dla systemów klasy enterprise, a jeszcze inaczej dla oprogramowania pudełkowego. Rolą testera jest poddanie próbom przenośności aplikacji, tj. jej uruchamialności na różnych urządzeniach (np. nawigacje), w różnych wersjach systemu operacyjnego, we wspieranych przeglądarkach internetowych itp. Testy można uznać za pozytywne, jeżeli oprogramowanie da się uruchomić na różnych urządzeniach, serwerach etc. przy zachowaniu zgodności z wydaną rekomendacją dotyczącą środowiska. Nie można oczekiwać, że program będzie działał w pełni poprawnie, jeżeli uruchomimy go na urządzeniu spoza listy referencyjnej lub w przeglądarce, której formalnie system nie obsługuje. Wbrew pozorom to, co zostało uruchomione w środowisku deweloperskim, niekoniecznie udaje się wdrożyć produkcyjnie. Testy przenośności mają to zweryfikować. Problemy z przenośnością aplikacji mogą wynikać z błędów zaszytych bezpośrednio w kodzie, z niuansów w konfiguracji dla nieco innych serwerów, nieścisłości lub wady instrukcji wgrania itp. Dokumentacja wdrożenia również podlega ocenie i weryfikacji. Nie może zawierać błędów merytorycznych. Testy przenośności powinny być wykonywane na niezależnym środowisku, tj. innym niż używane do produkcji oprogramowania.

3.2.4. Testy ergonomii systemu informatycznego Przez pojęcie „ergonomia systemu informatycznego” należy rozumieć optymalne dostosowanie jego funkcjonalności do predyspozycji i cech psychofizycznych użytkowników. W procesie produkcji oprogramowania duży nacisk kładzie się na realizację zdefiniowanych funkcjonalności, jednocześnie przywiązując mniejszą wagę do tego, w jaki sposób z poziomu interfejsu użytkownika to się odbywa. Projektanci i programiści skupiają się na tym, aby zaspokoić oczekiwania wynikające z wymogów biznesowych. W tym celu kierują siły i środki na realizację logiki funkcji wraz z niezbędnymi

Rozdział 3.  Typy testów

119

modyfikacjami GUI. Załóżmy, że postawione jest wymaganie dodania nowej opcji do listy rozwijanej w menu aplikacji. W którym miejscu programista dostawi nową wartość? Jeżeli w projekcie nie będzie miał literalnego zapisu o lokalizacji, z pewnością doda ją na końcu listy menu. Czy implementując kod, zastanowił się, jak często ta opcja będzie używana? Mam śmiałość wątpić. Może lepszym rozwiązaniem byłoby przesunięcie nowej opcji na jedną z górnych pozycji z uwagi na intensywność jej używania? Testy ergonomii systemu są trudne do wykonywania z racji tego, że testerzy weryfikują aplikacje przeznaczone dla różnych — często zupełnie obcych — branż. Ponadto bardzo trudno odnieść się do ergonomii, pracując (testując) jedynie z nową funkcjonalnością. Ergonomia odnosi się do całego systemu, dlatego też testy „wygody funkcjonalnej” powinny wykraczać poza nowo implementowany obszar. Ignorując tę zasadę, będzie bardzo trudno ocenić, czy nowa opcja, moduł w aplikacji jest utrzymany w przyjętej konwencji. Często zdarzają się sytuacje, gdzie mimo poprawności merytorycznej funkcjonalności zgłaszane są co do niej błędy. Analizując treść owych zgłoszeń, znajdziemy w nich zapisy mniej więcej tego typu:  proszę o zmianę nazwy i kolejności przycisków;  proszę o przesunięcie pola oraz zmianę jego wielkości;  proszę o wprowadzenie listy rozwijanej zamiast wyboru za pomocą obiektu

radio button;  proszę o zmianę wielkości i koloru czcionki;  proszę o przesunięcie pozycji w menu;  proszę o zmianę skrótu klawiszowego do opcji;  proszę o zwiększenie/zmniejszenie grafiki, tabeli itp.;  proszę o zmianę treści i formy komunikatu;  etc.

W praktyce zamówiona opcja działa, ale jej obsługa nie do końca odpowiada użytkownikowi końcowemu. Interfejs użytkownika w pewnych okolicznościach może okazać się niewygodny lub wręcz irytujący w użyciu. Bywa, że przeprowadzenie „silnych” testów ergonomii jest poza zasięgiem możliwości testera. Wynika to z utrwalonych nawyków i przyzwyczajeń kontrolerów jakości dotyczących obsługi aplikacji, braku zrozumienia branży oraz niechęci programistów do wprowadzania zmian „kosmetycznych”. Nieznajomość dziedziny, którą obsługuje oprogramowanie, może wpłynąć na odmienne wyobrażenie funkcjonalności, niż było intencją zamawiającego. Problem pogłębia jawna niechęć programistów do realizowania poprawek zgłaszanych pod flagą ergonomii. Zwykle koder powie: „Przecież działa, nie mam czasu na pierdoły”. Niemałym problemem w zachowaniu spójności jest fakt realizacji kodu przez kilku różnych programistów, gdy każdy z nich ma inne wyobrażenie w kwestii interfejsu użytkownika. Po połączeniu kodu w całość może się okazać, że pod względem ergonomii moduły nie do końca do siebie pasują. Programista A przygotuje komunikaty walidacyjne w kolorze zielonym, a jego kolega na innym formularzu w barwie

120

Testowanie oprogramowania. Podręcznik dla początkujących

niebieskiej. Z kolei inny koder ostrzeżenia wyświetla w tonie czarnym, a cała reszta systemu podaje alerty w kolorze czerwonym i dodatkowo w ramce. Podstawowe testy ergonomii powinny obejmować:  weryfikację, czy wszystkie komunikaty i ostrzeżenia w całym systemie

zachowują spójną konwencję;  weryfikację spójności etykiet;  weryfikację nazewnictwa i układu przycisków, np. czy przyciski Akceptuj

i Anuluj są zawsze w takiej samej kolejności;  weryfikację, czy komponenty, np. lista rozwijana, pola tekstowe, są optymalnie

dobrane i poprawnie zaimplementowane;  weryfikację układu menu;  ocenę skuteczności i przydatności skrótów klawiszowych (ang. hotkey);  ocenę konsekwentnego wdrażania dodatkowych elementów, np. podpowiedzi

dla pól;  weryfikację obsługi klawiszy na formularzach, np. ENTER;  ocenę ułatwień dla użytkownika, np. automatyczne podpowiadanie 00

po separatorze dziesiętnym, sam separator;  ocenę użyteczności wzorców do przeszukiwania danych (filtry);  ocenę potrzeby stronicowania danych;  weryfikację rozkładu elementów i grafiki;  wielokrotne powtarzanie tej samej czynności (wywołanie i użycie tej samej

funkcji);  etc.

Komunikaty zwracane użytkownikowi w wyniku jego interakcji z systemem lub błędów wewnętrznych są nieodzownym elementem aplikacji. Niezmiernie istotne jest utrzymanie jednolitej konwencji prezentacji takowych treści. Załóżmy, że przyjęto, że wszystkie informacje walidacyjne będą prezentowane w kolorze czerwonym, w określonym kroju czcionki bezpośrednio pod polem, którego dotyczą. Ergonomia wymaga, aby wszystkie tego typu zdarzenia były obsługiwane w założony powyżej sposób. Niedopuszczalne jest zmienianie czcionki oraz lokalizacji prezentowania komunikatu. Wszystkie odchylenia od wytycznych należy traktować jako błąd, pomimo że pod względem funkcjonalności wszystko działa. Rysunki 3.72 i 3.73 pokazują dwa formularze tego samego systemu, które w odmienny sposób prezentują komunikaty walidacyjne. Jest to złamanie zasady ergonomii. Równie istotna jest sama treść prezentowanej informacji. Należy pamiętać, że to, co jest zrozumiałe dla umysłu programisty i w pewnym stopniu do zaakceptowania dla testera, dla docelowego użytkownika może być zupełnie niezrozumiałe. Użytkownika nie interesują wyjątki, klasy i inne techniczne problemy. Do raportowania i zbierania danych technicznych powinny służyć logi. Odbiorca docelowy musi otrzymać czytelny i jasny komunikat, który jest w stanie zrozumieć i zinterpretować.

Rozdział 3.  Typy testów

121

Rysunek 3.72. Zachowanie ergonomii dla komunikatów walidacyjnych

Rysunek 3.73. Złamanie zasad ergonomii i estetyki dla komunikatów walidacyjnych

Wystąpił błąd wewnętrzny aplikacji. Spróbuj ponownie za kilka minut. ORA-12154: TNS: could not resolve service name. Powyższa treść komunikatu nigdy nie powinna być ujawniona w interfejsie użytkownika, tj. powinna być „obcięta” o szczegóły techniczne. Spójność nazewnictwa jest istotna z punktu widzenia jakości i kultury obsługi programu i nie znajduje uzasadnienia w trudnościach technicznych. Jeżeli w systemie przyjęto, że polska waluta będzie reprezentowana przez PLN, to nie powinno się zdarzyć, że na jakimś formularzu wystąpi znak zł. Analogicznie należy postąpić z separatorem dziesiętnym i formatem kwoty. W całym systemie musi być zastosowane spójne rozwiązanie. Niedopuszczalne jest równoległe stosowanie znaku kropki (.) i przecinka (,). Rysunek 3.74 i rysunek 3.75 przedstawiają dwa formularze, które we wzajemnej relacji nie zachowują ergonomii. Rysunek 3.74. Formularz doładowania telefonu  zastosowany separator dziesiętny to przecinek

122

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.75. Formularz doładowania telefonu  zastosowany separator dziesiętny to kropka

Kanonem ergonomii systemów informatycznych jest konsekwentna polityka rozmieszczania i nazywania przycisków/odnośników odpowiadających za akcję. Załóżmy, że przyjęta została następująca konwencja w całym systemie dla trzech przycisków (rysunek 3.76):  Anuluj — pozwala na wycofanie się z formularza i anulowanie procesu,

pierwszy od lewej;  Czyść — pozwala na wyczyszczenie danych z formularza, pozycja środkowa;  Zatwierdź — pozwala za zatwierdzenie procesu, trzeci w kolejności.

Zgodnie z powyższym założeniem wszystkie wymienione przyciski powinny być nazwane i zgrupowane w kolejności: Anuluj/Czyść/Zatwierdź. Zatem za niedopuszczalne należy uznać występowanie ich w zmienionym szyku oraz pod odmiennymi nazwami. Wyobraźmy sobie sytuację, kiedy na jednym z formularzy przycisk Anuluj zaimplementowany jest w standardowym miejscu przycisku Czyść (rysunek 3.77). Użytkownik kierujący się nawykiem może użyć środkowego przycisku do zresetowania formularza, a spotka go niespodzianka, gdyż faktycznie wykona akcję anulowania. Teoretycznie system zachował się poprawnie, akcja była wykonana zgodnie z opisem obiektu. Tego rodzaju „pułapki” czyhające na użytkownika mogą konsekwentnie zwiększać jego poziom irytacji. Rysunek 3.76. Ergonomia przycisków akcji

Rysunek 3.77. Złamanie zasad ergonomii

Rozdział 3.  Typy testów

123

Bywa, że zastosowane przez programistę rozwiązanie pomimo poprawnego działania nie jest najszczęśliwiej dobrane pod względem użyteczności. Przyjrzyjmy się formularzowi z rysunku 3.78, gdzie wybór rachunku obciążanego odbywa się za pomocą obiektu radio button. Zasadniczo bardziej korzystnym rozwiązaniem jest wprowadzenie listy rozwijanej. Po pierwsze: w ten sposób zmniejszymy liczbę detali (elementów) strony. Po drugie: z pewnością przyspieszy to obsługę formularza (rysunek 3.79). Strona stanie się bardziej przyjazna pod względem użyteczności. Rysunek 3.78. Wybór rachunku za pomocą obiektu radio button

Rysunek 3.79. Wybór rachunku za pomocą listy rozwijanej

Instytucje mające na uwadze komfort pracy z systemem informatycznym wprowadzają rozwiązania wspomagające jego obsługę. Przykładem jest implementacja mechanizmu dopełniania zerami wartości po separatorze dziesiętnym (dla pól z kwotami). Użytkownik wprowadza wartość 100 lub 100,5, a system automatycznie przekształca ciąg odpowiednio do postaci 100,00 i 100,50 (rysunki 3.80 i 3.81). Spotkałem się z aplikacją funkcjonującą produkcyjnie, która nie wykonuje takiego dopełnienia i po podaniu wartości 100,1 zatrzymuje operację na etapie walidacji danych formularza. Jest to niezwykle irytujące! W sytuacji kiedy system posiada wypracowany mechanizm „dopełniania zerami”, implementowanie go dla wszystkich analogicznych pól zapewnia ergonomię. Przez analogię problem należy przełożyć na pozostałe funkcje ułatwiające obsługę np. podpowiedzi dla pól i opcji formularza, tzw. „helpów”. W przypadku kiedy aplikacja zapewnia określony wachlarz wskazówek dla użytkownika, taki standard powinien być naniesiony również na nowo produkowane moduły/funkcje. Należy wziąć pod uwagę, że użytkownicy zapewne przywykli do tego typu dodatków i z pewnością ich brak zostanie zauważony i oceniony. Powszechnie znaną prawdą jest, że wszelkie zmiany na „gorsze” są niechętnie akceptowane.

124

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek 3.80. Wprowadzenie kwoty z wartością dziesiętną, tj. jedno miejsce po przecinku

Rysunek 3.81. Dopełnienie wartości dziesiętnej na drugiej pozycji po przecinku

Ważnym elementem dla ergonomii systemu jest zagwarantowanie stronicowania danych. Obecnie aplikacje pracują z dużą ilością danych i wyświetlanie kilkuset czy nawet tysięcy wierszy na jednej stronie może okazać się trudne w obsłudze. Nie zawsze potrzeba stronicowania jest oczywista na etapie projektowania. Niemniej jednak w fazie testów i pracy z dużą ilością danych zapewne zostanie ona ujawniona. Niestety często będzie trudno przekonać programistę do wdrożenia omawianego rozwiązania, gdyż nie będzie ono miało literalnego potwierdzenia w wymaganiach (projekcie). Natomiast bezdyskusyjnie stronicowanie winno być powtarzane dla nowo powstałych funkcjonalności, jeżeli zastosowano je w dotychczasowych (konsekwencja). Rozbudowane interfejsy użytkownika umożliwiają przeszukiwanie danych w oparciu o zróżnicowane kryteria. Jak wiadomo, z tych samych danych można uzyskać różne informacje, sterując sekcją WHERE zapytania SQL. W tym celu powoływane są filtry, na których ustala się pożądany wzorzec przeszukiwania i prezentacji danych. Ergonomia wymaga, aby możliwości i układ graficzny tego typu filtru były powtarzane w każdym kolejnym miejscu jego występowania. Potrzeby biznesowe wymagają filtrowania danych w wielu różnych miejscach systemu. Komfort użytkowania takiej funkcji zależy od konsekwencji, z jaką jest ona implementowana. W istocie zdarzają się sytuacje, kiedy filtr z określonej opcji nie pasuje idealnie do drugiej. Niemniej jednak należy zachować maksymalną spójność i analogię we wszystkich instancjach tego rozwiązania na obszarze całego systemu. Większość aplikacji — niezależnie czy internetowych, czy desktopowych — obsługiwana jest przez użytkowników za pomocą kursora i myszy. Niestety programiści i wtórujący im testerzy zapominają o narzędziu, jakim jest klawiatura. Jeżeli w systemie przyjęte zostało, że klawisz ENTER będzie służył do zatwierdzania akcji, czyli działał analogicznie jak dedykowany temu przycisk graficzny, to takie rozwiązanie musi funkcjonować w całej aplikacji. Załóżmy, że wprowadzam kwotę doładowania w polu i zarazem jest to ostatni punkt formularza, a następnie wciskam ENTER. Jako użytkownik spodziewam się, że w ten sposób potwierdziłem formularz i przeszedłem do kolejnego kroku. W innej sytuacji wpisuję kod autoryzacyjny SMS w celu potwierdzenia operacji w serwisie transakcyjnym, a następnie wciskam ENTER. Ponownie oczekuję wykonania akcji zatwierdzenia. Przyjęcie założenia, że ENTER będzie zatwier-

Rozdział 3.  Typy testów

125

dzał akcje, zobowiązuje do konsekwentnego rozpropagowania tego rozwiązania w całym systemie (przypisanie domyślnej akcji do klawisza). Niedopuszczalne jest umożliwienie zalogowania się do systemu przy zatwierdzeniu klawiszem, ale już ENTER przy potwierdzaniu kodem SMS „nie działa”. Kolejnym problemem związanym z obsługą klawiatury są przypisania akcji menu do określonego skrótu klawiszowego (ang. hotkey). Bardzo często zaniedbywana jest pielęgnacja tej funkcji, co przekłada się na wadliwe działanie skrótów klawiszowych w późniejszych wersjach systemu. Częstym błędem jest dublowanie kombinacji klawiszy, tj. jeden skrót przypisany jest do dwóch lub więcej opcji. Flagowym miejscem, na którym „wykładają” się programiści, jest obsługa klawisza TAB. Przy założeniu, że ma on umożliwić poruszanie się pomiędzy polami formularza, to przede wszystkim:  musi on działać poprawnie, tj. faktycznie przenosić kursor po polach formularza,

a może zdarzyć się tak, że w wyniku błędu w kodzie przerzuci użytkownika w zupełnie inne miejsce systemu;  jeżeli działa, to powinien przenosić kursor kolejno po polach, a często w wyniku

modyfikacji formularza (dodawanie nowych elementów) akcja jest zaburzona, gdy równolegle nie utrzymano standardu obsługi klawisza. O problemie ergonomii elementu, jakim jest menu, wspominałem nieco wyżej. Warto nadmienić i przypomnieć, że samo działanie opcji nie wystarcza. Liczy się także przejrzystość, kolejność, estetyka i jednoznaczność nazw/etykiet. Z natury GUI wynika, że zawiera szereg elementów, których graficzne użycie przełoży się na zrealizowanie określonego procesu bez jawnego sterowania nim z wiersza poleceń. Graficzny interfejs użytkownika ułatwia obsługę systemu, tj. ma być funkcjonalny, łatwy, komfortowy i estetyczny (w kontrze do linii komend). Zatem powinien posiadać poprawnie wdrożony layout (ustaloną konstrukcję graficzną). Z reguły tester nie ocenia koncepcji graficznej, lecz jej implementację, np. czy wszystkie ikony są poprawnie wyświetlane, czy elementy nie zachodzą na siebie, czy flagi zmiany języka prawidłowo realizują swoją funkcję etc. Liczba wymaganych punktów kontroli w celu oceny ergonomii aplikacji zależy od jej charakteru i przyjętego standardu, jaki ma spełniać. Utrzymanie estetyki, spójności i konsekwencji w aplikacji to elementy świadczące o dużej dojrzałości zespołu produkcyjnego. Wysoka ergonomia systemu wzbudza zaufanie i daje podstawy do przypuszczeń, że logika programu będzie również dobrej jakości.

3.3. Testy regresywne Testy regresywne to działania polegające na weryfikacji istniejących funkcjonalności systemu, które zostały wytypowane jako potencjalnie zagrożone na skutek modyfikacji kodu. Wspomniana modyfikacja kodu może dotyczyć bezpośrednio wskazanego elementu, np. w wyniku dodawania nowych pól na formularzu, lub elementów pobocznych, pośrednich, np. procedury, która jest wywoływana w jakiejś opcji formularza. Systemy informatyczne rozwijają się na skutek dostosowywania ich funkcjonalności

126

Testowanie oprogramowania. Podręcznik dla początkujących

do bieżących potrzeb biznesowych. Ów rozwój najczęściej polega na dodawaniu nowych opcji, modyfikacji istniejących lub wygaszaniu części funkcji. Testy regresywne mają wykazać, że zmiany dokonane w kodzie nie „uszkodziły” dotychczasowych funkcjonalności. Rozważmy sytuację, w której modyfikowany jest moduł kartoteki klientów, tj. dodajemy kilka nowych walidacji dla istniejących pól formularza oraz implementujemy nowy element, tj. nowe pole. Oprócz weryfikacji działania formularza pod kątem użycia nowych walidacji należy również sprawdzić, czy modyfikacja kodu nie rzutuje negatywnie na globalne działanie kartoteki klienta, np. na dodawanie/edycję danych podmiotu. Reasumując, formularz powinien zachowywać się poprawnie w niezmienionym obszarze działania przy jednoczesnym uwzględnieniu „wprowadzonych nowości”. Testy regresywne polegają na ponownej weryfikacji już przetestowanego fragmentu lub całości aplikacji po wprowadzeniu w niej modyfikacji. Wspomniana modyfikacja może dotyczyć kodu programu lub środowiska, w którym został on osadzony (uruchomiony). Ze względu na powtarzalność wykonywania ścieżek testowych testy regresywne dobrze nadają się do automatyzacji. Testy regresywne można wykorzystywać do oceny i potwierdzania zachowania systemu w aspekcie cech funkcjonalnych, jak i niefunkcjonalnych. Kłopotliwą kwestią nieustannie pozostaje określenie promienia kręgu, jaki testy regresywne powinny zataczać. Prawdą jest, że czynności związane z oceną i weryfikacją stanu oprogramowania kosztują. Naturalnym zachowaniem jest dążenie do optymalizacji kosztów produkcji. Zatem zespół testów powinien liczyć się z naciskami na ograniczenie czasu poświęconego na testy regresywne. Z drugiej strony, wykonanie ich zbyt pobieżnie może spowodować nieporównywalnie większe koszty w sytuacji przeoczenia błędu, który ujawni się bezpośrednio w środowisku produkcyjnym. Aplikacji nie można testować w nieskończoność. Przeszacowanie pracochłonności na testy regresywne również nie jest dobre z punktu widzenia globalnego wyniku ekonomicznego projektu. Ponownie wszystko sprowadza się do oszacowania ryzyka i optymalnego dopasowania rodzaju oraz zakresu testów do sytuacji. Niestety złotego środka w projektowaniu tegoż rodzaju testów nie ma. Wydaje się, że u podstaw sukcesu leży doświadczenie i bardzo dobra znajomość systemu poparta kompetencjami z zakresu użytej technologii. Bywa, że drobne zmiany w kodzie wymagają dużego wysiłku ze strony testów. Zdarzają się też odwrotne sytuacje. Dużą trudnością będzie przekonanie przełożonego o konieczności wykonania testów regresywnych, których czas kilkukrotnie przekracza liczbę roboczogodzin poświęconych na implementację modyfikacji. Niemniej jednak za takim rozwiązaniem może przemawiać duże ryzyko związane z danym elementem (modułem). Doświadczony tester, który dobrze zna i rozumie system, wie, które komponenty są naznaczone dużą „błędogennością”. Podwyższone ryzyko może być wynikiem nie najlepszego wykonania modułu już na etapie powoływania go do życia. Historia pracy, obsługi kodu komponentu także wiele mówi o potencjalnym ryzyku. Wyobraźmy sobie sytuację, w której kod elementu był modyfikowany przez kilku programistów i w dodatku część z nich już nie pracuje w zespole. Przechodzenie źródła kodu przez wiele rąk, szczególnie kiedy jest to skomplikowane rozwiązanie, z pewnością podnosi ryzyko wystąpienia błędu. W tym miejscu należy przypomnieć, że testy białej skrzynki umożliwiają weryfikację utrzymania standardu programowania przez koderów.

Rozdział 3.  Typy testów

127

Złudne, a zarazem zgubne w skutkach może być poczucie, że wprowadzona modyfikacja nie wymaga uruchomienia testów regresywnych. Takie mylne przekonanie może być wynikiem braku widocznych różnic w obsłudze danej funkcjonalności z punktu widzenia użytkownika. W aspekcie GUI wszystko pozostaje „po staremu”, lecz może zmienić się w potężny sposób logika uruchamiana pod warstwą prezentacji np. w celu poprawy wydajności. Standardowo testy funkcjonale mogą się odbyć poprzez porównanie wyników otrzymanych „po staremu” i „po nowemu”. Niemniej jednak jest to niewystarczający test. Zakres prac należy poszerzyć o obsługę mniej typowych i powszechnych sytuacji oraz skrupulatnie przyjrzeć się funkcjonowaniu modułów pobocznych (zależnych). Oczywiście istnieją uzasadnione sytuacje, kiedy można istotnie zmniejszyć testy regresywne lub je w ogóle zarzucić. Załóżmy, że w jakimś pakiecie bazy danych ORACLE dodana jest nowa procedura z silną logiką. Testy regresywne wspomnianego pakietu nie muszą być ultraskrupulatne, gdyż część kwestii sprawdzi kompilator, a pozostałe aspekty można zweryfikować na przykład poprzez porównanie kodu obu wersji pakietu (aktualnego i poprzedniego). Porównanie kodu powinno dać odpowiedź, czy faktycznie dodano tylko nową procedurę, czy może pojawiły się jakieś nowości w innych elementach pakietu. Jeżeli analiza wykaże, że zmodyfikowano „coś jeszcze”, będzie to oznaczało, iż nasz test regresywny „złapał programistę za rękę na gorącym uczynku” i powinien on wytłumaczyć się z zaistniałej sytuacji. Jako ostateczną konkluzję należy przyjąć, że testy regresywne są konieczne, a ich zakres powinien być adekwatny do oszacowanego ryzyka.

128

Testowanie oprogramowania. Podręcznik dla początkujących

Rozdział 4.

Wprowadzenie do projektowania testów Projektowanie testów odnosi się do opracowania przypadków testowych oraz określenia warunków ich wykonania. Testy projektuje się w celu optymalnego zorganizowania pracy, zapewnienia ich wiarygodności i użyteczności w sposób jak najbardziej zbliżony do rzeczywistego użytkowania systemu. Czyli po prostu testy należy zorganizować w sposób jak najmniej kosztowny i jednocześnie zapewniający maksymalne bezpieczeństwo w rozumieniu jakości systemu. W jaki sposób zapewnić taką symbiozę? Otóż poprzez profesjonalne zaprojektowanie testów. Rysunek 4.1 obrazuje przypadek testowy dla kalkulatora, a konkretnie operacji iloczynu wykonanego w technologii REST (wywołanie RESTful). Jako wartości początkowe (x, y) dla weryfikacji operacji mnożenia podano x = 2, y = 2. Czy tak zaprojektowany test jest prawidłowy? Nie, jest to typowo akademicki przykład wadliwie wykonanego testu. Wrócimy do tego przykładu na dalszych kartach niniejszej publikacji. Rysunek 4.1. Test operacji mnożenia dla kalkulatora wykonanego w technologii REST (wywołanie RESTful)

Projektowanie testów Pierwszym krokiem powinno być zidentyfikowanie warunków testowych. Proces ten obejmuje określenie, co testujemy oraz czego potrzebujemy do zrealizowania testów. Oprzyjmy się na przykładzie. Dostajemy do testów nowy moduł już zaimplementowany w istniejącej aplikacji, którą uprzednio mieliśmy okazję weryfikować (rozszerzenie funkcjonalności). Moduł ma prezentować ma w formie graficznej i tabelarycznej zmiany kursu walut. Kursy walut mają być pobierane za pomocą usługi sieciowej (web service). Niezbędne do wykonania testów są:  dostęp do usługi WS lub własny symulator systemu zewnętrznego;  monitor TCP/IP do przechwycenia komunikacji oraz ewentualnego wstrzymania

jej (timeout);

130

Testowanie oprogramowania. Podręcznik dla początkujących  dodatkowy klient usługi, np. SoapUI;  dokumentacja komunikacji pomiędzy systemami, np. WSDL;  dokumentacja analityczna i projektowa implementacji;  analiza i przygotowanie zestawu danych, które klient WS musi otrzymać,

aby w pełni przetestować funkcjonalność;  analiza i alokacja zasobów, np. weryfikacja, czy nie zachodzi potrzeba separacji

testów funkcjonalności od głównego środowiska testów;  rewizja potencjału kompetencji, tj. czy dysponujemy odpowiednio

„wyszkolonymi” testerami i/lub w jaki sposób ewentualne luki merytoryczne uzupełnić. Zajęcie się powyższymi zagadnieniami stanowi pierwszą fazę projektowania testów. Na podstawie zgromadzonych informacji w postaci odpowiedzi na powyższe pytania należy poczynić i wypunktować istotne założenia wstępne, które stanowią podbudowę do rozpoczęcia bardziej szczegółowych prac. Faza pierwsza powinna uzmysłowić, jakie problemy natury technicznej mogą nam sprawić kłopot oraz w jakim stopniu dotychczasowe zasoby (środowisko i ludzie) odpowiadają potrzebom projektu testowego. Kolejny etap projektowania testów poświęcony jest na wyspecyfikowanie przypadków testowych. Wspomniana specyfikacja powinna uwzględniać dane wejściowe, warunki początkowe, oczekiwany rezultat oraz technikę wraz z narzędziami wykonania testu. Pojedynczy przypadek testowy odnosi się do fragmentu aplikacji, np. sprawdzamy treść komunikatu walidacyjnego. Musimy dążyć do wywołania tej informacji, tzn. należy poprowadzić system tak, aby wymusić wyświetlenie komunikatu, który ujawnia się w ściśle zdefiniowanych okolicznościach. Po zdefiniowaniu celu opisujemy sekwencję zdarzeń i czynności w scenariuszu przypadku testowego. Celem przypadku testowego jest stworzenie warunków do potwierdzenia, że wyspecyfikowany fragment (funkcjonalność) systemu działa zgodnie z oczekiwaniem. W analogiczny sposób mnoży się przypadki testowe, tak aby uzyskać zbiór, który w pełni pokryje system testami. Należy dążyć do jak najbardziej wiernego zaprojektowania przypadków, tj. idealne jest osiągnięcie maksymalnego odwzorowania rzeczywistego stanu przypadków użycia uzupełnionego o niestandardowe wariacje. Przypadki testowe powinny wykraczać poza spodziewaną obsługę systemu w warunkach realnego użycia aplikacji. Założenia i treść merytoryczna przypadku są uzależnione od rodzaju wykonywanego testu  mogą to być np. testy bezpieczeństwa, testy funkcjonalne, testy wydajnościowe. Bardzo istotnym elementem jest szczegółowe i jasne wyspecyfikowanie oczekiwanego rezultatu, jaki powinniśmy otrzymać po wykonaniu przypadku. Brak tego opisu może spowodować, że wynik zostanie błędnie zinterpretowany i zaklasyfikowany jako poprawny. Należy pamiętać, że testy mogły zostać zaprojektowane przez inną osobą niż ta, która je wykonuje. Trzecim etapem jest ustalenie kolejności wykonywania przypadków testowych. Jest to krok wręcz strategiczny dla całego procesu testowania. Przypadki testowe powinny być zgrupowane ze względu na obszary systemu, których dotyczą, i konsekwentnie ułożone. Nie mogą być realizowane w sposób przypadkowy, gdyż ostateczny wynik może być osiągnięty z dużym opóźnieniem i/lub jego rezultat nie będzie odpowiadał stanowi faktycznemu aplikacji. Kluczem, według którego ustala się kolejność, są:

Rozdział 4.  Wprowadzenie do projektowania testów

131

 priorytety — pewne części aplikacji nie mogą zostać należycie zweryfikowane,

jeżeli uprzednio nie nabierzemy pewności, że moduły, od których one zależą, funkcjonują prawidłowo (logika działania);  zależności techniczne — istnieją okoliczności, które silnie uzależniają

uruchomienie modułu od innego fragmentu aplikacji  nieefektywne jest rozpoczynanie gruntownych testów modułu drugiego, jeżeli nie jesteśmy przekonani, że element pierwszy spełnia wszystkie założenia techniczne (zależność techniczna, współdziałanie);  ryzyko wystąpienia błędów krytycznych — praktyka wskazuje, że błędy lubią

się kumulować, doświadczony tester jest w stanie „wyczuć”, które obszary winny być weryfikowane jako pierwsze w celu jak najszybszego wykrycia i usunięcia błędów krytycznych (w tym blokujących);  poziom testów, np. regresywny, integracyjny, systemowy.

Testy to czynność, która trwa w czasie. Planując przypadek testowy, należy oszacować czas jego wykonania, tj. jednej iteracji. Lista przypadków testowych powinna być uczciwie zrealizowana. Wymaga to określenia realnego czasu potrzebnego na wykonanie przypadku. Błędem jest zakładanie, że przypadek zakończy się sukcesem po pierwszym wykonaniu. Harmonogram musi zakładać kolejne iteracje, gdyż z pewnością część przypadków będzie musiała być wykonywana wielokrotnie (powtarzana). Niedoszacowanie czasochłonności realizacji ogółu testów dla projektu skutkować będzie przekroczeniem założonego budżetu lub wydaniem produktu o nie do końca pewnej jakości. Niniejszy rozdział poświęcony jest teoretycznym aspektom projektowania testów w oparciu o techniki białej i czarnej skrzynki. Finalnie podsumujemy rozważania omówieniem kilku przykładów praktycznych.

4.1. Projektowanie testu w oparciu o technikę czarnej skrzynki Niniejszy podrozdział poświęcony jest projektowaniu przypadków testowych w oparciu o technikę czarnej skrzynki. Omówię niezmiernie istotne zagadnienie warunków brzegowych, wspomnę o klasach równoważności oraz weryfikacji stanu oprogramowania.

4.1.1. Wartości brzegowe Analizę i ustalenie wartości brzegowych wykonuje się w celu zaprojektowania testu celującego bezpośrednio w krawędzie przedziału (warunku) z uwagi na to, że jest to miejsce szczególnie narażone na błędy. Omówię zagadnienie w oparciu o poniższe przykłady. Aplikacja zawiera filtr, który używa zakresu dat do zawężenia prezentowanych wyników (wyszukiwanie). Załóżmy, że filtr oferuje opcje wyszukiwania: bieżący miesiąc, bieżący rok, poprzedni rok. Trudno sobie wyobrazić, że tester będzie wprowadzał

132

Testowanie oprogramowania. Podręcznik dla początkujących

(generował) co najmniej 365x2 (2 lata — historia) wartości w celu weryfikacji zachowania aplikacji. Bywa, że warunek ilościowy nie jest jedynym i dodatkowo należy zróżnicować dane, co zwielokrotniłoby ilość wierszy. W tym celu należy posłużyć się analizą wartości brzegowych, tzn. maksymalnymi i minimalnymi wartościami, które będą wykorzystywane w decyzyjnym warunku filtra. Za datę wykonania testu przyjmijmy 23.11.2013. Opcje filtra zgodnie z założeniem powinny obejmować przedziały:  „bieżący miesiąc”: — zakładamy, że nie pokazujemy

danych „w przód” od daty bieżącej;  „bieżący rok”: ;  „poprzedni rok”: .

Zastosowanie testu opartego na warunkach brzegowych wymaga przygotowania zestawu minimalnych danych, które ujęte są w tabeli 4.1. Spreparowane dane ze szczególnym naciskiem na krawędzie przedziałów pozwolą na weryfikację logiki bez posiłkowania się dużym wolumenem danych. Tego typu test ma ocenić, jak zachowuje się aplikacja w momentach decyzyjnych. Oczywiście jeżeli naszym zamierzeniem jest dodatkowo sprawdzić dynamikę przeszukiwania oraz formę prezentacji wyniku, powinniśmy uzupełnić zakres i jakość danych. Tabela 4.1. Zakres danych (dat) do wykonania testu w oparciu o klasy równoważności Parametry filtra Daty

bieżący miesiąc

bieżący rok

poprzedni rok

31.12.2011 01.01.2012

01.01.2012

02.01.2012

02.01.2012

15.05.2012

15.05.2012

31.12.2012

31.12.2012

01.01.2013

01.01.2013

02.01.2013

02.01.2013

25.06.2013

25.06.2013

31.10.2013

31.10.2013

01.11.2013

01.11.2013

01.11.2013

02.11.2013

02.11.2013

02.11.2013

23.11.2013

23.11.2013

23.11.2013

24.11.2013

24.11.2013

25.11.2013

Idealnym przedmiotem rozważań o klasach równoważności są typowe przedziały liczb. Sklepy internetowe lub serwisy aukcyjne oferują funkcjonalność filtrowania przedmiotów, których cena znajduje się w zadanym przedziale (leżącym w zaintere-

Rozdział 4.  Wprowadzenie do projektowania testów

133

sowaniu klienta). Załóżmy, że interesują nas przedmioty od 250 zł do 1200 zł. Zanim taka opcja zostanie udostępniona produkcyjnie, powinna być poddana weryfikacji. Idealnie do tego celu nadaje się test warunków brzegowych. Tabela 4.2 zawiera zestaw danych, który pozwoli na zrealizowanie tego typu testu. Tabela 4.2. Zestaw danych do testu warunków brzegowych od 250 do 1200

Kwota 249,99 250,00

250,00

250,01

250,01

500,10

500,10

1130,98

1130,98

1199,99

1199,99

1200,00

1200,00

1200,01

Pełny test wartości granicznych musi obejmować wartości: pierwszą niepoprawną, poprawną, drugą poprawną, przedostatnią poprawną, ostatnią poprawną, pierwszą błędną z drugiej strony (rysunek 4.2). Reasumując: określamy przedział , gdzie x i y to poprawne wartości graniczne. Test powinien obejmować wartości: Rysunek 4.2. Analiza warunków brzegowych

W ramach podstawowego kursu ISTQB poruszane jest zagadnienie „klasy równoważności”, gdzie dane o podobnych cechach i charakterze grupuje się. To grupowanie oparte jest na mniemaniu, że dane zbliżone, pokrewne będą obsługiwane przez system w analogiczny sposób. Na tej podstawie tworzy się zarówno wartości poprawne, jak i niepoprawne, które użyte zostaną jako reprezentanci grupy (klasy równoważności). Poprawna klasa równoważności to taka, która powinna zostać przepuszczona przez system. Natomiast niepoprawna klasa równoważności powinna być odrzucana. Analizę wartości brzegowych można traktować jako rozszerzenie podziału na klasy równoważności.

134

Testowanie oprogramowania. Podręcznik dla początkujących

4.1.2. Przejścia pomiędzy stanami Niegdyś testowanie przejść pomiędzy stanami systemu traktowałem z przymrużeniem oka. Zatem nie czyniłem większych wysiłków, aby postrzegać system przez pryzmat stanów, jakie przyjmuje. Sytuacja diametralnie uległa zmianie, kiedy zauważyłem bardzo irytujący błąd w oprogramowaniu nowo nabytego telewizora. Oprogramowanie wbudowane np. w automatyce przemysłowej powinno podlegać tego typu testom. Niemniej jednak tego rodzaju testy nie są wyłącznie zarezerwowane dla kodu zaszytego w urządzeniach. Z powodzeniem można planować przypadki np. dla aplikacji internetowych. Wracając do telewizora. Oferuje on standardową funkcję przeskoku do interesującego nas kanału poprzez podanie jego numeru, np. z 4 na 46. Owa opcja działa znakomicie, o ile nie pomylę się we wpisywaniu wartości lub nie zmienię zdania w trakcie. Załóżmy, że zamierzam skoczyć do kanału 57. Wybieram opcję na sterowniku (pilocie) i wpisuję 57. Jednak zanim potwierdzę akcję lub telewizor sam sfinalizuje zadanie, wycofuję się z opcji (zamykam funkcję). Za kilkanaście sekund ponownie wywołuję tę samą opcję w celu wybrania jednego z dalszych kanałów. Niestety czeka mnie rozczarowanie, gdyż oprogramowanie (telewizor) nie zmieniło stanu, tj. nie wyzerowało uprzednio wprowadzonych wartości i powtarza domyślnie wartość 57. Funkcja skoku umożliwia podanie czterech liczb (np. 5 7 _ _). W związku z brakiem możliwości edycji już wprowadzonych muszę dopisać jeszcze dwie (dopełnić do czterech, np. 5 7 1 1), aby zresetować wszystkie pozycje (_ _ _ _) i wpisać interesującą wartość, np. 27 (2 7 _ _). To był punkt zwrotny, w którym wzmocniłem czujność i zacząłem przywiązywać większą wagę do stanu, jaki przyjmuje aplikacja. Na rysunku 4.3 przedstawiłem domniemany algorytm działania omawianej funkcji. Spodziewałem się, że w chwili wycofania się z opcji w momencie wprowadzania numeru kanału oprogramowanie zmieni stan na pierwotny, tj. wyzeruje wartości tak, aby po ponownym wywołaniu funkcji można było bez przeszkód wpisać nową wartość. Tak się niestety nie dzieje. Jest to o tyle ciekawe, że w momencie pełnej ścieżki, tj. przeskoku do wybranego kanału, stan jest zerowany. Dodatkowym powodem do irytacji jest brak możliwości edycji poszczególnych pozycji, tzn. po wprowadzeniu 5 7 _ _ nie można cofnąć się i zmienić np. na 5 8 _ _. Rysunek 4.3. Przykładowy diagram zmiany stanu dla funkcji wybierania numeru kanału w telewizorze

Rozdział 4.  Wprowadzenie do projektowania testów

135

Projektując testy uwzględniające przejścia pomiędzy stanami, należy starać się pokryć stosunkowo wiernie ścieżki użytkowania oprogramowania. Testy powinny również wyeliminować problemy martwego kodu i zapętleń.

4.1.3. Projektowanie testu w oparciu o przypadki użycia Przypadki użycia (ang. use cases) są powszechnie wykorzystywane do opisu wymagań funkcjonalnych. Odnoszą się one do interakcji pomiędzy systemem a użytkownikiem i są opisem sekwencji kroków (zdarzeń). Przypadek użycia powinien definiować główną (najbardziej optymalną) ścieżkę obsługi bez wnikania w aspekty implementacji. Oprócz głównego przebiegu akcji możliwe jest zdefiniowanie ścieżki alternatywnej (mniej prawdopodobnej, choć przypuszczalnej). Przypadek użycia musi opisywać, w jaki sposób aplikacja powinna być obsługiwana (używana), aby osiągnąć zadany cel. Przykład przypadku użycia (przebieg główny): 1. Użytkownik loguje się do serwisu transakcyjnego za pomocą loginu i hasła. 2. Użytkownik zostaje zalogowany. 3. Wybiera opcję doładowanie telefonu. 4. Wprowadza kwotę doładowania, numer telefonu oraz wybiera numer

rachunku obciążanego. 5. Zatwierdza operację przyciskiem Doładuj (krok 1. z 3). 6. Weryfikuje dane (krok 2. z 3). 7. Autoryzuje transakcję kodem SMS (krok 3. z 3). 8. Transakcja zostaje przyjęta. 9. Wylogowanie manualne (przez użytkownika).

Przykład przypadku użycia (ścieżka alternatywna 1): 8a. Autoryzacja się nie powiodła. 8a1. Powrót do kroku 7. (druga próba autoryzacji).

Przykład przypadku użycia (ścieżka alternatywna 2): 9a. Wylogowanie automatyczne (długa bezczynność użytkownika).

Testy zaprojektowane w oparciu o przypadki użycia znajdują bardzo dobre zastosowanie podczas wykonywania testów akceptacyjnych. Definicja testów jest bardzo zbliżona do rzeczywistej obsługi systemu i przepływu procesów biznesowych w realnych warunkach. Tego rodzaju testy pomagają w weryfikacji aplikacji w ujęciu całościowym, tj. „uczciwej” obsługi biznesu. Wyszczególnienie przypadków użycia daje pogląd, „jak” dana aplikacja będzie używana, z jakimi typowymi ścieżkami będzie musiała się zmagać w sposób ciągły.

136

Testowanie oprogramowania. Podręcznik dla początkujących

4.2. Projektowanie testu w oparciu o technikę białej skrzynki Testowanie instrukcji kodu polega na uruchomieniu każdej instrukcji przynajmniej raz. Pokrycie instrukcji kodu odnosi się do procentu uruchomionych instrukcji w wyniku wykonania przypadków testowych. Instrukcja to najmniejszy samodzielny fragment kodu, który jest wykonywalny. Listing 4.1. Przykład nr 1 do analizy pokrycia instrukcji kodu package pl.testowanie; public class TBookKod { public static void main(String args[]) { int a = 1; int b = 0; int zc; if (a > b) { zc = 100; } else { zc = 200; } if (a == 1) { zc = 300; } } }

Przypadek testowy dla powyższego programu (listing 4.1), w którym parametry a oraz b zainicjowane są wartościami odpowiednio 1 i 0, nie gwarantuje pełnego pokrycia instrukcji. Wskazane wartości wejściowe pokryją 4 z 6 instrukcji (linii kodu). Rysunek 4.4 prezentuje wynik wykonania instrukcji z omawianego listingu. Porównanie zapisu instrukcji z rezultatem uwiarygodni wywód, które linie programu zostały wykonane (pokryte). W celu zapewnienia 100% pokrycia testy należy uzupełnić o dodatkowy przypadek, który uruchomi blok else pierwszej instrukcji warunkowej if. W tym celu można zainicjować program z wartościami a=2, b=5. Analizę pokrycia instrukcji oprzyjmy na jeszcze jednym przykładzie. Przyjrzyjmy się pseudoinstrukcjom na listingu 4.2. Uruchomienie bloku instrukcji z parametrem a=10 spowoduje wykonanie 80% instrukcji programu. Drugie uruchomienie z wartością a=1 pokryje pozostałe instrukcje.

Rozdział 4.  Wprowadzenie do projektowania testów

137

Rysunek 4.4. Źródło programu wraz z rezultatem działania przy zadanych wartościach a = 1, b = 0 Listing 4.2. Przykład nr 2 do analizy pokrycia instrukcji kodu package pl.testowanie; public class TBookKod2 { public static void main(String args[]) { int a = 10; int zc; int xc; int vc; String wynik = "Wynik to: "; if (a == 1) { zc = 100; } else { a = a * 5; zc = 200; xc = 500; vc = a - zc; vc = vc + xc; wynik = wynik + Integer.toString(vc); System.out.print(wynik); } } }

138

Testowanie oprogramowania. Podręcznik dla początkujących

Testowanie decyzyjne polega na weryfikacji zachowania aplikacji w momencie osiągnięcia punktu decyzyjnego w kodzie. Decyzja to miejsce w kodzie, które wpływa na sterowanie, tzn. posiada co najmniej dwie alternatywne drogi, których wybór uzależniony jest od wartości parametrów wejściowych. Warunki logiczne mogą mieć charakter złożony, to znaczy taki, w którym występują więcej niż dwie ścieżki przebiegu. Pojedynczy warunek musi być przetestowany dwa razy. Raz „na prawdę”, drugi raz „na fałsz”. Pokrycie w 100% decyzji zapewnia pełne pokrycie instrukcji kodu. Listing 4.3 zawiera przykładowy kod do analizy pokrycia decyzji. W celu pełnego pokrycia decyzji przy minimalnej ilości przypadków można zastosować rozwiązanie, gdzie a=100 i b=40, plus drugie wywołanie a=1 i b=30. Oba przypadki zagwarantują 100% pokrycia decyzji. Pierwszy z nich wymusi fałsz/prawda, a drugi prawda/fałsz. Listing 4.3. Przykład nr 1 do analizy pokrycia decyzyjności package pl.testowanie; public class TBookKod3 { public static void main(String args[]) { int a = 100; int b = 40; int zc; String wynik = "Wynik to: "; if (a == 1) { zc = 100; wynik = wynik + Integer.toString(zc); System.out.print(wynik); } if (a - b > 1) { zc = 200; wynik = wynik + Integer.toString(zc); System.out.print(wynik); } } }

Przypadki testowe realizowane w ramach testów pokrycia (ang. coverage test) modelowane mogą być w oparciu o grafy przepływu sterowania (ang. Control Flow Graph — CFG). Graf (ang. graph) to graficzna reprezentacja relacji zachodzących pomiędzy poszczególnymi elementami programu. Listing 4.4 przedstawia źródło programu, które zostało odwzorowane na grafie z rysunku 4.5. Listing 4.4. Kod programu, który posłuży do przygotowania grafu przepływu sterowania package pl.testowanie; public class TBookGraph { // Start public static void main(String args[]) { // Krok 1 int a = 12; int zc; // Krok 2 if (a >= 100) { zc = -5;

Rozdział 4.  Wprowadzenie do projektowania testów // Krok 3 } else { // Krok 4 switch (a) { case 0: zc = 0; break; case 1: zc = 1; break; default: zc = 5; } } // Krok 5 System.out.print(zc); } } // Koniec

Rysunek 4.5. Graf przepływu sterowania przygotowany w oparciu o listing 4.4

139

140

Testowanie oprogramowania. Podręcznik dla początkujących

4.3. Projektowanie testu w oparciu o doświadczenie testera Brak lub niekompletność dokumentacji stosunkowo często doskwiera testerowi w momencie projektowania testów. Zbudowanie zestawu przypadków testowych oparte jest na informacjach uzyskanych z nieformalnych rozmów z programistami, analitykami i projektantami funkcjonalności. Nieformalnie zdobyta podstawa merytoryczna zwykle jest uzupełniana o informacje z dotychczasowej dokumentacji (może być nieaktualna) i/lub szczątkowy materiał analityczno-projektowy. Niemniej jednak wiodącym elementem jest podpieranie się doświadczeniem i intuicją testera. Testy zaprojektowane według tej metody w momencie ich zastosowania mogą nie w pełni odpowiadać rzeczywistym potrzebom. Oznacza to, że mogą one pomijać pewne obszary, które zostały dodane w momencie implementacji, a w czasie „wywiadu” nie były poruszone. Kolejnym problemem mogą się okazać różnice pomiędzy zapisami testu a faktyczną funkcjonalnością, np. programista wykonał implementację inaczej, niż zapowiadał. Dużym problemem jest swoboda w interpretacji założeń i brak formalnego powiązania (odpowiedzialności) na linii programista – tester. Ostateczna efektywność testów zaprojektowanych tą metodą może być niezadowalająca. Nieformalne projektowanie testów może posłużyć do szacunkowego określenia kosztu realizacji testów. Niemniej jednak należy pamiętać o ryzyku, jakie niesie ta metoda. Wartością dodaną metody eksploracyjnej jest możliwość poddania aplikacji testom przez osoby, które wcześniej nie miały styczności z systemem i uprzednio nie zapoznały się z dokumentacją. Nieporadna obsługa aplikacji może ujawnić problemy, które nie byłyby możliwe do wykrycia przez osoby mające „poukładaną” sekwencję zdarzeń. Niestety działa to również w drugą stronę. Testy poprzez „atak” na program z dużym prawdopodobieństwem nie pokryją w pełni wszystkich funkcjonalności. W rzeczywistości tester projektuje i wykonuje przypadek w czasie rzeczywistym. Otwiera pewną funkcjonalność i stara się jej użyć zgodnie z tym, co podpowiada mu doświadczenie i intuicja. Metoda eksploracji systemu może posłużyć do odtworzenia dokumentacji, np. opisu GUI.

4.4. Przypadki testowe w ujęciu praktycznym Niestety nie istnieje złoty środek, za pomocą którego można szybko i obszernie opanować sztukę projektowania przypadków testowych. Oczywiście punktem wyjścia jest opanowanie elementarnych podstaw teoretycznych, które stanowią podbudowę do planowania testów. W praktyce najdoskonalsze efekty uzyskuje się poprzez zastosowanie wypadkowej wynikającej z doświadczenia, teorii, znajomości użytej technologii oraz intuicji. Chcę powiedzieć przez to, że tester, a zatem i opracowane przez niego

Rozdział 4.  Wprowadzenie do projektowania testów

141

przypadki testowe rozwijają się i dojrzewają wraz z systemem. Czytelnicy, którzy liczyli, że w tej publikacji zaznajomią się ze sztuką projektowania uniwersalnych przypadków testowych, mogą czuć się w pewnej mierze nieusatysfakcjonowani. Moim celem jest wskazanie, jak ważne jest poprawne zaprojektowanie przypadku testowego i jakie konsekwencje wiążą się z błędnie przygotowanym planem testów. Zrozumienie wagi problemu stanowi istotę zagadnienia. Zwykle początkujący tester lub tester rozpoczynający pracę z zupełnie nowym systemem korzysta z przypadków testowych wykonanych przez osobę bardziej znającą system. Taka kolej rzeczy umożliwia stosunkowo płynne nauczenie się nowej aplikacji (testować zapewne już umiemy). Jeżeli potrafimy testować i znamy system, to z pewnością zostaniemy poproszeni również o projektowanie testów w niedalekiej przyszłości. Poniżej przytoczę kilka przykładów (sytuacji), które z pewnością naświetlą powagę problemu i uwiarygodnią mój wywód.

Przykład 1. Błędnie zaprojektowany przypadek dla operacji iloczynu Rozdział rozpocząłem od podania w wątpliwość poprawności testu dla jednej z operacji kalkulatora matematycznego. Precyzując: chodzi o operację iloczynu. Jest to przykład często przytaczany w literaturze. Wynika to z tego, że bardzo precyzyjnie i jednoznacznie uwypukla sedno problemu. Najprostszy przypadek weryfikacji operacji mnożenia powinien uwzględniać dwie liczby całkowite, które będą stanowiły wartości wejściowe. W sytuacji kiedy podamy je odpowiednio 2 i 2, czyli założymy wykonanie operacji 2 * 2 i za spodziewany wynik uznamy liczbę 4, ten test nie może być uznany za prawidłowy. Dlaczego? Otóż ten sam wynik może (powinna) zwrócić operacja sumowania, tj. 2 + 2 = 4. Jeżeli system zwróci 4, to pomimo że zaznaczyliśmy (wybraliśmy) opcję mnożenia, nigdy nie mamy pewności, czy instrukcje programu napisane są prawidłowo. Przyjrzyjmy się listingowi 4.5, który prezentuje kod źródłowy hipotetycznego kalkulatora wykonanego w technologii REST. Listing 4.5. Kod źródłowy kalkulatora (z błędem w operacji mnożenia) package kalkulator_rest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @Path("/policz") public class Kalkulator { String wynik="Wynik operacji dodawania (suma) to: "; String wynik2="Wynik operacji mnożenia (iloczyn) to: "; @GET @Path("{x}+{y}") public String dodawanie( @PathParam("x") int arg1, @PathParam("y") int arg2){ int w=arg1+ arg2; wynik=wynik+Integer.toString(w); return wynik; } @GET @Path("{x}-{y}")

142

Testowanie oprogramowania. Podręcznik dla początkujących public int odejmowanie( @PathParam("x") int arg1, @PathParam("y") int arg2){ return arg1-arg2; } @GET @Path("{x}*{y}") public String mnozenie ( @PathParam("x") int arg1, @PathParam("y") int arg2){ // Błąd w kodzie dla operacji iloczynu // Prawdopodobnie w wyniku kopiowania kodu z metody dodawanie int w=arg1+arg2; wynik2=wynik2+Integer.toString(w); return wynik2; } @GET @Path("{x}/{y}") public int dzielenie( @PathParam("x") int arg1, @PathParam("y") int arg2){ return arg1/arg2; } }

Metoda mnozenie zawiera błąd. Pomimo wskazania, że intencją użytkownika jest wykonanie iloczynu, realizuje ona operację dodawania (rysunek 4.6). Tak prosty błąd mógł powstać np. poprzez kopiowanie kodu przez programistę, który zmienił wywołanie URL (@Path("{x}*{y}")dla metody, ale zapomniał zmodyfikować implementację logiki (int w=arg1+arg2;). Wracając do pierwotnie założonego testu 2 * 2 = 4, przyjrzyjmy się dwóm kolejnym rysunkom: 4.7 oraz 4.8. Wykonaliśmy operacje odpowiednio sumowania i mnożenia. Oba rezultaty realizacji żądania to 4 na skutek podania parametrów wejściowych 2 i 2. Nie wiemy, czy pod wywołaniem operacji sumowania kryje się faktycznie dodawanie i na odwrót (pomijając pomocniczą etykietę, którą również zwraca WS). Rysunek 4.6. Kalkulator — operacja mnożenia z błędem

Rysunek 4.7. Kalkulator — operacja dodawania

Rozdział 4.  Wprowadzenie do projektowania testów

143

Rysunek 4.8. Kalkulator — operacja mnożenia

Niemniej jednak modyfikując wartości początkowe, na przykład wprowadzając 2 * 3, z łatwością zweryfikujemy, czy operacja działa właściwie. Spodziewany wynik to 6, a funkcja zwraca 5 (rysunek 4.9). Zatem skuteczność testu zależy od prawidłowo zaprojektowanego przypadku testowego. Powyższa sentencja może stanowić mantrę dla każdego testera. Rysunek 4.9. Kalkulator — poprawne dane wejściowe (iloczyn)

Przykład 2. Błąd w kalkulatorze dopuszczonym do sprzedaży — kupiłem i wykryłem Pozostając w tematyce kalkulatorów, przytoczę przykład (błąd), jaki zauważyłem we własnym urządzeniu. Rozpakowałem „sprzęt” nabyty za kilkanaście złotych i przystąpiłem do badania jego możliwości. Nie spodziewałem się niczego nadzwyczajnego po tym sprzęcie, ale także nie podejrzewałem, że będzie zawierał tak „głupi” błąd. Zdawało mi się, że układy elektroniczne do liczydeł są tak powszechne, że rzeczą naturalną jest ich poprawne działanie. A tu taka niespodzianka. Mój kalkulator pozwala na wprowadzanie ciągu, tj. liczby z wieloma kropkami, np. 40.0.0…0. Nietrudno się domyślić, że wszelkie obliczenia kończą się wyjątkiem (błędem). Irytuje mnie to niezmiernie, gdyż za każdym razem w wyniku mojej nieuwagi tracę całe obliczenie. Kara jest niewspółmierna do winy, niestety. W tak prostych urządzeniach — tzn. pod względem interfejsu i interakcji z użytkownikiem — można było się pokusić o wyłączenie wstawiania drugiej kropki w podstawowym trybie obliczeń. Powyższy przykład uwiarygodnia tezę, że błędy mogą wystąpić zawsze.

Przykład 3. Testy zachowania aplikacji w momencie utraty kooperacji z innym systemem Systemy, które wymagają współpracy z innymi aplikacjami, powinny być poddane weryfikacji pod kątem ich zachowania w momencie utraty „kontaktu” ze zdalnym obiektem. Wspomniana utrata łączności może nastąpić przed próbą użycia zdalnej funkcjonalności. W takim przypadku zwykle użytkownik otrzymuje stosowny komunikat, np. funkcjonalność w chwili obecnej jest niedostępna. Niestety dużo gorszą sytuacją jest przerwanie komunikacji w momencie realizacji procesu biznesowego, np. pobieramy dane z WS do wygenerowania jakiegoś raportu. Użytkownik wybrał opcję generuj raport. Aplikacja wysłała żądanie, odebrała odpowiedź i ponownie wysłała żądanie do innej funkcji WS. Niestety drugi krok nie mógł zostać zrealizowany ze względu na

144

Testowanie oprogramowania. Podręcznik dla początkujących

przerwaną komunikację ze zdalną usługą sieciową. Pytanie, jak zachowa się aplikacja w takich okolicznościach? Trzeba to sprawdzić i spreparować podobny scenariusz.

Przykład 4. Walidacja dopuszczalnych znaków w polu formularza Formularze zwykle posiadają stosunkowo silną walidację wprowadzanych danych, tj. wykluczają pewne znaki ze względów bezpieczeństwa i na potrzeby zachowania spójności z innymi systemami etc. Zatem konieczność weryfikacji tego rozwiązania staje się nieunikniona. Załóżmy, że w specyfikacji podano, że pole numer telefonu nie dopuszcza następujących znaków: ’|~{}{}?/;\. Czy test, w którym wprowadzimy od razu cały wykluczany ciąg, będzie poprawny (rysunek 4.10)? Nie wydaje mi się z dwóch powodów. Po pierwsze: w ten sposób nie dowiemy się, jak aplikacja reaguje na poprawne dane, tzn. czy potrafi prawidłowo przeszukać cały ciąg w poszukiwaniu zakazanych znaków. Po drugie: nie możemy mieć pewności, czy oprogramowanie wyłapuje wszystkie znaki. Jeżeli nie mamy wglądu w kod źródłowy, a komunikat walidacyjny jest niezwykle lakoniczny, np. wprowadzono niepoprawne znaki/dane, to skąd wiemy, czy walidacja zadziała tak samo dla każdego ze znaków — zakładam, że po wykryciu pierwszego następuje przerwanie weryfikacji i zwrot komunikatu walidacyjnego. Nieco bardziej kulturalnym i cywilizowanym rozwiązaniem jest wypisanie w „zwrocie”, jakie zabronione znaki wykryto. W takim przypadku podanie od razu pełnego wykluczanego ciągu nabiera większego sensu. Weryfikacja wspomnianego wymagania powinna uwzględniać przypadki z ciągiem znaków mieszanych (błędne i poprawne), tylko błędne, tylko poprawne oraz jeżeli jest to wymagane  jednostkowe (pojedyncze) testy dla każdego z zabronionych znaków. Rysunek 4.10. Test walidacji wprowadzanych danych — ryzykownie zaprojektowany

Niestety formularze nie ograniczają się do jednego pola, a i system zwykle posiada ich wiele dla różnych procesów biznesowych. Sytuacja jest o tyle ciekawa, że często reguły walidacyjne są propagowane na inne analogiczne pola. Jeżeli czujemy się na siłach, możemy podjąć próbę oceny, czy owe walidacje realizowane są w oparciu o jedną i tę samą metodę (fragment kodu). Możemy również zaufać informacjom płynącym od programisty. Niemniej jednak jeżeli nabierzemy wystarczającego zaufania do silnika tych reguł, to testując kolejne pola, możemy złagodzić rygor testowy.

Przykład 5. System rezerwacji imprezy turystycznej online Jednym z poważniejszych błędów w systemie produkcyjnym, który udało mi się wykryć jako użytkownik, była wada systemu rezerwacji imprezy turystycznej. Problem dotyczył obliczania kosztów wycieczki przy uwzględnieniu wieku dziecka. Załóżmy, że za dziecko do lat dwóch (w momencie powrotu) wnosi się jedynie opłatę za ubezpieczenie. Powyżej tego wieku koszty znacznie wzrastają.

Rozdział 4.  Wprowadzenie do projektowania testów

145

Kalkulowałem koszty dla dwóch osób dorosłych i dziecka (2+1), które w momencie powrotu miałoby 2 lata i 6 miesięcy. System po podaniu daty urodzenia dziecka nie wiadomo czemu klasyfikował je w przedziale testsumy.sha1 [uzytkownik@KOMPUTER ~]$ cat testsumy.sha1 93202a39a12797be912fff4609bd7db65d225ed5 testsumy.txt

Algorytm MD5 (ang. Message-Digest algorithm 5) — przykład na listingu A.2. Listing A.2. Generowanie sumy kontrolnej według algorytmu MD5 [uzytkownik@KOMPUTER ~]$ md5sum testsumy.txt > testsumy.md5 [uzytkownik@KOMPUTER ~]$ cat testsumy.md5 14e19ce8e1699633d4a5f33db19b2f1d test sumy.txt

Dodatek B

Membrane SOAP Monitor Membrane Monitor to narzędzie, które pozwala na przechwytywanie wiadomości HTTP oraz SOAP, a co najważniejsze  oferuje możliwość zatrzymania komunikatu np. w celu jego modyfikacji. Aplikację można pobrać ze strony domowej projektu: http:// www.membrane-soa.org/soap-monitor/. Wspomniane narzędzie wspiera obsługę SSL. Wspominałem wcześniej, że bywają sytuacje, kiedy testy wymagają wglądu w treść komunikatów wymienianych pomiędzy klientem a serwerem, np. w przypadku braku fizycznego dostępu do symulatora usługi sieciowej (logi, konfiguracja odpowiedzi itp.). W takiej sytuacji projektowane testy są istotnie zależne od możliwości zaślepki. Z założenia są one mocno ograniczone i nie zawsze zaspokoją nasze potrzeby. Narzędzie Membrane SOAP Monitor może poszerzyć możliwości wykonywania testów poprzez przechwycenie odpowiedzi serwera i manipulację treścią wiadomości, np. zmianę wartości w tagach. Oczywiście możliwa jest również odwrotna sytuacja, tj. przechwycenie i zatrzymanie żądania do serwera. Po uruchomieniu aplikacji wybieramy opcję Add Proxy. Ukaże się kreator, który poprowadzi nas przez wstępną konfigurację (rysunek B.1). Po ustawieniu podstawowych parametrów powrócimy do głównego okna narzędzia (rysunek B.2). Za pomocą przycisku Edit wywołamy opcję dodatkowej konfiguracji nasłuchu (rysunki B.3, B.4, B.5). Na zakładce Proxy Key definiujemy numer portu, na którym będzie nasłuchiwać monitor, oraz określamy hosty, dla których będzie przechwytywana komunikacja. Kolejna karta Target służy do zdefiniowania akcji, czyli tego, gdzie monitor ma przekierować ruch po przechwyceniu wiadomości (docelowy adres serwera). Zakładka Actions umożliwia wybranie momentu blokady komunikacji (zapytanie, odpowiedź).

190

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek B.1. Membrane — kreator konfiguracji

Rysunek B.2. Membrane — główne okno programu

Dodatek B  Membrane SOAP Monitor Rysunek B.3. Membrane — dodatkowa konfiguracja nasłuchu A

Rysunek B.4. Membrane — dodatkowa konfiguracja nasłuchu B

191

192

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek B.5. Membrane — dodatkowa konfiguracja nasłuchu C

Konfiguracja na powyższych ilustracjach realizować będzie założenie: przechwytywanie ruchu dla wszystkich hostów (*) na porcie 3128. Po wychwyceniu wiadomości ma ona zostać skierowana na adres 127.0.0.1 i port 8088. Jednak zanim nastąpi zmiana trasy wiadomości, należy ją odblokować, gdyż włączona jest opcja Block Request. Odblokować (opcja Continue), tzn. dokonać ewentualnej edycji i „popchnąć” manualnie dalej (rysunek B.6). Z punktu widzenia testów klienta usług sieciowych bardziej interesującą opcją jest blokowanie odpowiedzi nadchodzącej z serwera w celu ewentualnej modyfikacji treści. Niejednokrotnie wspominałem, że symulatory z reguły mają ograniczoną logikę biznesową. Membrane Monitor umożliwia ingerencję w treść komunikatu (rysunki B.7 i B.8). Rysunek B.9 prezentuje podgląd treści zmodyfikowanej odpowiedzi (tag ). Należy zwrócić uwagę na adres endpoint, na który było wysłane żądanie (127.0.0.1:3128). Jest to adres monitora, który przechwycił wiadomość i przesłał ją do serwera usługi sieciowej pod adresem 127.0.0.1:8088.

Dodatek B  Membrane SOAP Monitor

Rysunek B.6. Membrane — blokowanie przechwyconej wiadomości (Request) Rysunek B.7. Membrane — blokowanie odpowiedzi z serwera (response)

193

194

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek B.8. Membrane — edycja przechwyconej wiadomości (response)

Rysunek B.9. SoapUI — podgląd zmodyfikowanej odpowiedzi

Dodatek C

Wireshark — analizator ruchu sieciowego Wireshark to potężne i wysoko specjalistyczne narzędzie służące do analizowania ruchu sieciowego. Przystosowanie tego programu do własnych potrzeb może okazać się kłopotliwe, o ile ktoś pierwszy raz ma styczność z tą aplikacją. Stwarza ona wrażenie bardzo skomplikowanej, co nie jest bezpodstawne, biorąc pod uwagę większość możliwości drzemiących w tym narzędziu. Program można pobrać ze strony domowej projektu: http://www.wireshark.org/. Pozwolę sobie ograniczyć się w tej książce jedynie do przedstawienia tego narzędzia. Rysunek C.1 prezentuje główne okno programu. Podstawowe czynności to skonfigurowanie nasłuchu, np. TCP or UDP port 80 (rysunek C.2), oraz wybranie protokołów, które mają być analizowane (rysunek C.3). Rysunek C.1. Wireshark — główne okno programu

196

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek C.2. Wireshark — konfiguracja nasłuchu A

Rysunek C.3. Wireshark — konfiguracja nasłuchu B

Bogactwo możliwości może okazać się przeszkodą w dostosowaniu narzędzia do potrzeb testów. Niemniej jednak dobrze mieć w zanadrzu oprogramowanie wspomagające testy o różnym poziomie zaawansowania. Wireshark wspiera SSL.

Dodatek D

Generowanie danych testowych Nieuchronnie każdy tester stanie przed koniecznością posłużenia się dużą ilością danych o jakości (wartości) zbliżonej do rzeczywistych danych, jakie będą przepływać przez system. Przy profesjonalnym podejściu do procesu weryfikacji oprogramowania należy sprzeciwiać się pracy wyłącznie na danych o „śmieciowej” wartości, tj. takich danych, które nie niosą wymiernej informacji, a jedynie stanowią wypełniacz bazy danych. Bardzo szczegółowe i specyficzne testy znacznie łatwiej jest prowadzić przy użyciu poważnie zawężonego zakresu danych. Nie ma nic złego w takim podejściu, gdyż wspomniany stan rzeczy ułatwia weryfikację wyniku oraz ewentualną analizę problemu. Niemniej jednak kiedy już nabierzemy wystarczającego zaufania do logiki (biznesu) testowanej funkcjonalności, warto pokusić się o poszerzenie testów w ujęciu masowego przetwarzania danych. Zapewne nie wszystkie obszary systemu będą wymagały takiej weryfikacji. Niemniej jednak funkcjonalności związane z importem i eksportem danych, generowaniem i prezentacją raportów, stronicowaniem danych, przetwarzaniem masowym w systemach korowych etc. powinny podlegać testom uwzględniającym hurtową ilość danych. Testy ilościowe powinny dać odpowiedź, jak system radzi sobie z dużą ilością danych. Do podstawowych punktów pomiaru należy zaliczyć:  sukces: zasileń, generowania raportów, przetwarzań;  sukces + użycie wszystkich przewidzianych danych (system nie powinien

gubić/pomijać bezzasadnie danych);  logikę biznesową zachowującą się analogicznie przy dużej i małej ilości danych

(chyba że zapisy wymagań określają sytuację inaczej). Testy ilościowe dobrze jest prowadzić w oparciu o dane bardzo zbliżone do rzeczywistych. W miarę możliwości istotne jest wprowadzanie relatywnie dużej różnorodności informacji, jakie niosą dane. Należy również pamiętać o preparowaniu danych, których import z założenia powinien zakończyć się niepowodzeniem. Oprzyjmy dalsze rozważanie na hipotetycznej sytuacji importu pliku płaskiego, który służy do wymiany

198

Testowanie oprogramowania. Podręcznik dla początkujących

danych pomiędzy dwoma różnymi systemami. Rzeczą oczywistą jest, że znamy specyfikację pliku, tj. liczbę pól, typy danych, separatory pól itp. Przyjmijmy, że kolumn (pól) będzie 20. Weryfikując logikę, dotychczas posługiwaliśmy się co najwyżej kilkunastoma wierszami. Nadszedł czas, aby poddać system właściwej próbie ognia i tym samym zwiększyć zakres danych do 30 000 wierszy. Trudno sobie wyobrazić, aby tester preparował taki plik „ręcznie”. Byłoby to nieefektywne i żmudne, a efekt końcowy mógłby być niezadowalający. W takich sytuacjach z powodzeniem można się posiłkować gotowymi narzędziami, tzn. generatorami danych testowych, o których będzie mowa na poniższych kartach tego rozdziału. Wróćmy do naszego pliku. Idealną sytuacją jest wykonanie importu hurtowej ilości danych, których kombinacja (wartości i struktura) zmuszą system do maksymalnie wytężonego wysiłku. Poprzez wspomnianą ekspresję mam na myśli dotknięcie jak największej liczby linii kodu w trakcie przetwarzania pliku. Zatem należy zadbać, aby pewien procent wierszy oprogramowanie uznało za błędne (dane, strukturę linii). Błędne wiersze dobrze jest rozmieścić asynchronicznie w całym obszarze pliku. Trudność w spreparowaniu linii poprawnych jest mocno zależna od poziomu powiązania (walidacji) danych importowanych z danymi faktycznie istniejącymi w systemie docelowym. Załóżmy, że importujemy kampanię promocyjną realizowaną poprzez wiadomość SMS. Jeżeli system odrzuca wiersze, dla których nie znalazł w bazie faktycznie istniejącego abonenta, pomimo iż wszelkie pozostałe reguły zostały spełnione, to sytuacja robi się nieco skomplikowana. Nadmieniona komplikacja dotyczy trudności w „wyprodukowaniu” hurtowej ilości poprawnych danych z uwagi na silne algorytmy walidacyjne, które bazują na już istniejących danych w bazie. Upraszczając: możemy mieć pewien kłopot z wygenerowaniem danych, które łatwo uda się dopasować do aktualnego stanu bazy danych tak, aby wiersze zostały uznane za poprawne (najczęściej w aspekcie biznesowym). Preparując plik w generatorze danych, z reguły otrzymamy losowy identyfikator klienta, który niekoniecznie znajdzie odwzorowanie w bazie docelowej. Co możemy uczynić, aby zrealizować import? Jeżeli jest to nasz jedyny problem, możemy spróbować podmienić wartości w kłopotliwej kolumnie, posługując się półśrodkami, np. arkuszem kalkulacyjnym. Rekomenduję tę praktykę, jeśli chce się uzyskać maksymalną różnorodność danych. W sytuacji kiedy system pozwala na import 30 000 identycznych wierszy (dla tego samego identyfikatora klienta), można najzwyczajniej powielić interesującą nas liczbę wierszy, choć traktowałbym ten zabieg jako rozwiązanie ostateczne czy wręcz awaryjne. Reasumując: importowany plik powinien zawierać pewien odsetek wierszy błędnych (o różnej przyczynie), dużą liczbę różnorodnych danych (dla wielu klientów) oraz nieść informację o rzeczywistym znaczeniu (nie zaśmiecać bazy). Warto nadmienić, że istotnym parametrem determinującym podejście do produkcji pliku importu jest zachowanie systemu w razie wykrycia błędu w trakcie importu. Jeżeli oprogramowanie realizuje funkcjonalność masowego zasilania jako operację atomową, tj. taką, która musi powieść się w całości lub zostanie wycofana, to nieco inaczej przygotujemy wiersze błędne (w tym przypadku będzie to pułapka w pliku). Wspomnianą pułapkę należy zastawić raz na końcu pliku w celu zweryfikowania, czy np. po przetworzeniu 25 000 wierszy system je wycofa. Drugi raz trzeba wprowadzić błędny wiersz na początku w celu oceny, czy system przerwie i wycofa proces. Jeżeli import nie ma znamion operacji atomowej, to błędne dane zwykle zostają porzucone (oznaczone jako błędne), a poprawne „trafią” tam, gdzie przewiduje projekt.

Dodatek D  Generowanie danych testowych

199

Z jednego z powyższych akapitów wynika, że generatory danych nie stanowią panaceum na wszelkie problemy związane z testami ilościowymi. Bywa, że uda się przygotować dane idealnie pasujące do potrzeb, np. plik SQL, który wstawi dane do bazy. Bywa również, że wygenerowany zestaw danych stanowi półprodukt, który trzeba dostosować do indywidualnych potrzeb  konieczna jest np. konwersja znaku końca linii, zmiana separatora pól, dostosowanie XML do komunikatu SOAP jako żądania web service itp. Oprócz problemów czysto technicznych możemy mieć również kłopot z dopasowaniem pozyskanych danych do rzeczywistych potrzeb, np. w polach typu „nazwa miasta” generator „wstawi” nazwy miejscowości rodem z Wielkiej Brytanii, gdy system będzie pracował z „polskimi danymi”. Stwarza to pewien problem, gdyż dla systemu „podstawione” dane będą zgodne z regułami walidacyjnymi, ale dla użytkownika mogą wydawać się nieprzyjemne w użyciu. Niemniej jednak doświadczony tester poradzi sobie również w takiej sytuacji. Generatory danych testowych są bardzo pomocne w sytuacjach, kiedy ewentualna logika w trakcie przetwarzania realizowana jest w tle przez system i tester nie musi się interesować, co dzieje się „pod spodem”. Równie przydatne mogą okazać się w trakcie przygotowania wsadu (danych) do testów niefunkcjonalnych (wydajnościowych), np. do wygenerowania 100 komunikatów XML. Generator danych testowych może również posłużyć do spreparowania pliku, którym tylko zasilimy odpowiednio bazę danych, a uzyskane w ten sposób źródło będzie wykorzystane przez zupełnie inny proces. Sytuacja znacznie bardziej komplikuje się, kiedy potrzebujemy dużej ilości danych, ale nie możemy ich w prosty sposób „wstawić” do bazy. Załóżmy, że chcemy sprawdzić generowanie raportu dla 50 000 klientów (w naszych rozważaniach kontekst informacji nie jest potrzebny). Raport wykorzystuje dane z wielu różnych tabel, których część jest zasilana poprzez procesy, które są bardzo trudne do „podrobienia”. Oczywiście korelacja pomiędzy tabelami jest wysoce silna. Wprowadzenie spójnych danych do kilku tabel będzie zabiegiem karkołomnym. Jedyną rozsądną drogą jest przygotowanie danych „po bożemu” poprzez „wyklikanie” danych w systemie. W takich sytuacjach niezmiernie użyteczne okazują się skrypty z automatyzacji testów, np. dla testów regresywnych. Automatyzacja testów również może być wykorzystana do generowania uczciwych danych testowych w sposób szybki i nienużący testera. Warto zapamiętać, że automaty testowe można zaangażować również do preparowania danych testowych, np. skryptu zakładającego użytkownika, który po 100-krotnym przetworzeniu stworzy całkiem pokaźną grupę reprezentantów np. do raportu. Więcej informacji o automatyzacji testów znajduje się w rozdziale 8. niniejszej publikacji. W analogiczny sposób można potraktować narzędzia do automatycznych testów wydajnościowych, np. Apache JMeter. Tester może zaprojektować przypadek, który w pętli będzie powtarzał określoną czynność (np. insert lub wywołanie funkcji WS) i systematycznie będzie inicjował generowanie danych. Jest to rozwiązanie niezmiernie elastyczne, gdyż w czasie rzeczywistym można monitorować ilość pozyskiwanych danych i nią sterować. Nie wymaga ono również uporczywej pracy przy powielaniu liczby wierszy w pliku. Wystarczy przygotować jedno uniwersalne zdarzenie, które będzie uwzględniało potrzebę generowania różnorodnych danych, a następnie je wielokrotnie wykonywać. Zwykle zasilenia z plików są jednokrotnego użytku, a dobrze przygotowany test w JMeter można realizować wielokrotnie bez konieczności modyfikacji. Narzędzie Apache JMeter zostało opisane w rozdziale poświęconym testom wydajnościowym.

200

Testowanie oprogramowania. Podręcznik dla początkujących

Internet oferuje szereg narzędzi umożliwiających generowanie danych w trybie online. Jedno z nich zamieszczone jest na stronie http://www.generatedata.com/. Wniesienie relatywnie niewielkiej opłaty poszerza zakres możliwości wspomnianego narzędzia, tj. umożliwia wygenerowanie większej liczby wierszy niż 100. Rysunek D.1 prezentuje interfejs wspomnianego narzędzia.

Rysunek D.1. Generator danych testowych

Generatedata.com oferuje eksport danych w wielu różnych formatach (sekcja EXPORT TYPES), w tym SQL, XML, CSV. Format eksportu może być dodatkowo spersonalizowany według naszych potrzeb. Przykładowo dla CSV można ustawić separator pól oraz znak końca linii (rysunek D.2). Dla opcji SQL można wybrać między innymi typ bazy danych oraz akcję, jaka ma zostać wykonana (rysunek D.3). Opcja XML umożliwia zdefiniowanie własnego formatu pliku (opcja Use custom XML format) — rysunek D.4. Sekcja DATA SET służy do definiowania nazw pól oraz typu danych, które mają zostać wygenerowane (rysunek D.5). W górnej części interfejsu znajduje się opcja COUNTRY-SPECIFIC DATA, która służy do zdefiniowania formatu generowanych danych w kontekście wybranego kraju, np. jeśli zdecydowaliśmy się zastosować wzorzec z danymi „niemieckimi”, to algorytm narzędzia będzie się starał wygenerować dane zbliżone do rzeczywistych, np. dopasuje format kodu pocztowego lub numer telefonu specyficzne dla Niemiec.

Dodatek D  Generowanie danych testowych

201

Rysunek D.2. Generator danych testowych CSV

Rysunek D.3. Generator danych testowych SQL

Rysunek D.4. Generator danych testowych XML

Rysunek D.5. Generator danych testowych SQL — specyfikacja kraju

Rysunek D.6 przedstawia kod wygenerowany w oparciu o omawiane narzędzie zgodnie z konfiguracją z rysunku D.5. Rysunek D.7 pokazuje skutek wykonania tak spreparowanego skryptu na bazie MySQL. Przytoczony przykład jest bardzo prosty i ma wartość w zasadzie akademicką. Jednak w wystarczający sposób obrazuje możliwości generatorów danych i sposób użycia pozyskanych plików źródłowych.

202

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek D.6. Wygenerowane dane przy użyciu konfiguracji z rysunku D.5

Rysunek D.7. Skutek wykonania pliku SQL na bazie MySQL

W kwestii formalnej porównajmy plik wynikowy CSV z konfiguracją (rysunki D.8 i D.9). Liczba kolumn oraz typ danych są zgodne, separator pól także jest poprawny, znak końca linii również odpowiada założeniom. Z powodzeniem można stwierdzić, że narzędzie posiada interesujący potencjał. Jedną z ciekawszych funkcji narzędzia generatedata.com jest zdolność do generowania plików XML, przy czym istotna jest możliwość modyfikowania schematu do własnych potrzeb. Niemniej jednak efekt pracy generatora zapewne będzie tylko prefabrykatem, który będzie musiał być poddany dalszemu przekształceniu, aby osiągnąć interesującą nas postać pliku.

Dodatek D  Generowanie danych testowych

203

Rysunek D.8. Generator danych testowych CSV — przykładowa konfiguracja

Rysunek D.9. Plik wynikowy CSV

Równie ciekawym rozwiązaniem może okazać się narzędzie desktopowe Spawner Data Generator, które można pobrać ze strony domowej projektu http://sourceforge.net/ projects/spawner/. Pozwala ono na generowanie danych testowych do pliku TXT, SQL lub co jest niezmiernie ciekawe  bezpośrednio do bazy MySQL. Ta niewielka aplikacja przy przemyślanym użyciu może rozwiązać wiele problemów związanych z generowaniem danych testowych i testami ilościowymi. Rysunki D.10 i D.11 przedstawiają interfejs wspomnianego narzędzia. Ciekawym i lekkim narzędziem jest dgMaster. Można je pobrać ze strony http:// dgmaster.sourceforge.net/. Rysunek D.12 prezentuje GUI narzędzia bezpośrednio po pierwszym jego uruchomieniu. Wspomniane oprogramowanie wymaga konfiguracji przed praktycznym użyciem. W tym celu najdogodniej będzie odwołać się do instrukcji zamieszczonej na stronie domowej projektu. W internecie można znaleźć wiele programów o podobnych możliwościach co przywołane powyżej. Spora część z nich napisana jest w języku Java, co może wymuszać drobne czynności konfiguracyjne, np. sterowniki JDBC. Niemniej jednak opanowanie jednego z najbardziej odpowiadających nam narzędzi skróci czas przygotowania danych, a zatem zwiększy efektywność naszej pracy. Z pewnością trud włożony w „rozpracowanie” oprogramowania wspomagającego testy będzie dobrą inwestycją.

204

Testowanie oprogramowania. Podręcznik dla początkujących

Rysunek D.10. Spawner  interfejs A

Rysunek D.11. Spawner  interfejs B

Dodatek D  Generowanie danych testowych

205

Rysunek D.12. GUI dgMaster

W pewnych sytuacjach dobrym rozwiązaniem jest napisanie własnego narzędzia, które w sposób optymalny i najbardziej wygodny dla testera będzie generować dane w postaci całościowej lub umożliwiać miejscowe jego zastosowanie. Mam na myśli przygotowanie funkcji na bazie danych, która przy odpowiednim wywołaniu będzie zwracała przykładowe dane. Jest to rozwiązanie stosunkowo wyrafinowane, ale bardzo elastyczne i wygodne, jeżeli testy, które wykonujemy, wymagają w sposób ciągły świeżych i wiarygodnych danych.

206

Testowanie oprogramowania. Podręcznik dla początkujących

O autorze Rafał Pawlak jest doświadczonym inżynierem kontroli jakości oprogramowania, praktykiem, jak również aktywnym dydaktykiem. Zwieńczenie prac nad niniejszą publikacją zbiegło się niemalże z datą trzydziestych trzecich urodzin autora. Jest on osobą mającą kilkuletnie doświadczenie w zawodzie testera oprogramowania. Pracuje z systemami klasy enterprises, aplikacjami internetowymi oraz programami stanowiącymi twarde zaplecze dla procesów biznesowych. Od samego początku rozwija i buduje kompetencje w obszarze testowania oprogramowania, realizując projekty w spółce informatycznej o znaczącej pozycji na arenie międzynarodowej oraz wyróżniającej się na rynku europejskim. Zainteresowanie technologiami teleinformatycznymi autor zaczął rozwijać w trakcie nauki w technikum elektronicznym. Szczególną uwagę poświęcił zagadnieniu okablowania strukturalnego sieci. Kontynuacją tej drogi było podjęcie studiów I stopnia o specjalizacji sieci komputerowe. Uzyskaniu dyplomu inżyniera towarzyszyło wydanie książki pt. Okablowanie strukturalne sieci. Teoria i praktyka (Helion, Gliwice 2006), która w kolejnych latach ukazała się w dodatkowych dwóch wydaniach. Specjalizacja studiów II stopnia stanowiła kontynuację początkowych zainteresowań autora. Kierunkowe wykształcenie techniczne uzupełnił podyplomowymi studiami pedagogicznymi, dzięki którym zdobył uprawnienia pedagogiczne. Tym samym zrealizował on głęboko drzemiące pragnienie udziału w kształtowaniu społeczeństwa twórczego i informacyjnego. Jego praca dyplomowa nosiła tytuł Kształtowanie postawy twórczej na lekcjach informatyki. Momentem zwrotnym, w którym autor skierował większość energii na rozwijanie kompetencji z zakresu testowania oprogramowania, a tym samym odłożył tematykę okablowania strukturalnego, był udział w kilku niezmiernie interesujących i trudnych projektach w roli kontrolera jakości oraz uzyskanie certyfikatu ISTQB Foundation Level. Trzymają Państwo w ręku egzemplarz książki napisanej przez osobę, która dzieli się z Czytelnikiem własnymi przemyśleniami i doświadczeniem. Nie jest to publikacja, która napisana jest językiem akademickim, choć informatyka jest dyscypliną, która narzuca w swojej istocie syntetyczny i mało poetycki sposób wypowiedzi. Kontakt z autorem można uzyskać za pośrednictwem wydawnictwa lub kierując bezpośrednio pytania, uwagi i propozycje na adres e-mail: [email protected].

208

Testowanie oprogramowania. Podręcznik dla początkujących

Skorowidz A administracja środowiskiem testów, 27 adres URL, 109 adresowanie IP, 180 algorytm SHA, 188 algorytmy walidacyjne, 33 analizator ruchu sieciowego, 195 API, Application Programming Interface, 76 architektura wielowarstwowa, 36 arkusze kalkulacyjne, 52 atak Blind SQL-Injection, 102, 103 SQL-Injection, 96, 99, 104 typu XSS, 93 autodiagnoza, 161 automatyzacja testów, 183

B baza danych, 76 bezpieczeństwo, 92 Blind SQL-Injection, 102, 103 blokowanie spamu, 92 błąd, 11 etap symulacji, 29 potencjalne przyczyny, 30 replikacja, 29 błędna obsługa wyjątku, 44 błędy blokujące, 12 krytyczne, 12 projektowe, 145 sterownika JDBC, 77 wewnętrzne, 42 w koncepcji, 145 w operacji mnożenia, 141 w procesie integracji, 70 zewnętrzne, 40

C certyfikat ISTQB, 58 cykl życia oprogramowania, 62 czas wykonania zapytania, 83, 85

D debugowanie, 12 dokumentacja, 13 dokumentowanie testów, 34 doświadczenie testera, 140 drzewo MockService, 174 wyników, 83

E edytory tekstu, 52 emulatory, 52 ergonomia komunikatów walidacyjnych, 121 przycisków akcji, 122 systemów informatycznych, 122

F filtrowanie danych, 97 formularz doładowania telefonu, 94, 107 funkcja zwracająca wersję, 36

G generator danych, 32 danych testowych, 197, 200 CSV, 201, 203 SQL, 201 XML, 201

210

Testowanie oprogramowania. Podręcznik dla początkujących

generator certyfikatów SSL, 51 MockService, 173 sumy kontrolnej, 51, 187 GIT, 35 graf przepływu sterowania, 138, 139 GUI, 15 GUI dgMaster, 205

I ICT, Information and Communication Technologies, 92 IEEE, 13 implementacja monitora TCP, 178 informacja o znaku końca linii, 187 informacje o nazwie serwera, 113 o systemie, 111 o wersji serwera, 113 zwrotne, 18 instrukcja IF, 15 instrukcja INSERT INTO, 80 intensywność testów, 53 interfejs użytkownika, 15 interpretacja projektu, 149

J jakość oprogramowania, 17, 20, 63, 163 JDBC, 80 język WADL, 166 WSDL, 166

K klauzula UNION, 99, 100 klient MockService, 176 usługi sieciowej, 165 kod JavaScript, 95 kodowanie znaków, 52 konfiguracja nasłuchu, 178 nowej instrukcji, 85 połączenia JDBC, 79 programu Fiddler, 114 TCPMon, 180 testu dla aplikacji, 87 zapytania SQL, 80 żądania, 88

kontrola jakości, 46, 52 wersji oprogramowania, 35 krok pośredni, 109 kryterium ilościowe, 17

L logowanie błędów, 45, 50

M Manifest Zwinnego Wytwarzania Oprogramowania, 64 menedżer plików, 51 metoda białej skrzynki, 14 czarnej skrzynki, 13 metodyki zwinne, 64 metryka pakietu Oracle, 37 miara jakości oprogramowania, 17 migracja danych, 48 model kaskadowy, 63 V, 64 modyfikacja kodu, 115 kodu formularza, 117 treści strony, 112 monitor TCP, 177 monitorowanie jakości, 21

N nagłówek HTTP, 113 narzędzia automatyzacji testów, 52, 186 narzędzie Apache JMeter, 51, 52, 199 Apache TCPMon, 177, 178, 180, 181 dgMaster, 203 Eclipse, 51 Fiddler, 105–108, 111–117 Fiddler2, 51 Firebug, 51, 106 JMeter, 76–90 KeyStore Explorer, 51 Membrane Monitor, 51 Membrane SOAP Monitor, 189–93 NetBeans IDE, 51 Notepad++, 51 Oracle OpenScript, 52 PL/SQL Developer, 51 PuTTY, 51 Selenium, 52

Skorowidz

211

SoapUI, 51, 165–176, 180 Spawner Data Generator, 203 TCPMon, 51 Total Commander, 51 VNC, 51 WebScarab, 105 WinSCP, 51 Wireshark, 51, 195, 196

O obsługa błędów wewnętrznych, 42 błędów zewnętrznych, 40 wyjątków, 43 zgłoszeń, 39 ogólna teoria testowania, 11 opis kodu źródłowego, 38 oprogramowanie Bugzilla, 39 JIRA, 39 Mantis Bug Tracker, 39 SVN, 35

P plik wynikowy CSV, 203 podgląd odpowiedzi, 114 podmienianie wartości, 105 polecenie DESC, 96 porty, 180 poziomy wykonywania testów, 65 presja czasu, 151 procentowy rozkład zgłoszeń, 21 proces integracji, 70 obsługi błędów, 40, 42 testowania, 149 projekt REST, 170 projektowanie testu, 129 doświadczenie testera, 140 technika białej skrzynki, 136 technika czarnej skrzynki, 131 protokół REST, 168 przebieg realizacji projektu, 9, 53 rejestracji błędów, 22, 23 przechwycenie sesji, 108 przejścia pomiędzy stanami, 134 przekazywanie zmian w kodzie, 39 przyczyny błędów, 30 zniechęcenia, 156 przypadek testowy, 12 przypadki użycia, 135

R realizacja projektu, 60 reguły odpowiedzi, 175 rejestracja błędów, 22, 23 rekonesans, 111 replikacja błędów, 28 REST, Representational State Transfer, 165 retest, 11 równomierne obciążenie pracą, 54

S scenariusz testów, 13 serwer poczty, 52 SHA, Secure Hash Algorithm, 188 skrypt JS, 95 skutki zniechęcenia, 159 SOAP, Simple Object Access Protocol, 165 specyfikacja, 13 spójność nazewnictwa, 121 SQL-Injection, 96, 99, 104 SSL, Secure Socket Layer, 166 standardy, 13 Subversion, SVN, 35 suma kontrolna, checksum, 187 sygnatura wersji, 103 symulacja błędu, 29 symulator aplikacji, 31 serwera usług sieciowych, 171 syndrom zniechęcenia, 153 system kontroli wersji, 35 GIT, 35 Subversion, 35

Ś środowisko deweloperskie, 30 produkcyjne, 25 testów, 23, 27, 28

T tabela, 81 technika białej skrzynki, 136 czarnej skrzynki przejścia pomiędzy stanami, 134 przypadki użycia, 135 wartości brzegowe, 131 techniki testowania, 13

212

Testowanie oprogramowania. Podręcznik dla początkujących

teoria testowania, 11 tester oprogramowania, 54 aspekty psychologiczne, 149 syndrom zniechęcenia, 153 testowanie aplikacji internetowej, 86 bazy danych, 76 bezpieczeństwa aplikacji, 91 ergonomii systemu informatycznego, 118 instalacji, 117 instrukcji, 136 migracji danych, 47 obsługi wyjątków, 43 przejść pomiędzy stanami, 134 przenośności kodu, 117 usług sieciowych, 89, 165 testy akceptacyjne, 72 decyzyjne, 138 funkcjonalne, 73 ilościowe, 197 integracyjne, 67 jakościowe, 8 modułowe, 66 niefunkcjonalne, 74 obciążeniowe, 75 przeciążeniowe, 75 regresywne, 125 systemowe, 71 bezpieczeństwa, 72 ergonomii, 72 funkcjonalne, 71 instalacji, 72 regresywne, 72 wydajnościowe, 71 w cyklu życia oprogramowania, 62 w oknie czasu, 58 wewnętrzne, 8 wydajności, 74, 76 trywialność wykrycia błędu, 12 tworzenie symulatora WS, 172 typy testów, 73

U

W WADL, Web Application Description Language, 166 walidacja dopuszczalnych znaków, 144 pól dla kwot, 147 walidatory, 52 wartości brzegowe, 131 wersje oprogramowania, 35 wrażliwe informacje, 109 WSDL, Web Services Description Language, 166 wstrzyknięcie kodu JS, 95 wyjątki, 43 wykres testu aplikacji, 89 wyłączenie części kodu, 104 wymaganie, 13 wytwarzanie oprogramowania, 64 wywołanie monitora TCP, 179 wywoływanie funkcji MockService, 176 procedury, 84

X XSS, Cross Site Scripting, 93

Z zasada kumulowania się błędów, 9 zastosowanie opcji disabled, 117 ORDER BY, 102 SELECT UNION, 99, 101 złamanie zasad ergonomii, 122 zniechęcenie, 153 przyczyny, 156 skutki, 159

Ź źródła pliku WSDL, 166 źródło zmodyfikowanej strony, 116

Ż

usługi sieciowe, 165 żądanie REST, 171