Testy, narzędzia do testowania, metodologia testów.

autorzy:
Anna Rączka
Dariusz Gryglas
Rafał Kwatek


spis treści:
1. Wstęp
2. Metodologia testów
3. Automatyzacja testowania na przykładzie JUnit
4. Jak testować testy?
5. Testowanie obciążenia
6. JMeter
7. Mierzenie wydajności systemu operacyjnego



Wstęp, Co to jest testowanie?

Testowanie oprogramowania to wykonywanie kodu dla kombinacji danych wejściowych i stanów w celu wykrycia błędów. Testy projektuje się, analizując testowany system i rozstrzygając do jakiego stopnia jest on obciążony ryzykiem błędów. Celem testowania oprogramowania jest wykrycie jak największej liczby, nie wykrytych wcześniej błędów. Jakość testu można mierzyć w prawdopodobieństwie znalezienia jeszcze nie wykrytego błędu.
Istnieje tzw. Automatyka testu, czyli oprogramowanie, które automatyzuje testowanie systemu użytkowego. Umożliwia ono generowanie danych testowych i wyników oczekiwanych, wykonuje zestaw testów bez ręcznej ingerencji i ocenę czy test przeszedł, czy nie. Testy można wykonywać automatycznie lub „ręcznie”.

Zalety testowania automatycznego to m. in.:
- możliwość szybkiej i wydajnej weryfikacja poprawek błędów
- możliwość odtworzenia testu, co jest bardzo przydatne zwłaszcza jeżeli chcemy sprawdzić czy wskazywane błędy zostały usunięte
- możliwość kompleksowej analizy wyników testów
- szybsze i tańsze tworzenie sprawozdań
- szybsze wprowadzanie testów
- nieomylność przy w prowadzaniu danych
- możliwość podania dużej liczby danych testowych

Czemu opłaca się testowć?

Testowania umożliwia wykrycie błędów we wczesnych stadiach rozwoju oprogramowania, co pozwala zmniejszyć koszty usuwania tego błędu. Dlatego warto przeprowadzać testy na każdym etapie tworzenia oprogramowania oraz inwestować w zespoły testerów, gdyż dzięki ich pracy możemy zaoszczędzić sporo czasu oraz pieniędzy. Poniżej przedstawiono tabelę, w której pokazano jak zwiększają się koszty usunięcia błędów w zależności od etapu tworzenia oprogramowania, na którym zostały wykryte. Jak widać opłaca się prowadzić testy na jak najwcześniejszym etapie. Im później wykryty zostanie błąd tym większy jest koszt jego usunięcia.

faza
koszt($)
koszt(czas)
analiza
1
6 min
projekt
5
0,5h
kodowanie
10
1h
testy jednostkowe
15
1,5h
testy integracyjne
22
2,2h
testy systemowe
50
5h
po wdrożeniu
100+
10h+
źródło: Adam Wojciechowski: www.cs.put.poznan.pl/awojciechowski/dydaktyka/opp/2004/04opp5tst.ppt

Weryfikacja i walidacja
Testowanie oprogramowania jest często używane w połączeniu ze słowami weryfikacja oraz walidacja.
Weryfikacja to sprawdzanie lub testowanie rzeczy pod kątem zgodności ze specyfikacją. Testowanie oprogramowania jest jednym z rodzajów weryfikacji.
Walidacja to proces sprawdzania czy to co zostało napisane w specyfikacji jest zgodne z tym czego pragnie użytkownik.



Metodologia testów

Analiza statyczna i dynamiczna
Testowanie oprogramowania można wykonywać pod kątem analizy statycznej i dynamicznej.
Analiza statyczna polega na sprawdzaniu kodu źródłowego i znajdowaniu w nim błędów bez uruchamiania sprawdzanego kodu.
Podczas analizy dynamicznej oprogramowanie jest uruchamiane i badane pod kątem ścieżki przebiegu i czasu wykonywania.

Czarne i białe skrzynki czyli funkcjonalne i strukturalne testy dynamiczne.
Testy funkcjonalne (czarnej skrzynki), które zakładają znajomość jedynie wymagań wobec testowanej funkcji. System jest traktowany jako czarna skrzynka (ang. black box), która w nieznany sposób realizuje wykonywane funkcje. Wprowadzamy dane wejściowe i analizujemy wyniki otrzymywane na wyjściu. Testy funkcjonalne powinny wykonywać osoby, które nie były zaangażowane w realizację testowanych fragmentów systemu.
Testy strukturalne (szklanej skrzynki) (ang. white box), które zakładają znajomość sposobu implementacji testowanych funkcji i są opracowywane na podstawie sprawdzania kody źródłowego.

