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
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.
Jeśli jakiś test sie nie powiedzie pasek bedzie czerwony i zostanie
wypisany stosowny komunikat informujący o znalezionym błędzie.
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.
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:
ż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.
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ę:
Teraz można już ustalać ilość wirtualnych użytkowników, czas w jakim
mają się oni ‘uaktywniać’ oraz liczbę powtarzania ich czynności.
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:
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:
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.