Najpopularniejszym narzędziem do uruchamiania testów obciążeniowych jest JMeter. Nie potrafię się do niego przekonać - organicznie nie cierpię modelowania struktury kodu (tu - testów) w formie rozwijanego drzewa, gdzie zrozumienie co robią poszczególne elementy wymaga przeklikiwania się przez nie, a niekiedy nawet otwierania okienek właściwości. Prosty kod jest zdecydowanie czytelniejszy. Podobnie, wolę napisać wywołanie funkcji niż przeszukiwać wielopoziomowe prawoklikane menu.
Dlatego opisuję interesującą, lekką alternatywę, ciekawie łączącą testy wydajnościowe z jednostkowymi.
FunkLoad
Mam na myśli FunkLoad. Jest to prosta wielowątkowa napędzarka napisana w Pythonie.
Scenariusze zapisywane są w formacie Pythonowych unittestów i z zasady mogą być uruchamiane zarówno masowo - w celu zrobienia benchmarku, jak jednorazowo - w celu weryfikacji poprawności działania serwisu.
Testowany serwis nie musi być Pythonowy, sami autorzy testują min. aplikacje pisane w Javie.
Dzięki użyciu Pythona do zapisu schematów działania mamy do dyspozycji pełną siłę tego języka i jego bibliotek - nie stanowi więc problemu np. wyłuskanie jakiejś informacji z otrzymanej strony, wyszukiwanie wzorców, generowanie testowych danych.
API obsługujące interakcję HTTP z testowanym serwerem oparte jest o webunit, co zapewnia min. automatyczne pobieranie wraz z ładowanymi stronami także używanych przez nie obrazków czy arkuszy styli (można to wyłączyć), obsługę cookie, respektowanie redirectów itp.
Jest też trochę interesujących narzędzi pomocniczych - proxy nagrywające jako test sekwencję operacji realizowanych w przeglądarce, możliwość automatycznego podglądania pobieranych przez test stron w przeglądarce, funkcje do tworzenia danych testowych, generator sympatycznie wyglądających raportów.
Wyniki testu są (poza raportem) dostępne w formie pliku XML.
Instalacja
Potrzebnych jest trochę narzędzi i bibliotek. W przypadku Ubuntu wystarczy:
$ sudo apt-get install tcpwatch-httpproxy \ python-dev python-xml python-setuptools \ python-webunit python-docutils gnuplot
Na innych systemach trzeba w dowolny sposób zainstalować powyższe biblioteki Pythonowe i GnuPlota, jedynym niestandardowym elementem jest TCPWatch.
Sam funkload
najprościej zainstalować przy
pomocy easy_install
. W moim wypadku:
$ mkvirtualenv funkload $ easy_install funkload
z późniejszym workon funkload
ilekroć mam zamiar
uruchamiać to narzędzie
(w razie potrzeby patrz co to jest mkvirtualenv).
Można też mniej porządnie:
$ sudo easy_install funkload
W repozytoriach Debiana i Ubuntu jest pakiet
funkload
ale ... nie działa, skrypty w ogóle się nie uruchamiają (problemy z wyborem właściwej wersji używanego Pythona), zresztą jest to dość stara wersja.Można wypróbować plik .deb ze strony autorów, na części dystrybucji zapewne zadziała, u mnie (9.04) niestety nie chce się zainstalować: (
funkload: Depends: python-webunit (< 1.3.9) but 1:1.3.8-2 is to be installed
).
Uwaga: samo easy_install funkload
, bez wcześniejszej
instalacji pomocniczych narzędzi, nie wystarczy - efektem
będzie zubożona wersja pozbawiona części skryptów.
Efektem instalacji powinno być pojawienie się skryptów:
fl-run-test
(uruchamianie jako testu jednostkowego),fl-run-bench
(uruchamianie jako benchmarku/testu obciążeniowego),fl-record
(tworzenie wstępnej wersji testu przez nagranie akcji realizowanych w przeglądarce),fl-build-report
(generowanie raportu),fl-monitor-ctl
(opcjonalne pomocnicze narzędzie zbierające informacje o obciążeniu maszyny),fl-credential-ctl
(opcjonalne pomocnicze narzędzie do przechowywania haseł)fl-install-demo
(o którym za moment).
Test poinstalacyjny (i przegląd przykładów)
Skrypt fl-install-demo
generuje katalog z przykładami
kilku testów. Po prostu:
$ cd /gdzies/tam $ fl-install-demo $ ls ./funkload-demo
Powstaje kilka przykładów (min. dotyczące Zope i JBossa, narzędzi z
których korzystano/korzysta się w
nuxeo). Najbardziej realistycznym do
poczytania jest seam-booking
, pokazujący (plik
test_SeamBooking.py
) jak może wyglądać symulacja
sekwencji zatwierdzanych
formularzy połączona z wyłuskiwaniem i kopiowaniem ukrytych pól
a także ustawianiem asercji na treść odpowiedzi.
Przykład ten używa
find
do lokalizowania ukrytych zmiennych formularza. Oczywiście można skorzystać z wyrażeń regularnych albo nawet parsera HTML (funkload
ma nawet klej składniowy ułatwiający użycie SimpleDOM) ale wiązałoby się to z nieco większym obciażeniem procesora przez napędzarkę, dlatego przy sztywnej strukturze analizowanych stron jest to rozsądny wybór.
Z kolei simple
to proste narzędzie mierzące czasy otwierania
wybranych bezkontekstowych stron, które można wykorzystać
do przetestowania instalacji bez konieczności edytowania kodu.
Tu wystarczy otworzyć w edytorze Simple.conf
, poprawić url
(główny
adres testowanej aplikacji) oraz pages
(lista otwieranych stron) i
ewentualnie cycles
(ilość testowanych równoległych użytkowników,
np. 10:20:30
oznacza trzy testy, najpierw z dziesięcioma, potem z
dwudziestoma, wreszcie z trzydziestoma symulowanymi użytkownikami).
No i odpalamy. Najpierw jednorazowo:
$ fl-run-test -v test_Simple.py test_simple (test_Simple.Simple) ... Ok ---------------------------------------------------------------------- Ran 1 test in 5.139s OK
Potem (mając uruchomionego Firefoksa) z prezentacją ściąganych stron w przeglądarce:
$ fl-run-test --firefox-view test_Simple.py
Przy uruchomieniu z powyższą opcją (którą można skrócić jako
-V
) każda pobrana strona zostanie zapisana w katalogu tymczasowym i otwarta w nowym tabie przeglądarki. Jest to bardzo poręczne - pozwala naocznie weryfikować przebieg testu, ułatwia też wykopiowywanie fragmentów tekstu do użycia w asercjach.Uwaga: strony nie wyglądają w pełni poprawnie, bo względne linki (np. do CSS czy grafik) nie są korygowane i wiodą w pustkę. Do zorientowania się w treści strony prezentowany obraz w zupełności jednak wystarcza.
Inne przydatne opcje fl-run-test
to --pause
(sprawia, że
skrypt zaczeka na naciśnięcie
Enter przed pobieraniem każdej strony), --simple-fetch
(nie będą
pobierane obrazki i pliki CSS) oraz -d
(szczegółowe informacje
diagnostyczne). Te ostatnie mogą wyglądać tak:
test_simple: Starting ----------------------------------- Access 1 times the following pages: /mk/smtuning/index:/mk/mieszkanie/index:/mk/szachy/index test_simple: setUp test_simple: Try 0 test_simple: GET: http://localhost:82/mk/smtuning/index Page 1: Get /mk/smtuning/index ... test_simple: Done in 0.269s test_simple: Load css and images... test_simple: Done in 0.110s test_simple: GET: http://localhost:82/mk/mieszkanie/index Page 2: Get /mk/mieszkanie/index ... test_simple: Done in 0.077s test_simple: Load css and images... test_simple: Done in 0.320s test_simple: GET: http://localhost:82/mk/szachy/index Page 3: Get /mk/szachy/index ... test_simple: Done in 0.191s test_simple: Load css and images... test_simple: Done in 0.000s test_simple: tearDown.
Wreszcie, próbujemy uruchomić benchmark (opcję -c
czyli
ilość symulowanych użytkowników podaję
jako ciekawostkę, nadmazuje ona ustawienia z pliku .conf
):
$ fl-run-bench -c 1:10:20:30:40:50 \ test_Simple.py Simple.test_simple ======================================================================== Benching Simple.test_simple ======================================================================== Access 1 times the following pages: /mk/smtuning/index:/mk/mieszkanie/index:/mk/szachy/index ------------------------------------------------------------------------ Configuration ============= * Current time: 2009-09-14T22:14:34.827066 * Configuration file: /home/marcin/robocze/funkload-demo/simple/Simple.conf * Log xml: /home/marcin/robocze/funkload-demo/simple/simple-bench.xml * Server: http://localhost:82 * Cycles: [1, 10, 20, 30, 40, 50] * Cycle duration: 20s * Sleeptime between request: from 0.0s to 0.5s * Sleeptime between test case: 1.0s * Startup delay between thread: 0.05s Benching ======== Cycle #0 with 1 virtual users ----------------------------- * Start monitoring localhost: ... done. * Current time: 2009-09-14T22:14:34.909006 * Starting threads: . done. * Logging for 20s (until 2009-09-14T22:14:54.967133): ..... done. * Waiting end of threads: . done. * Waiting cycle sleeptime 1s: ... done. * Stop monitoring localhost: done. * End of cycle, 22.32s elapsed. * Cycle result: **SUCCESSFUL**, 5 success, 0 failure, 0 errors. Cycle #1 with 10 virtual users ------------------------------ * Start monitoring localhost: ... done. * Current time: 2009-09-14T22:14:57.168425 * Starting threads: .......... done. * Logging for 20s (until 2009-09-14T22:15:17.795262): ............................. done. * Waiting end of threads: .......... done. * Waiting cycle sleeptime 1s: ... done. * Stop monitoring localhost: done. * End of cycle, 26.34s elapsed. * Cycle result: **SUCCESSFUL**, 29 success, 0 failure, 0 errors. ... End of loop: 100 pages rendered in 8.164s, avg of 0.082s per page, 12.248 SPPS without concurrency. . ---------------------------------------------------------------------- Ran 1 test in 12.064s OK
Warto zwrócić uwagę, że o ile w fl-run-test
podawaliśmy tylko
nazwę pliku, tu podajemy też nazwę klasy i metody (jeśli mamy
kilka metod testowych, benchmarkujemy każdą z osobna).
Efektem powyższego jest powstanie w bieżącym katalogu
plików simple-bench.xml
i simple-bench.log
(lokalizację i nazwy można zmienić w pliku .conf
lub parametrem).
Ten pierwszy to właściwy pakiet wyników, wyglądający mniej
więcej tak:
<funkload version="1.10.0" time="2009-09-14T22:14:34.829103"> <config key="sleep_time_min" value="0.0" /> <config key="class_description" value="Simply testing a default apache 2 server" /> <config key="cycles" value="[1, 10, 20, 30, 40, 50]" /> (...) <response cycle="000" cvus="001" thread="000" suite="Simple" name="test_simple" step="001" number=" 001" type="get" result="Successful" url="/mk/smtuning/index" code="200" description="Get /mk/smtuni ng/index" time="1252959274.91" duration="0.150722026825" /> <response cycle="000" cvus="001" thread="000" suite="Simple" name="test_simple" step="001" number="002" type="link" result="Successful" url="/ajax/libs/yui/2.6.0/build/reset-fonts/reset-fonts.css" code="200" description="" time="1252959275.07" duration="0.0776710510254" /> <response cycle="000" cvus="001" thread="000" suite="Simple" name="test_simple" step="001" number="003" type="link" result="Successful" url="/ajax/libs/yui/2.6.0/build/base/base-min.css" code="200" description="" time="1252959275.15" duration="0.0502681732178" /> (...) <monitor key="test_simple:0:1" loadAvg1min="0.11" tasks="421" loadAvg5min="0.55" swapTotal="2626588" memFree="24100" loadAvg15min="0.99" swapFree="1872184" receivePackets="68480425" running="3" IDLTotalJiffies="182256601" receiveBytes="10472803362" host="localhost" memTotal="882532" transmitBytes="10472803362" time="1252959275.04" interface="lo" transmitPackets="68480425" CPUTotalJiffies="9049751" /> <monitor key="test_simple:0:1" loadAvg1min="0.11" tasks="421" loadAvg5min="0.55" swapTotal="2626588" memFree="24028" loadAvg15min="0.99" swapFree="1872184" receivePackets="68480480" running="2" IDLTotalJiffies="182256684" receiveBytes="10472832733" host="localhost" memTotal="882532" transmitBytes="10472832733" time="1252959275.55" interface="lo" transmitPackets="68480480" CPUTotalJiffies="9049765" /> (...) </funkload>
Wreszcie - budowa raportu:
$ fl-build-report --html simple-bench.xml Creating html report: ...done: file:///home/marcin/robocze/funkload-demo/simple/test_simple-20090914T221434/index.html
Powyższy plik można otworzyć w przeglądarce, jest to czytelne zwarte podsumowanie wyników.
Inny sposób generowania raportu to:
$ fl-build-report simple-bench.xml > raport.restPowstaje plik w formacie ReStructuredText.
Pliki opisujące test
Test standardowo składa się z dwóch plików.
Skrypt pythonowy (test_Nazwa.py
) zawiera właściwą sekwencję kroków
do wykonania - jakie strony otwierać, jak weryfikować ich poprawność,
jak wypełniać danymi formularze itp. W przykładach każda z klas ma
tylko jedną metodę testującą, ale można stworzyć ich więcej.
Plik konfiguracyjny (Nazwa.conf
) to parametry techniczne.
Ustalane są tam lokalizacje plików wynikowych, charakterystyki
czasowe testu (ilości uruchamianych klientów, odstępy czasowe
między żądaniami, czasy trwania wstępnego rozruchu i właściwego
pomiaru), tekstowe etykiety do użycia w raporcie i kilka pokrewnych
parametrów. Można dodać także własne - np. sensownym pomysłem
może być umieszczenie tu adresu testowanej aplikacji.
Niemal wszystkie parametry z pliku .conf
można nadmazać
argumentami uruchamianych komend (np. zamiast opisywać
czas trwania benchmarku w stałej duration
w pliku konfiguracyjnym
można go zadać parametrem --duration
polecenia fl-run-bench
).
Tworzenie testów
Testy można pisać ręcznie, ale wygodniej jest je nagrać i potem dopracować.
Nagrywanie sesji w przeglądarce
FunkLoad, podobnie jak kilka innych narzędzi, umie nagrać sekwencję żądań generowanych przez przeglądarkę, a robi to przy pomocy specjalizowanego programu działajacego jako proxy HTTP.
Proxy to uruchamiamy poleceniem fl-record
:
$ mkdir testy_programu_x $ cd testy_programu_x $ fl-record -p 9090 szukanie_partii Hit Ctrl-C to stop recording. HTTP proxy listening on :9090 Recording to directory /tmp/tmpDsArDA_funkload.
Parametr -p
zadaje numer portu, na którym działa proxy.
Trzeba też podać nazwę testu (powyżej - szukanie_partii
), która
posłuży do wygenerowania nazwy pliku, klasy i metody testowej
(w tym wypadku powstaną pliki test_SzukaniePartii.py
oraz SzukaniePartii.conf
, w tym pierwszym znajdzie
się klasa SzukaniePartii
z metodą test_szukanie_partii
).
Ten parametr jest ważny, jeśli go zapomnimy,
fl-record
nie stworzy żadnych plików a jedynie zrzuci kluczowy fragment skryptu na standardowe wyjście.
Następnie trzeba chwilowo przełączyć przeglądarkę tak, by wykorzystywała powyższe proxy. Bardzo przydaje się tutaj FoxyProxy - dodatek do Firefoksa, który pozwala przełączać między różnymi proxy (a także nie używaniem proxy w ogóle) przy pomocy jednego kliknięcia w ikonkę na pasku statusu.
Zatem:
- instalujemy FoxyProxy (i restartujemy przeglądarkę, by aktywować dodatek),
- otwieramy opcje dodatku (lewoklik w napis
FoxyProxy: Disabled
na pasku statusu albo prawoklik i wybórOptions
z menu), - w zakładce
Proxies
wybieramyAdd New Proxy
, - w otwartym oknie: w zakładce
Proxy Details
wybieramyManual Proxy Configuration
, podając127.0.0.1
jako adres i port zgodny z użytym przy uruchamianiufl-record
(tu - 9090), a w zakładceGeneral
wpisujemy jakąś sensowną nazwę (np.FunkLoad - nagrywarka
), - zamykamy okna (pojawi się jeszcze ostrzeżenie, że nowo dodane proxy nie będzie używane o ile nie zostanie ręcznie wybrane - FoxyProxy służy także do automatycznego konfigurowania różnych proxy zależnie od odwiedzanych stron, to zastosowanie nie jest nam tu potrzebne).
Od tego momentu aby przestawić przeglądarkę na puszczanie ruchu
przez nagrywarkę wystarczy prawokliknąć znacznik FoxyProxy
na pasku
statusu i wybrać odpowiednią pozycję. Analogicznie wracamy do
normalnego działania.
Zatem uruchamiamy fl-record
, przełączamy się nań jako na proxy
i po prostu chodzimy po testowanej aplikacji.
Po wykonaniu planowanych operacji wracamy do terminala,
w którym działa fl-record
i naciskamy Ctrl-C.
Program przerywa działanie i tworzy pliki .py
i .conf
.
^CTCPWatch finished. Creating script: ./test_SzukaniePartii.py. Creating configuration file: ./SzukaniePartii.conf.
Oczywiście można
fl-record
zaraz potem uruchomić powtórnie, z inną nazwą testu - w ten sposób nagrywając różne scenariusze bez rekonfiguracji przeglądarki.
Po zakończeniu nagrywania przełączamy przeglądarkę z powrotem w normalny tryb działania.
Automatycznie stworzony skrypt wygląda mniej więcej tak (tu przykład z przeklikania przez parę stron WatchBota):
# -*- coding: iso-8859-15 -*- """szukanie_partii FunkLoad test $Id: $ """ import unittest from funkload.FunkLoadTestCase import FunkLoadTestCase from webunit.utility import Upload from funkload.utils import Data #from funkload.utils import xmlrpc_get_credential class SzukaniePartii(FunkLoadTestCase): """XXX This test use a configuration file SzukaniePartii.conf. """ def setUp(self): """Setting up test.""" self.logd("setUp") self.server_url = self.conf_get('main', 'url') # XXX here you can setup the credential access like this # credential_host = self.conf_get('credential', 'host') # credential_port = self.conf_getInt('credential', 'port') # self.login, self.password = xmlrpc_get_credential(credential_host, # credential_port, # XXX replace with a valid group # 'members') def test_szukanie_partii(self): # The description should be set in the configuration file server_url = self.server_url # begin of test --------------------------------------------- self.get(server_url + "/mk/watchbot/index", description="Get /mk/watchbot/index") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308929&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Findex&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F2075658&dt=1253308929650&correlator=1253308929661&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1098108903&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=9&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=381&xpc=tpFogaVhRa&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get(server_url + "/mk/watchbot/search", description="Get /mk/watchbot/search") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308931&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Findex&dt=1253308932227&correlator=1253308932246&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1637732867&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=10&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=129&xpc=kCa3r85ZMm&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.post(server_url + "/mk/watchbot/search/player", params=[ ['color', ''], ['player', 'alefzero']], description="Post /mk/watchbot/search/player") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308941&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fplayer&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch&dt=1253308942367&correlator=1253308942384&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1816251964&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=11&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=385&xpc=2ffzSVCW9K&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Falefzero.xml", description="Get /reader/api/0/subscribed") self.get(server_url + "/mk/watchbot/game/1921371", description="Get /mk/watchbot/game/1921371") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=60&slotname=1054995717&w=234&lmt=1253308947&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F1921371&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fplayer&dt=1253308947628&correlator=1253308947632&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1295369605&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=12&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=780&bih=708&fu=0&ifi=1&dtd=M&xpc=MfgZ8gRBt0&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308947&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F1921371&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fplayer&dt=1253308949423&prev_slotnames=1054995717&correlator=1253308947632&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1295369605&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=12&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=780&bih=708&fu=0&ifi=2&dtd=9&xpc=GsYclJhI06&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.post(server_url + "/mk/watchbot/related_games", params=[ ['game_id', '1921371'], ['white', 'alefzero'], ['black', 'ewankolang'], ['variant', 'standard']], description="Post /mk/watchbot/related_games") self.post(server_url + "/mk/watchbot/previous_part", params=[ ['clock_increment', '45'], ['variant', 'standard'], ['black', 'ewankolang'], ['clock_base', '45'], ['game_id', '1921371'], ['white', 'alefzero']], description="Post /mk/watchbot/previous_part") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Falefzero.xml", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Fewankolang.xml", description="Get /reader/api/0/subscribed") self.get(server_url + "/mk/watchbot/player/ewankolang", description="Get /mk/watchbot/player/ewankolang") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308966&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fplayer%2Fewankolang&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F1921371&dt=1253308966582&correlator=1253308966591&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=791246183&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=13&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=124&xpc=iIfM2y0d2A&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Fewankolang.xml", description="Get /reader/api/0/subscribed") self.get(server_url + "/mk/watchbot/index", description="Get /mk/watchbot/index") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308969&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Findex&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fplayer%2Fewankolang&dt=1253308969984&correlator=1253308969989&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=813049338&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=14&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&eid=36815002&fu=0&ifi=1&dtd=128&xpc=4nz8W6iSk7&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get("http://pagead2.googlesyndication.com/pagead/gen_204?id=osd&ad0=p%3A%2C328%2C610%2C478%2C790%2Ctos%3A%2C0%2C0%2C0%2Ctfs%3A%2C1401%2Ctls%3A%2C-1%2Cfp%3A%2C%26slotname%3D6992303502%26dt%3D1253308969984&bs=780,708&ps=763,1125&fp=%3Fclient%3Dca-pub-6046952099901614%26url%3Dhttp%253A%252F%252Fmekk.waw.pl%252Fmk%252Fwatchbot%252Findex%26correlator%3D1253308969989%26eid%3D36815002&deb=1-1-1-0-1-0&tt=1402&r=p", description="Get /pagead/gen_204") self.get(server_url + "/mk/watchbot/search", description="Get /mk/watchbot/search") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253308972&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Findex&dt=1253308973370&correlator=1253308973391&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1812156353&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=15&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=148&xpc=SDiRtQX3Bk&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.post(server_url + "/mk/watchbot/search/advanced", params=[ ['player2', ''], ['Search', 'submit'], ['highrated', '1'], ['player1', ''], ['clock', '45_45'], ['variant', 'standard'], ['color1', 'any'], ['color2', 'any'], ['before', '']], description="Post /mk/watchbot/search/advanced") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=60&slotname=0717372261&w=468&lmt=1253309012&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fadvanced&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch&dt=1253309012984&correlator=1253309013008&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=663727162&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=16&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=1&dtd=321&xpc=D81LNVtiHT&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253309012&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fadvanced&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch&dt=1253309013522&prev_slotnames=0717372261&correlator=1253309013008&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=663727162&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=16&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=763&bih=708&fu=0&ifi=2&dtd=5&xpc=sTiAZXzbTP&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get(server_url + "/mk/watchbot/game/2017244", description="Get /mk/watchbot/game/2017244") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=60&slotname=1054995717&w=234&lmt=1253309029&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F2017244&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fadvanced&dt=1253309030166&correlator=1253309030174&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1062293505&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=17&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=780&bih=708&fu=0&ifi=1&dtd=413&xpc=q1OppjyXi7&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253309029&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F2017244&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fadvanced&dt=1253309030841&prev_slotnames=1054995717&correlator=1253309030174&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1062293505&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=17&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=780&bih=708&fu=0&ifi=2&dtd=5&xpc=gbC2a9MDOg&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-6046952099901614&output=html&h=150&slotname=6992303502&w=180&lmt=1253309029&flash=10.0.32&url=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fgame%2F2017244&ref=http%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fsearch%2Fadvanced&dt=1253309031037&prev_slotnames=1054995717%2C6992303502&correlator=1253309030174&frm=0&ga_vid=1850784160.1192365704&ga_sid=1253307215&ga_hid=1062293505&ga_fc=1&ga_wpids=UA-169694-4&u_tz=120&u_his=17&u_java=0&u_h=1050&u_w=1680&u_ah=1000&u_aw=1680&u_cd=24&u_nplug=9&u_nmime=96&biw=780&bih=708&fu=0&ifi=3&dtd=5&xpc=81LhDH4U2I&p=http%3A//mekk.waw.pl", description="Get /pagead/ads") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Ffeeds.mekk.waw.pl%2FWatchbotInterestingGames", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Fpawns.xml", description="Get /reader/api/0/subscribed") self.get("http://www.google.com/reader/api/0/subscribed?s=feed%2Fhttp%3A%2F%2Fmekk.waw.pl%2Fmk%2Fwatchbot%2Fatom%2Fafegasta.xml", description="Get /reader/api/0/subscribed") # end of test ----------------------------------------------- def tearDown(self): """Setting up test.""" self.logd("tearDown.\n") if __name__ in ('main', '__main__'): unittest.main()
Dla kompletności: tak wygląda wygenerowany plik .conf
:
# FunkLoad test configuration file # $Id: $ # ------------------------------------------------------------ # Main section # [main] title=SzukaniePartii description=XXX the TestCase class description # the server url to test url=http://mekk.waw.pl # the User-Agent header to send default is 'FunkLoad/1.xx' examples: #user_agent = Opera/8.0 (Windows NT 5.1; U; en) #user_agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) #user_agent = Mozilla/5.0 (X11; U; Linux i686; en; rv:1.7.10) Gecko/20050912 Firefox/1.0.6 # ------------------------------------------------------------ # Tests description and configuration # [test_szukanie_partii] description=XXX the test case description # ------------------------------------------------------------ # Credential access # [credential] host=localhost port=8007 # ------------------------------------------------------------ # Monitoring configuration # [monitor] #hosts=localhost # Each host in [monitor]hosts should have a section # with a 'port' and 'description' keys [localhost] port=8008 description=The benching machine # ------------------------------------------------------------ # Configuration for unit test mode fl-run-test # [ftest] # log_to destination = # console - to the screen # file - to a file log_to = console file # log_path = path and file name to store log file log_path = szukanie_partii-test.log # result_path = path to store the xml result file result_path = szukanie_partii-test.xml # ok_codes = list of successfull HTTP response code default is 200:301:302 # ok_codes = 200:301:302 # sleeptime_min = minimum amount of time in seconds to sleep between requests # to the host sleep_time_min = 0 # sleeptime_max = maximum amount of time in seconds to sleep between requests # to the host sleep_time_max = 0 # ------------------------------------------------------------ # Configuration for bench mode fl-run-bench # [bench] # cycles = list of cycles with their number of concurrent users cycles = 1:2:3 # duration = duration of a cycle in seconds duration = 30 # startup_delay = time to wait between starting-up threads in seconds startup_delay = 0.2 # sleep_time = time to wait between test in seconds sleep_time = 1 # cycle_time = time to wait between cycle in seconds cycle_time = 1 # same keys than in [ftest] section log_to = file log_path = szukanie_partii-bench.log result_path = szukanie_partii-bench.xml #ok_codes = 200:301:302 sleep_time_min = 0 sleep_time_max = 2
Moim zdaniem oba są całkiem czytelne.
Edycja testu
Wygenerowany test wymaga pewnej edycji.
Oczywistą sprawą jest usunięcie śmieci (reklam, pomocniczych widgetów, stron zarejestrowanych w efekcie omyłkowych kliknięć itp).
Dłuższe sekwencje można podzielić na kilka funkcji - o ile mogą być uruchamiane niezależnie.
Ciekawsza, a ważna, sprawa dotyczy przenoszenia danych. Nagranie
zawiera sztywne, stałe wartości dla wszystkich pól formularzy. Dla
pól typu hidden
bardzo często trzeba zachować się inaczej -
wyparsować odpowiednią wartość z poprzednio otrzymanego HTML i użyć
jej w kolejnym żądaniu (trzeba to np. robić gdy są stosowane
zabezpieczenia przed CSRF lub gdy w ukrytych polach lub parametrach są
przekazywane identyfikatory sesji).
Do odczytania danych można wykorzystać dowolną lubianą technikę
parsowania HTML (dostępnego jako response.body
), od szukania stałych
tekstowych, przez regexpy, po prawdziwy parser HTML. FunkLoad (a
raczej webunit) ułatwia wykorzystywanie SimpleDOM (funkcje
response.getDOM()
, resonse.extractForm()
i response.getForm()
),
niestety ten parser potrafi być dosyć kapryśny (zgłaszając błędy dla
poprawnych stron, zwłaszcza zawierających sporo
JavaScriptu). BeautifulSoup (używany samodzielnie albo za
pośrednictwem lxml) sparsuje niemal wszystko.
Podobna obsługa może być wymagana przy ustalaniu adresów otwieranych stron (np. gdy test klika link o zmiennych parametrach).
Ostatni ważny krok to dodanie asercji weryfikujących czy została otwarta właściwa strona i czy zawiera ona poprawne dane.
Można tu podchodzić dwojako: przeprowadzić szczegółową weryfikację poprawności strony albo po prostu zweryfikować obecność kilku charakterystycznych tekstów - by potwierdzić, że otwarta została właściwa strona (co w kontekście testu benchmarkowego może być bardziej naturalne, a na pewno jest tańsze).
Proste asercje bardzo wygodnie dodaje się przy pomocy:
$ fl-run-test --firefox-view --pause test_SzukaniePartii.py
W tym trybie po pobraniu każdej strony FunkLoad zatrzymuje się i pokazuje ją w przeglądarce. Można wykopiować charakterystyczny fragment i dodać odpowiadającą mu asercję, np:
self.get(server_url + "/mk/watchbot/index", description="Get /mk/watchbot/index") self.assert_("It's main purpose is to preserve comments" in self.getBody())
albo (funkcje get
i post
zwracają obiekt odpowiedzi, metoda getBody
odwołuje się do ostatnio pobranej odpowiedzi):
r = self.get(server_url + "/mk/watchbot/index", description="Get /mk/watchbot/index") self.assert_("It's main purpose is to preserve comments" in r.body) self.assert_("Recent Games" in r.body)
Po dodaniu asercji naciskamy Enter w terminalu i powtarzamy to samo dla kolejnej strony.
Przykład końcowego efektu:
# -*- coding: utf-8 -*- import unittest from funkload.FunkLoadTestCase import FunkLoadTestCase class SzukaniePartii(FunkLoadTestCase): def setUp(self): self.logd("setUp") self.server_url = self.conf_get('main', 'url') def test_view_index(self): server_url = self.server_url self.get(server_url + "/mk/watchbot/index", description="Get /mk/watchbot/index") self.assert_("It's main purpose is to preserve comments" in self.getBody()) self.assert_("Recent Games" in self.getBody()) def test_simple_search_and_open(self): server_url = self.server_url r = self.get(server_url + "/mk/watchbot/search", description="Get /mk/watchbot/search") self.assert_("Find games played by given player." in r.body) self.assert_("Recent Games" in r.body) r = self.post(server_url + "/mk/watchbot/search/player", params=[ ['color', ''], ['player', 'alefzero']], description="Post /mk/watchbot/search/player") self.assert_("Recent games played by alefzero" in r.body) self.assert_("aidog(2149) vs. alefzero(2240)" in r.body) self.assert_("Newest 20 games were displayed" in r.body) r = self.get(server_url + "/mk/watchbot/game/1164729", description="Get /mk/watchbot/game/1164729") self.assert_("<h1>alefzero(2231) vs. petersanderson(2241) 1-0</h1>" in r.body) r = self.post(server_url + "/mk/watchbot/related_games", params=[ ['game_id', '1164729'], ['white', 'alefzero'], ['black', 'petersanderson'], ['variant', 'standard']], description="Post /mk/watchbot/related_games") self.assert_("Related Games" in r.body) self.assert_("petersanderson(2188) vs. alefzero(2243) 1/2-1/2" in r.body) r = self.post(server_url + "/mk/watchbot/previous_part", params=[ ['clock_increment', '45'], ['variant', 'standard'], ['black', 'petersanderson'], ['clock_base', '45'], ['game_id', '1164729'], ['white', 'alefzero']], description="Post /mk/watchbot/previous_part") self.assert_("Earlier moves" in r.body) self.assert_('<a href="/mk/watchbot/game/1164592">' in r.body) def test_direct_player(self): server_url = self.server_url r = self.get(server_url + "/mk/watchbot/player/alefzero", description="Get /mk/watchbot/player/alefzero") self.assert_("Recent games played by alefzero" in r.body) self.assert_("aidog(2149)" in r.body) def test_adv_search_and_open(self): server_url = self.server_url r = self.get(server_url + "/mk/watchbot/search", description="Get /mk/watchbot/search") self.assert_("Find games using different criteria." in r.body) r = self.post(server_url + "/mk/watchbot/search/advanced", params=[ ['player2', ''], ['Search', 'submit'], ['highrated', '1'], ['player1', ''], ['clock', '45_45'], ['variant', 'standard'], ['color1', 'any'], ['color2', 'any'], ['before', '']], description="Post /mk/watchbot/search/advanced") self.assert_("Your search (criteria: clock 45+45, standard, highrated) located the following games:" in r.body) self.assert_("afegasta(2218) vs. gegitor(2317)" in r.body) self.assert_("1-0" in r.body) r = self.get(server_url + "/mk/watchbot/game/1221993", description="Get /mk/watchbot/game/1221993") self.assert_("<h1>aidog(2149) vs. alefzero(2240) 1/2-1/2</h1>" in r.body) self.assert_("whispers: I prepared this variation for a game against Nc5 player, so I analyzed it thoroughly" in r.body) r = self.post(server_url + "/mk/watchbot/related_games", params=[ ['game_id', '1221993'], ['white', 'aidog'], ['black', 'alefzero'], ['variant', 'standard']], description="Post /mk/watchbot/related_games") self.assert_("Related Games" in r.body) self.assert_('<a href="/mk/watchbot/game/1230757">' in r.body) def tearDown(self): self.logd("tearDown.\n") if __name__ in ('main', '__main__'): unittest.main()
Edycja konfiguracji
Edycja konfiguracji jest mniej wymagająca.
Można dodać tekstowe opisy testu czy jego poszczególnych kroków, które zostaną użyte w generowanym raporcie (niestety używanie polskich znaków prowadzi do błędów w trakcie tworzenia raportu).
Zwykle warto skorygować ścieżki do plików logu i wyniku (log_path
,
result_path
) - kierując je do osobnego katalogu.
Naprawdę istotne są parametry benchmarku:
-
cycles
- ilu równoczesnych użytkowników będzie symulowanych, np.1:10:20
oznacza wykonanie benchmarku najpierw z symulacją jednego użytkownika, potem z symulacją dziesięciu równoczesnych sesji, wreszcie z symulacją dwudziestu użytkowników, -
sleep_time_min
isleep_time_max
- symulowany czas namysłu użytkowników (po załadowaniu każdej strony, tj. po każdym żądaniuget
lubpost
, wątek roboczy odczeka losowy czas z tego przedziału przed generowaniem dalszych żądań), -
sleep_time
- odstęp czasowy między kolejnymi powtórzeniami testu przez ten sam wątek (przy benchmarku każdy wątek roboczy wykonuje test wielokrotnie, powtarzając go w kółko,sleep_time
to dodatkowa pauza przed kolejnym powtórzeniem), -
cycle_time
- odstęp czasowy między poszczególnymi cyklami (tj. np. między testowaniem dla jednego użytkownika a testowaniem
dla dziesięciu) - jest to czas na uspokojenie się testowanego serwera przed następną nawałą, -
startup_delay
- odstęp czasowy między startem poszczególnych wątków (pozwala symulować stopniowy wzrost obciążenia serwera zamiast nagłego skoku), -
duration
- czas pomiaru (przez ile czasu będzie generowane pełne obciężenie i zbierane będą metryki), powinien być dobrany tak, by testowa procedura zdążyła się wykonać 5-10 razy (z uwzględnieniem dodatkowych opóźnień wynikających zsleep_time_min/sleep_time_max
orazsleep_time
).
Pojedynczy cykl benchmarku (dla ustalonej liczby użytkowników) działa następująco:
- tworzona jest odpowiednia ilość wątków roboczych (co
startup_delay
startuje następny), - każdy tworzony wątek zaczyna od razu, w pętli, realizować testowaną
procedurę (pamiętając o krótkotrwałym przysypianiu wdg.
sleep_time_min
/sleep_time_max
przy każdym żądaniu HTTP i wdgsleep_time
po każdym obrocie całej procedury), - po wystartowaniu wszystkich wątków zaczynają być zbierane metryki i trwa
to przez
duration
sekund, - po upływie
duration
wątki zaczynają się kończyć.
Przykład konfiguracji dla powyższego testu:
[main] title=SzukaniePartii description=Testy przeszukiwania url=http://platon:82 user_agent = Mozilla/5.0 (X11; U; Linux i686; en; rv:1.7.10) Gecko/20050912 Firefox/1.0.6 # ------------------------------------------------------------ # Tests description and configuration # [test_view_index] description=Strona indeksu [test_simple_search_and_open] description=Simple search [test_direct_player] description=Direct player page [test_adv_search_and_open] description=Advanced search # ------------------------------------------------------------ # Monitoring configuration # [monitor] hosts=localhost [localhost] port=8008 description=Plato # ------------------------------------------------------------ # Configuration for unit test mode fl-run-test # [ftest] log_to = console file log_path = output/szukanie_partii-test.log result_path = output/szukanie_partii-test.xml ok_codes = 200:301:302 sleep_time_min = 0 sleep_time_max = 0 # ------------------------------------------------------------ # Configuration for bench mode fl-run-bench # [bench] log_to = file log_path = output/szukanie_partii-bench.log result_path = output/szukanie_partii-bench.xml ok_codes = 200:301:302 # cycles = list of cycles with their number of concurrent users cycles = 1:10:20:30 # duration = duration of a cycle in seconds duration = 30 # startup_delay = time to wait between starting-up threads in seconds startup_delay = 0.2 # sleep_time = time to wait between test in seconds sleep_time = 1 # cycle_time = time to wait between cycle in seconds cycle_time = 1 sleep_time_min = 0 sleep_time_max = 2
Uruchamianie
Test uruchamiamy tak, jak to już opisywałem wyżej:
$ fl-run-test -v test_SzukaniePartii.py
dla pojedynczego uruchomienia (weryfikacji czy wszystko działa poprawnie).
Przed odpaleniem benchmarku warto jeszcze uruchomić (na testowanym
serwerze, nie na maszynie klienckiej) fl-monitor
. Nie jest to obowiązkowe
ale uzupełni wyniki testu informacjami o obciążeniu testowanej maszyny.
Tworzymy plik monitor.conf
o treści:
[server] # configuration used by monitord host = localhost port = 8008 # sleeptime between monitoring in second # note that load average is updated by the system only every 5s interval = .5 # network interface to monitor lo, eth0 interface = lo # ------------------------------------------------------------ [client] # configuration used by monitorctl host = localhost port = 8008
i uruchamiamy serwerek:
$ fl-monitor-ctl monitor.conf start
Powyższy przykład dla - niezbyt korzystnej dla wiarygodności testu (napędzarka też obciąża maszynę) - sytuacji, w której i napędzarka i testowany serwer działają na jednej maszynie. W konfiguracji wielomaszynowej monitor należy uruchomić na serwerze na którym działa testowana aplikacja (i odpowiednio skorygować adres monitora w pliku konfiguracyjnym testu).
Wreszcie może ruszyć benchmark (jeśli mamy kilka funkcji, każdą benchmarkujemy z osobna):
$ fl-run-bench test_SzukaniePartii.py SzukaniePartii.test_simple_search_and_open
Powstaje plik XML, na bazie którego możemy stworzyć raport:
$ fl-build-report --html output/szukanie_partii-bench.xml \ --output-directory=./output
Efekt można obejrzeć tutaj.
Wady i ograniczenia
Najważniejsze ograniczenie FunkLoad to działanie napędzarki tylko na jednej maszynie. Gdy dodamy do tego używanie osobnego wątku dla każdej symulowanej sesji, mamy dość mocne ograniczenie skalowalności narzędzia. Pojedynczy serwer można z powodzeniem przetestować, wydajności większej farmy przy pomocy FunkLoad nie zweryfikujemy.
Program działa w sztywno ustalony sposób - od harmonogramu uruchamiania testów po format raportu. Nie jest łatwo wprowadzać zmiany, np. własny inny raport można oczywiście zrobić, ale wymaga to zaprogramowania go od początku (no, można skopiować oryginalny skrypt i go zmieniać).
Przy tym wszystkim narzędzie jest jednak bardzo użyteczne, testy można nagrać i dopracować bardzo małym nakładem pracy, a ich kod jest jasny i czytelny.