Testy funkcjonalne
Wadą testów funkcjonalnych jest to, że pełne przetestowanie rzeczywistego systemu jest praktycznie niemożliwe z powodu ogromnej liczby kombinacji danych wejściowych i stanów. Zakłada się, że jeżeli dana funkcja działa poprawnie dla kilku danych wejściowych, to działa także poprawnie dla całej klasy danych wejściowych. Niestety fakt poprawnego działania w kilku przebiegach nie gwarantuje (ale daje duże szansę), że błędne wykonanie nie pojawi się dla innych danych z tej samej klasy. Aby jakoś temu zaradzić należy przeprowadzać testy dla wartości granicznych np. min i max oraz dla wartości, które wynikają z opisu wymagań.

Testy strukturalne
Testy strukturalne przeprowadza się zawsze według pewnych kryteriów. Dwa najbardziej popularne kryteria to:
Kryterium pokrycia wszystkich instrukcji. Zgodnie z tym kryterium dane wejściowe należy dobierać tak, aby każda instrukcja została wykonana co najmniej raz.
Kryterium pokrycia instrukcji warunkowych. Dane wejściowe należy dobierać tak, aby każdy elementarny warunek instrukcji warunkowej został co najmniej raz spełniony i co najmniej raz nie spełniony.

Poziomy testowania – testowanie na różnych etapach projektu
Testowanie jednostek - każda jednostka oprogramowania jest testowana w celu sprawdzenia, czy została ona poprawnie zaimplementowana zgodnie ze szczegółową specyfikacją.
Testowanie zintegrowane - w tym przypadku testowane są poszczególne komponenty odpowiadające elementom wyodrębnionym w specyfikacji architektury. (zależności między komponentami)
Testowanie systemu - na tym poziomie testowana jest cała aplikacja w celu sprawdzenia zgodności z wyspecyfikowanymi wymaganiami. (cechy systemu jako całości)

Dla każdego z tych poziomów należy stworzyć odpowiednie testy. Należy przygotować testy przed stworzeniem oprogramowania. Jeżeli podczas testowania oprogramowania na dowolnym poziomie, zostaną ujawnione błędy to należy je usunąć, a proces testowania powtórzyć dla całego zbioru testów.
Podczas rozszerzania możliwości oprogramowania należy pamiętać o rozszerzeniu bazy testów.

Dokumentacja testów. – norma IEEE 829
Testowanie oprogramowania wymaga systematyczności w procesie testowania oraz dokumentowania testów. Istnieje norma IEEE 829, która zapewnia pełną systematykę zarówno testowania jak i dokumentowania testów. Norma IEEE 829, celem usystematyzowania testowania, podzieliła je na cztery etapy:
- przygotowanie planu testu
- zaprojektowanie zestawu testów
- testowanie składowych
- przygotowanie sprawozdania podsumowującego

Dlaczego nie da się całkowicie przetestować oprogramowania?
Nigdy nie możemy mieć 100% pewności, że nasze oprogramowanie nie zawiera błędów. Testy umożliwiają tylko zminimalizowanie ryzyka ich wystąpienia. Wielka ilość danych wejściowych, wyjściowych oraz ogrom możliwych ścieżek prowadzących przez program powoduje, że nie ma fizycznej możliwości sprawdzenia oprogramowania dla wszystkich możliwych danych. Ważnym czynnikiem, który też powoduje, że nie można całkowicie przetestować oprogramowania jest to, że każdy subiektywnie postrzega specyfikację wymagań. Właśnie specyfikacja opisuje jak powinno działać oprogramowanie. Jeśli program działa niezgodnie z tym co jest w niej zapisane jest to mamy doczynienia z błędem. Jeśli specyfikacja nie jest postrzegana obiektywnie to i błędy też nie są. Jest to bardzo ważny czynnik, o którym należy pamiętać testując oprogramowanie.



Automatyzacja testowania na przykładzie JUnit.

Testowanie, jako jedna z podstawowywch technik zapewnienia jakości doczekało sie wielu narzedzi wspomagających. Idea biblioteki wspomagającej wywodzi się z języka smalltalk, w którym Kent Beck napisał jej pierwszą implementację.
Zadaniem tego typu oprogramowania jest uruchamianie testów i pokazywanie raportów z ich wykonania. Testy oczywiście same się nie napiszą. Specyfikacja wymagań pozostaje zadaniem dla programisty, ale sam proces wykonania testów jest już nieinteraktywny - wykonywany jest automatycznie.

Podstawowym wymaganiem stawianym oprogramowaniu automatyzującemu testowanie jest jego przyjazność użytkowania. Programista bez konieczności nauki dodatkowych narzędzi i języków powinien móc łatwo napisać testy.
Raz napisane testy powinno się móc zachować i wykorzystać w przyszłości,  lub na ich podstawie tworzyć nowe testy.
Powinno być możliwe jednoczesne wykonanie testów napisanych przez kilka różnych osób bez obawy o jakieś nieprzewidziane konflikty między nimi.  W trakcie pisania testów i sprawdzania ich wyników po pewnym czasie dostrzega się kolejne wymagania jakie powinien spełniać program do autamtyzacji testów. Testy powinno się dać organizować w zbiory wg. jakiegoś ustalonego klucza, by móc wybrane testy wykonywać jednocześnie. Niepowodzenie jednego testu nie powinno przerywać działania programu. Pozostałe tetsty pownny sie wykonać by zebrać jak najwięcej informacji za jednym razem (biblioteczna funkcja assert z języka c w przypadku nie powodzenia kończy program).  W wyniku wykonania testów program powinien tworzyć szczegółowy raport z informacjami o łącznej ilości przeprowadzonych testów ilości wykrytych błędów, czasie wykonania. Dla każdego testu jaki się nie powiódł powinna być zapisana informacja o tym jaka została uzyskana wartośc a jaka była oczekiwana. Program który spełnia wszystkie te wymagania pozwala w pełni na stosowanie techniki Test Driven Design czyli na pisanie testów równolegle z pisaniem kodu programu i testowanie programu na każdym etapie jego implementacji sprawdzając poszczególne metody klasowe, całe klasy, całe moduły i na koniec całą aplikację. Po każdej wprowadzonej w programie zmianie będzie się dało gruntownie ją przetestować (bez konieczności angażowania do tego programisty) i po jakimś czasie przejrzeć otrzymane wyniki poprawiając ewentualne wykryte błędy.

JUnit zrodził się jako odpowiedź na wszystkie te wymagania. Jest to chyba najbardziej znana biblioteka pomagająca w testowaniu oprogramowania. Została ona napisana w javie. Rekomendacją tej biblioteki są nazwiska jej twórców: Kent Beck (eXtreme Programing) oraz Erick Gamma (wzorce projektowe). JUnit był wielokrotnie nagradzany w kategorii najlepsze narzędzie do testowania i monitorowania wydajności w javie (m. in. Java World Editor's Choice Award ECA)

Instalacja JUnit

JUnit jest oprogramowaniem typu open source. Można go ściągnąć za darmo ze strony:
download.sourceforge.net/junit lub pośrednio korzystając z witryny domowej JUnit :  junit.org.
Na stronie JUnit znajduje się też pełna dokumentacja tego programu.

Po ściągnięciu i rozpakowaniu pliku junit.zip by dopełnić instalacji konieczne jest dopisanie katalogu w którym JUnit rozpakowaliśmy do środowiskowej zmiennej CLASSPATH.
w windows:
set CLASSPATH=%CLASSPATH%; ścieżka_do_katalogu_junit/junit/jar;
w linux:
export CLASSPATH=$CLASSPATH; ścieżka_do_katalogu_junit/junit.jar;

By przetestować poprawność instalacji i zobaczyć jak wygląda JUnit w działaniu można wykonać dołączone do programu przykładowe testy poprzez komendy:
java junit.textui.TestRunner junit.samplesAllTests
java junit.swingui.TestRunner junit.samplesAllTests

Pierwsza uruchomi JUnit w środowisku tekstowym, druga w graficznym.

Graficzny interfejs JUnit zawiera:
- pole z nazwą testowanej klasy bądź pakietu klas
- przycisk run, do odpalania procesu testowania
- pasek postepu zielony dopóki wszystkie testy zostaną zaliczone, czerwony w przeciwnym przypadku
- listę nieudanych testów
- pole ze śladem stosu po wykryciu błędu nieoczekiwanego

junit1.jpg

Oczywiście by móc uruchomić JUnit konieczna jest wcześniejsza instalacja środowiska Java.

Jak zabrać się za pisanie testów dla JUnit?

Dla każdej klasy w javie:

class Klasa {
  metoda1() {}
  metoda2() {}
  ...}


tworzymy odopowiednią klasę  KlasaTest dziedziczącą z zaimplementowanej w JUnit klasy bazowej testów TestCase.

import KlasaTest extends TestCase {
  metoda1test() {}
  metoda2test() {}
  ...}


W jednej testowanej klasie możemy mieć wiele testów. Testy różnych klas testowych możemy łączyć w zestawy, służy do tego także zaimplementowana w JUnit klasa TestSuite
Wkażdym z testów można wyróżnić nastepujące fazy:
-utworzenie potrzebnych obiektów do testowania - faza SetUp
-przeprowadzenie testu np. porównanie czy otrzymana wartość jest tą jakiej się spodziewaliśmy
-zwolnienie zasobów - faza TearDown
Kod części SetUp i TearDown może być wspólny dla wszystkich metod zaimplementowanych w KlasaTest

Przykład

Mamy klasę Trojkat. Obiekty tej klasy mają trzy atrybuty typu int określające długości trzech boków. Zaimplementowano w tej klasie metody dające odpowiedź na pytanie czy dany trójkąt jest równoboczny, równoramienny, czy różnoramienny. Zaimplementowano również metode sprawdzającą czy trójkąt o podanych bokach w ogóle istnieje, a także metodę sprawdzającą czy dwa trójkąty o podanych bokach są do siebie przystające bądź czy są izomorficzne. Chcemy testować tę klasę a konkretnie te wskazane jej metody. Tworzymy zatem:

public class TrojkatTest extends TestCase
  Trojkat t1;
  Trojkat t2;
  Trojkat t3;
  Trojkat t4;

  ...              
/* trzy kropki oznaczają mozliwośc dodania dowolnej ilości obiektów klasy Trojkat do testowania */

protected void SetUp() {
  t1 = new Trojkat (3,4,5);
  t2 = new Trojkat (6,6,6);
  t3 = new Trojkat (5,4,3);
  t4 = new Trojkat (2,4,2);
  ..}

protected void TearDown() {
  /* nic nie musimy sprzątać */ }

public void testCzyRownoboczny() {
  Assert.assertTrue (! t1.czyRownoboczny() );
  Assert.assertTrue (t2.czyRownoboczny() );
  Assert.assertTrue (! t3.czyRownoboczny() );
  Assert.assertTrue (! t4.czyRownoboczny() );
   ...}

public void testCzyRoznoboczny() {
  Assert.assertTrue (t1.czyRoznoboczny() );
  Assert.assertTrue (! t2.czyRoznoboczny() );
  Assert.assertTrue (t3.czyRoznoboczny() );
  Assert.assertTrue (! t4.czyRoznoboczny() );
  ...}

public void testCzyRownoramienny() {
  Assert.assertTrue (! t1.czyRownoramienny() );
  Assert.assertTrue (! t2.czyRownoramienny() );
  Assert.assertTrue (! t3.czyRownoramienny() );
  Assert.assertTrue (! t4.czyRownoramienny() );
  ...}

public void testCzyIstnieje() {
  Assert.assertTrue (! t1.czyIstnieje() );
  Assert.assertTrue (t2.czyIstnieje() );
  Assert.assertTrue (! t3.czyIstnieje() );
  Assert.assertTrue (! t4.czyIstnieje() );
  ... }

public void testCzyIzomorficzny() {
  Assert.assertTrue (! t1.izomorficzny(t2) );
  Assert.assertTrue (t1.izomorficzny(t3) );
  Assert.assertTrue (! t2.izomorficzny(t3) );
  Assert.assertEquals(t1,t3);
  ...}


/* jeżeli spodziewamy się wystąpienia wyjątku w jakimś miejscu w kodzie, żeby sprawdzić czy wystąpił używamy specjalnej konstrukcji również zaimplementowanej w JUnit pozwalającej na przechwycenie wyjątku: */

public void testInnyTest() {
  trojkat txx;
  try { txx = new Trojkat (-4,7,3455);
          fail ("exception not thrown");
  } catch (Exception e )  }


Uruchamiamy JUnit, wybierając naszą klasę do testowania.
Jeżeli wszystkie testy zakończą sie pomyślnie zobaczymy zielony pasek.

junit4.jpg

Jeśli jakiś test sie nie powiedzie pasek bedzie czerwony i zostanie wypisany stosowny komunikat informujący o znalezionym błędzie.

junit3.jpg

JUnit rozróżnia błędy oczekiwane wykrywane przez asercje - failures oraz błędy nie oczekiwane np. przekroczenie zakresu tablicy - errors.
Testy można łączyć w zestawy, korzystając z klasy TestSuite:

package trojkat
import junit.framework.*
public class AllTests {
  TestSuite suite = new TestSuite();
  suite.addTest(new TrojkatTest1("testCzyIstnieje"));
  suite.addTest(new TrojkatTest2("testCzyPrzystajacy"));
  ...
  return suite; }

Po stworzeniu pakietu możemy go później wybierać do testowania spośród dostepnych pakietów testów.

junit2.jpg

Pare wskazówek na koniec.

Co testować?
Wszystko. każdą klasę, jej metody, każdy moduł. Nie warto jedynie testować metod takich jak set i get. One powinny działać zawsze. Testując je testujemy w zasadzie nie tyle nasz program co kompilator bądź interpreter poleceń.
Zasada jest taka. Najpierw wymyślamy testy dla naszej metody, potem piszemy metodę tak by przeszła testy.
W przypadku wykrycia błędu w kodzie piszemy test którego nasz program nie przechodzi i poprawiamy kod do momentu gdy wszystkie testy zostaną zaliczone. Najlepsze testy to te, które nasz program powinienbył przejść a nie przeszedł bądź odwrotnie, te które spodziewaliśmy się że nasz program ich nie przejdzie a przeszedł. Tylko takie testy daja możliwość wykrycia i porawienia błędów w kodzie. Nawet po napisaniu całej aplikacji warto jeszcze raz powtórzyć testy wszystkich klas składowych by upewnić sie że nic się nie popsuło w trakcie pracy nad programem. Mając pod ręką testy poszczególnych klas i modułów można dokonywać znacznych zmian w kodzie (np. zamiana tablic na listy) nie martwiąc sie że błąd który popełnimy w tym momencie może mieć trudne do wykrycia skutki. Nasze testy od razu go wyłapią. Daje nam to dużą elastyczność.

Na koniec warto jeszcze wspomnieć o istnieniu narzędzi jeszcze prostszych i przyjemniejszych w obsłudze, będących pełnym środowiskiem programistycznym oparte o JUnit.
Jednym z takich programów jest JTest. Program ten ma wbudowaną własną inteligencję. Sam potrafi optymalizować parametry testów w taki sposób aby wypróbować jak największą część kodu Posiada także wbudowany analizator kodu pozwalający na utrzymanie standardów kodowania. Jest to niestety jednak program komercyjny. Jego wersje shareware bywają dołączane do pism komputerowych m.in. był on dołączony do pisma Software 2.0 z listopada 2004.

Na podstawie JUnit powstała cała rodzina oprogramowania zwana xUnit. Powstały programy do testowania programów napisanych w przeróżnych językach m.in cppUnit na potrzeby c++, htmlUnit do testowania aplikacji internetowych, sqlUnit dla testowania procedur sql-owych, dUnit dla delphi itd.
Ogólna budowa wszystkich tych programów jest bardzo do siebie zbliżona. Różnice wynikają ze specyfiki pewnych konstrukcji w obrębie danego języka. Np. w NUnit programie do testowania aplikacji .Net wykorzystano nie istniejące w javie atrybuty deklaratywne. dzięki temu nie potrzebne już jest specjalne nazewnictwo testowanych metod ani hierarchi dziedziczenia zestawów. O wiele prosztsze jest też w NUnit testowanie wyjątków. Nie potrzeba do tego celu bloków try catch.

Bardzo ciekawym systemem w którym zastosowano na szeroka skalę TDD (Test Driven Development) jest projekt open source'owy o nazwie "Eclipse". Ma on na celu utworzenie środowiska programistycznego (IDE) do wszystkiego.
Dzięki zaawansowanej architekturze Eclipse pozwala rozszerzać swoją funkcjonalność przez dodawanie tzw. "wtyczek". System powstaje w języku java i korzysta z JUnit jako platformy uruchamianaia testów. Eclipse dostarczone jest z ogromnym zestawem testów zaimplementowanych przy użyciu własnej biblioteki. Dla każdej wersji Eclipse przygotowywany jest raport pokazujący, które testy szakończyły się pomyslnie a które nie.
Innym, godnym wspomnienia projektem, który jest silnie związany z testowaniem jest Fit. Pozwala definiować testy akceptacyjne poprzez tabelki HTML. Wymagania te nastepne możemy sprawdzić poprzez wykonanie tak przygotowanych testów. Całość dostepna jest przez przeglądarkę WWW przez tzw. mechanizm Wiki.

więcej informacji w internecie:
www.extremeprogramming.com - wprowadzenie do eXtreme Programming
www.junit.org - strona poświęcona JUnit
fit.c2.com - strona domowa projektu Fit
www.eclipse.org - strona domowa projektu Eclipse



Jak testować testy?

Można wspomagać się narzędziami służacym do analizy ile procent kodu zostało przetestowane. Może to dać pewien wskaźnik jakości testów.
Można stosować techniki znane z eXtreme Programing polegające na tym, że jedna osoba pisze kod, zaś druga go sprawdza po czym role się zamieniają. Technika inspekcji pozwala na wykrycie błędów nieświadomie popełnianych przez programiste, który przyjmując pewne założenia odnośnie działania napisanego przez siebie programu nie zauważa tego co osoba nie skażona tymi założeniami zauważa o wiele łatwiej.
Istnieje także sposób pozwalający na pewną automatyzację testowania testów. Jest to Testowanie Mutacyjne, ciekawe rozwiązanie polegające na szukaniu błędów poprzez wprowadzanie do kodu innych błedów.
W swoim działaniu przypomina próby sprawdzenia czy człowiek jest odporny na daną chorobę poprzez wszczepienie mu kontrolowanej ilości nieszkodliwych wirusów i sprawdzeniu czy jego układ odpornościowy zareaguje na nie.
W Testowaniu Mutacyjnym wstrzykuje się do programu proste błędy typu zamiana znaków + na - , zanegowanie warunków logicznych, odwrócenie znaków > <, czy wręcz usunięcie bądź dodanie pewnych linii kodu. Wstrzykujemy do programu tego typu proste błędy tworząc mutacje naszego programu i sprawdzamy czy nasze testy je wykryły. Jeśli błędy zostały wykryte to wszystko jest w porządku. Jesli jednak nie to mamy sygnał że nasze testy nie do końca działają tak jak powinny. Poprawiamy je więc aż wykryja te błędy. Schemat działania Testowania Mutacyjnego przedstawiono na rysunku poniżej:

test_mut.jpgżródło Software 2.0  listopad 2004

Założenie na jakim się opiera Testowanie Mutacyjne mówi o tym że, jeżeli nasze testy wykrywają wszelkie wstrzyknięte błędy proste to najprawdopodobniej radzą też sobie z błędami złożonymi (składającymi się z błedów prostych - tzw. efekt sprzężenia) popełnionymi przez programistę. Założenie to opiera się na badaniach różnych kodów programów i napisanych do nich testów. Wykazano empirycznie że testy które wykrywają pojedyńcze zmiany w kodzie, w większości wypadków całkiem nieźle radzą sobie z mutacjami o wiele bardziej złożonymi. Dzięki temu można sie skupić na wykrywaniu tych najprostszych błedów oczekując że dzieki temu otrzymany program będzie poprawnie działał.



Testowanie obciążenia.

Sprawdzenie braku błędów przy tworzeniu aplikacji jest niezmiernie ważne, jednak ważne jest też, jak duży napór użytkowników jest ona w stanie wytrzymać. Bardzo ciężko przeprowadzić takie testy ręcznie, dlatego stworzono narzędzia ułatwiające testowanie obciążenia. Głównym celem takich testów jest określenie wydajności systemu w czasie symulowanego obciążenia przez wirtualnych użytkowników. Oczywiście takie testy nie zastąpią testów funkcjonalnych, gdyż automat nie jest w stanie sprawdzić, czy system zawiera wszystkie zaplanowane funkcje.
Testy wydajnościowe, podobnie jak inne testy najlepiej rozpocząć jak najwcześniej. Dla automatu testującego nie jest ważny wygląd aplikacji, ale jej możliwości, a przy opóźnionym testowaniu może okazać się, że system będzie wymagał gruntownej przebudowy. Wtedy koszty tworzenia systemu rosną drastycznie.
Bardzo ważne przy testowaniu obciążenia jest otoczenie – konfiguracja systemu operacyjnego, sprzętu, serwera WWW oraz środowisko testowe. Najlepiej byłoby gdyby system testowany był możliwie zbliżony parametrami do systemu produkcyjnego i w oddzielnej sieci. Odizolowanie sieci zmniejsza z jednej strony błąd pomiarowy spowodowany normalnym ruchem w sieci - niekoniecznie do naszej aplikacji, z drugiej zaś zwiększa komfort pracy pozostałym osobom w firmie - testowanie obciążeniowe na ogół generuje spory ruch. Warto również rozłożyć wirtualnych użytkowników na kilka komputerów - minimalizujemy wtedy wpływ na testy wydajności stacji testującej.
Dzięki programom komputerowym możemy świetnie symulować zachowania użytkowników odwiedzających serwis. Możemy zasymulować różnice w ich sposobie i szybkości działania.

Przykładem może być program ApacheBench dostarczany ze źródłami serwera Apache. Pokazuje on między innymi, ile zapytań na sekundę może obsłużyć serwer. Jego wywołanie jest proste. Jeżeli chcemy wysłać 1000 zapytań przez 20 klientów, wystarczy użyć komendy:

ab -n 1000 -c 20 http://www.jakis_serwer.pl

W wyniku dostajemy przykładowy raport:

Server Software: Apache
Server Hostname: www.jakis_serwer.pl
Server Port: 80
Document Path: /
Document Length: 5894 bytes
Concurrency Level: 20
Time taken for tests: 6.029 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 6266418 bytes
HTML transferred: 5970622 bytes
Requests per second: 165.86
Transfer rate: 1039.38 kb/s received

Connnection Times (ms)

min
avg
max
Connect:
2
33
130
Processing:
51
83
497
Total:
53
116
627

Parametry: Requests per second i Processing avg time pozwalają ocenić, ile czasu zajmuje serwerowi obsługa zapytania. ApacheBench pracuje tylko z jednym adresem. Jeśli chcielibyśmy przetestować więcej stron to musielibyśmy obudować go jakimś skryptem.
Innym programem jest http_load. Jego działanie polega na symulowaniu działania równocześnie kilku klientów. Adresy URL znajdują się w pliku podanym jako argument wywołania, dzięki czemu możemy podać więcej niż jeden adres. Jednak kolejność odpytywania adresów jest losowa, więc nie możemy prześledzić ścieżek poruszania się klienta po serwisie. Aby zasymulować równoczesne korzystanie z systemu przez wielu użytkowników program można wywołać z dwiema opcjami:
-parallel N, która określa ile równoległych połączeń ma zostać utworzonych;
-rate n, która określa, co ile sekund ma być nawiązywane nowe połączenie.


JMeter

Wykorzystując takie programy chcielibyśmy jak najlepiej zasymulować rzeczywiste warunki używania serwisu. Przykładowo rzadko zdarza się, aby obciążenie serwisu nagle skoczyło. Rozwiązanie takich problemów podjęto w programie JMeter.
Głównym zadaniem programu jest mierzenie wydajności aplikacji. Oryginalnie był stworzony do testowania aplikacji WEB-owych, ale został rozszerzony i umożliwia teraz także testowanie protokołów FTP i JDBC.
Po uruchomieniu JMeter na ekranie widzimy dwa węzły - TestPlan i WorkBench.

jmeter1.jpg

TestPlan przetrzymuje i uruchamia aktualny TestPlan. W WorkBench można tworzyć i konfigurować testy.
TestPlan składa się z grup wątków ThreadGroup (wątek w nomenklaturze programu JMeter można utożsamiać z wirtualnym użytkownikiem).

Grupy wątków mogą zawierać obiekty typu: timer, listener, controler.
Obiekty typu: timer służą spowolnieniu działania wirtualnych użytkowników i urzeczywistnieniu ich zachowania.
Controler- odpowiada bezpośrednio za testowanie.  Może zawierać elementy typu config odpowiedzialne za jego zachowanie. Listener zbierają informacje o przebiegu testowania;  w JMeter mamy dwa ich rodzaje: wizualne i raportowe. W czasie testowania dane mogą być zbierane do jednego lub więcej obiektów listener.

Aby dodać nową grupę wątków wystarczy kliknąć prawym przyciskiem myszy na węzeł TestPlan i wybrać odpowiednią opcję:

jmeter2.jpg

Teraz można już ustalać ilość wirtualnych użytkowników, czas w jakim mają się oni ‘uaktywniać’ oraz liczbę powtarzania ich czynności.

jmeter3.jpg

Następnie należy dodać do grupy wątków odpowiedni element konfiguracji. Jeśli chcemy testować wydajność jakiejś witryny WEB to powinien to być HTTP Request Defaults, a dalej jako jego ‘synów’ w drzewie jako odpowiedni ‘sampler’ (HTTP Request).
Możemy jeszcze dodać np. graficzny widok wyników:

jmeter4.jpg

Jeśli ustalone jest już, jakie strony (podstrony) wirtualni użytkownicy mają odwiedzić trzeba już tylko wybrać w menu Run opcję Start i oglądać rezultaty.

Przykładowy wynik graficzny może wyglądać tak:

jmeter5

Poza prostym testowaniem stron JMeter umożliwia także:
- Testowanie bardziej zaawansowanych stron (np. wymagających logowania etc.).
- Testowanie baz danych – zalogowanie do bazy i dostęp do zapytań.
- Testowanie żądań FTP’owych.
- Testowanie klas Javovych.
- Testowanie LDAP.

Wszelkie informacje można znaleźć na stronie domowej JMeter: http://jakarta.apache.org/jmeter/



Mierzenie wydajności systemu operacyjnego.

Benchmarking to proces pomiaru szybkosci z jaką system komputerowy wykonuje określone akcje np. operacje wejścia-wyjścia, oraz wydajności tego systemu poddanego zwiększonym obciążeniom. procedury pomiarowe produkują czyste statystyki, w których liczą się szczegóły. Najważniejsza w całym procesie jest poprawna interpretacja wyników uzyskanych na różnych systemach. Benchmarking nie odpowiada na pytanie który system jest lepszy, gdyż często dla oceny systemu o wiele ważniejsza jest jego stabilność niż rekordowe czasy np. operacji tworzenia i usuwania pustych plików. Daje jednak jakiś obraz sytuacji, a specjaliści lubią zestawienia cyferek.
Testy wydajności można podzielić na dwie zasadnicze grupy. Testy syntetyczne, których wyniki mówią o poszczególnych częściach składowych systemu i komputera, oraz testy polegające na mierzeniu szybkości działania na danym komputerze w danym systemie popularnych aplikacji np. typowych gier z akceleracją 3D wykorzystujących w pełni możliwości i zasoby sprzętu na którym są uruchamiane. Testy syntetyczne są często dość niskopoziomowe. Mierzą bezpośrednio wydajność takich komponentów jak CPU, pamięć czy też średni czas dostępu do danych na dysku. Sprawdzają się one tak długo, jak zdajemy sobie sprawę z ich ograniczeń i rozumiemy ich cel. Ich główną wadą jest to że nie mówią nic o wydajnośc systemu w "typowych" sytuacjach w czasie normalnego funkcjonowania.
Ważną rzeczą jest, aby testowany był tylko jeden pojedyńczy aspekt niezależny od innych. Np. test karty sieciowej powinien sprawdzać jej wydajność a nie wydajność całego systemu. Jeżeli procedura testująca jest dobra to wyniki dla danej karty bedą zbliżone niezależnie czy tetsowane były na szybkim czy na wolnym komputerze. W przeciwnym przypadku wyniki takich testów niewiele mówią o samej karcie, gdyż możliwych kombinacji sprzęt/sterowniki/system operacyjny jest tak wiele że ciężko by było na podstawie takich testów wyciągnąć jakieś konkretne wnioski co do samej karty.

Do testowania wydajności poszczególnych komponentów w linuxie mamy do dyspozycji bardzo wiele różnych narzędzi. Ciężko bybyło wymienić je wszystkie, większość z nich ma dość unikalne własności co powoduje że ciężko je omawiać jako grupę na jakims konkretnym przykładzie. Większość z nich jest jednak bardzo łatwo dostępna w sieci jako oprogramowanie open source. Dostepna jest także ich pełna dokumentacja.

Jakich narzędzi może użyć każdy?
Testem który każdy może wykonać jest rekompilacja jądra ze standardowymi opcjami. Czas zużyty na kompilację Reported Time może być niezłym miernikiem szybkosci systemu. Inne narzędzia to m.in. X-Bench mierzący wydajność serwera X, Unixbench testujący ogólna wydajność systemu unixowego (w tym między innymi czas operacji wejścia-wyjścia, wydajność wielozadaniowości jadra) oraz program Bytemarak mierzący wydajność procesora oraz pamięci. Oprócz tego samemu można wywołać kilka wbudowanych w system unixowy poleceń. Np. polecenie ps wywołane z odpowiednimi parametrami daje obraz tego jakie procesy aktualnie korzystają z systemu (zobacz man ps). Polecenie top daje spojrzenie w czasie rzeczywistym na działalność procesora. Wyświetla listę najbardziej zasobożernych zadań systemu i udostępnia interakcyjny interfejs obsługi procesów. Może sortować zadania wg zużycia CPU, pamięci i czasu działania. Do monitorowania zużycia pamieci służą polecenia: vmstat - zbierający statystyki dotyczące pamięci wirtualnej (man vmstat), iostat mierzący wydajność procedur wejścia-wyjścia, oraz free, który wyświetla informacje o pamięci wolnej i zużywanej w systemie.