Poglavlje 10: Sistemsko i prihvatno testiranje

Priča za početak. Prvog oktobra 2013. američka vlada otvorila je web stranicu Healthcare.gov, preko koje su milioni građana trebali izabrati i kupiti zdravstveno osiguranje. Bio je to jedan od najvažnijih i najskupljih projekata te vlade, i cijela zemlja je čekala. Stranica je pala skoro odmah. Ljudi su satima gledali poruke o grešci, prazne stranice i beskrajno učitavanje. Prema internim bilješkama objavljenim kasnije, prvog dana je kupovinu osiguranja uspjelo završiti svega šest ljudi, a drugog dana 248. Šta se desilo? Stranica je bila planirana za oko 60 000 istovremenih korisnika, a prvih dana ih je došlo oko 250 000. Ali problem je bio dublji od pogrešne procjene. Nekoliko dana prije otvaranja urađen je test opterećenja, i stranica je pala već s nekoliko stotina istovremenih korisnika. Izvođači su kasnije pred Kongresom rekli da je za testiranje cijelog sistema trebalo nekoliko mjeseci, a dobili su oko dvije sedmice. Dijelove su pravile različite firme, i svaki dio je testiran zasebno, ali cijeli sistem, sa svim dijelovima zajedno i pod pravim opterećenjem, nije bio provjeren na vrijeme. I upozorenje koje je test dao nije zaustavilo otvaranje. Popravka je trajala mjesecima i uključila hitno okupljen tim inženjera iz velikih tehnoloških firmi. Healthcare.gov je postao školski primjer onoga što se dešava kad se sistemsko testiranje i testiranje performansi ostave za kraj, i kad se rezultati testa ne poslušaju.

Slika 10.1 – Healthcare.gov 2013: test je pokazao pad s nekoliko stotina korisnika, a došlo ih je stotine hiljada

U poglavlju 9 spojili smo dijelove DeliveryApp: pravila, servise mapa i plaćanja, API. Ovo poglavlje se penje na vrh V-modela iz poglavlja 2. Prvo sistemsko testiranje: cijeli DeliveryApp, na pravom serveru, preko prave mreže, kao što ga koristi kupac. Zatim nefunkcionalno testiranje: ne pitamo „da li radi”, nego „koliko brzo, za koliko korisnika, koliko sigurno i koliko lako”. Na kraju prihvatno testiranje: da li sistem radi ono što kupac i vlasnik proizvoda zaista žele, napisano jezikom koji razumiju svi, u stilu BDD.

Literatura. Osnovna literatura predmeta:

  1. J. Popović: Priručnik za test inženjere, CET i Računarski fakultet, Beograd, 2023.
  2. Rakić-Skoković: Priručnik za testiranje softvera, Fakultet tehničkih nauka, Novi Sad, 2013.
  3. G. J. Myers: The Art of Software Testing, 2. izdanje, John Wiley & Sons, 2004.
  4. A. P. Mathur: Foundations of Software Testing, Addison-Wesley Professional, 2008.

Za ovo poglavlje: G. Adzic: Specification by Example, Manning, 2011; OWASP Top 10 (owasp.org); dokumentacija alata Locust (docs.locust.io) i pytest-bdd (pytest-bdd.readthedocs.io).

Riječi ovog poglavlja.

  • Sistemsko testiranje – testiranje cijelog, sklopljenog sistema u okruženju što sličnijem stvarnom.
  • Funkcionalno i nefunkcionalno – funkcionalno pita šta sistem radi (cijena, stanje); nefunkcionalno pita kako to radi (brzina, sigurnost, upotrebljivost).
  • Okruženje – računari, baze i podešavanja na kojima sistem radi: razvojno, testno, predprodukcijsko (staging), produkcijsko.
  • Vrijeme odziva – koliko korisnik čeka od zahtjeva do odgovora.
  • Percentil – vrijednost ispod koje je dati procenat mjerenja; „95. percentil je 16 ms” znači da je 95 % zahtjeva završilo za najviše 16 ms.
  • Propusnost (throughput) – broj zahtjeva koje sistem obradi u sekundi.
  • Test opterećenja, stresa, skoka i izdržljivosti – testovi performansi s očekivanim, rastućim, naglim i dugotrajnim brojem korisnika.
  • OWASP Top 10 – spisak deset najčešćih kategorija sigurnosnih propusta u web aplikacijama.
  • Prihvatno testiranje (acceptance testing, UAT) – testiranje koje odlučuje da li korisnik ili naručilac prihvata sistem.
  • Kriterij prihvatanja – konkretan, provjerljiv uslov koji korisnička priča mora ispuniti da bi bila „gotova”.
  • BDD (Behavior-Driven Development) – razvoj vođen ponašanjem: zahtjevi se pišu kao primjeri ponašanja, jezikom koji razumiju i korisnik i programer, i ti primjeri postaju automatski testovi.
  • Gherkin – jezik za BDD scenarije s ključnim riječima Dato, Kada, Zatim (na engleskom Given, When, Then).

Ciljevi poglavlja. Nakon poglavlja možete: objasniti šta je sistemsko testiranje i po čemu se razlikuje od integracionog; nabrojati okruženja i reći zašto test mora ličiti na produkciju; napisati sistemski test koji pokreće pravi server i prolazi cijeli put narudžbe; razlikovati vrste testova performansi; napisati scenarij za Locust i pročitati percentile; prepoznati osnovne sigurnosne propuste iz OWASP Top 10 na primjeru DeliveryApp; objasniti vrste prihvatnog testiranja; napisati kriterije prihvatanja i BDD scenarij na bosanskom jeziku i pokrenuti ga alatom pytest-bdd.

Plan poglavlja – Sedmica 10: tri časa teorije i tri časa praktične nastave

Čas Sadržaj Na času
Teorija 1 10.1–10.3 Sistemsko testiranje; okruženja; put narudžbe od kraja do kraja Šta sve mora raditi da bi N-1001 stigla kupcu?
Teorija 2 10.4–10.6 Performanse i Locust; sigurnost; upotrebljivost 200 korisnika na DeliveryApp
Teorija 3 10.7–10.9 Prihvatno testiranje; kriteriji prihvatanja; BDD i Gherkin Scenarij „Dato – Kada – Zatim”
Praksa 1–3 Sistemski test, Locust i BDD scenariji DeliveryApp tests_sistem/, locustfile.py, tests_bdd/

Dio 1 – Sistemsko testiranje

10.1 Cijeli sistem, kao kupac

Sistemsko testiranje provjerava sklopljen sistem u cjelini, u okruženju što sličnijem onom u kojem će ga koristiti ljudi. Na V-modelu (poglavlje 2) ono stoji naspram specifikacije sistema: jedinični testovi provjeravaju dizajn funkcija, integracioni saradnju dijelova, a sistemski da li cijeli sistem ispunjava zahtjeve.

Razlika u odnosu na integraciju iz poglavlja 9:

Integraciono Sistemsko
Šta se testira saradnja dva ili više dijelova cijeli sistem
Ko obično testira programeri nezavisni testni tim
Spoljni servisi često dvojnici pravi ili vjerne kopije
Okruženje računar programera testno okruženje slično produkciji
Zahtjevi funkcionalni (šta) funkcionalni i nefunkcionalni (šta i kako)

Slika iz života: tehnički pregled auta. Mehaničar je ranije provjerio kočnice i motor odvojeno (jedinični testovi) i kako rade zajedno (integracija). Na tehničkom pregledu se auto provjerava cijeli, onakav kakav izlazi na cestu: svjetla, kočnice, izduvni gasovi, sve odjednom, po propisanoj listi.

Sistemsko testiranje je skoro uvijek crna kutija: tester gleda sistem spolja, kao korisnik, i koristi tehnike iz poglavlja 8 (klase, granice, tabele, stanja) nad cijelim sistemom.

Ukratko.

  • Sistemsko testiranje provjerava sklopljen sistem u cjelini, u okruženju sličnom stvarnom.
  • Na V-modelu stoji naspram specifikacije sistema i provjerava funkcionalne i nefunkcionalne zahtjeve.
  • Obično ga radi nezavisan testni tim, tehnikama crne kutije.
  • Za razliku od integracije, koristi prave ili vjerne kopije spoljnih servisa.

10.2 Okruženja

Program koji radi „na mom računaru” ne mora raditi na serveru. Zato sistemi obično imaju nekoliko okruženja:

Okruženje Za šta Podaci
razvojno (development) programer piše i probava kod izmišljeni, mali
testno (test, QA) testeri testiraju novu verziju testni skup (N-1001, K-01…)
predprodukcijsko (staging) posljednja proba, što sličnije produkciji kopija strukture produkcije, bez stvarnih ličnih podataka
produkcijsko (production) stvarni korisnici stvarni

Sistemski test ima smisla samo ako okruženje liči na produkciju: ista verzija Pythona, ista baza, ista podešavanja, slična količina podataka. Healthcare.gov je dobar primjer suprotnog: dijelovi su testirani u svojim okruženjima, a cijeli sistem skoro nikako.

Dvije zamke:

  1. Razlike u okruženju. Test prolazi na testnom serveru s 10 narudžbi u bazi, a produkcija s 10 miliona je spora. Ili test koristi drugi format datuma od produkcije.
  2. Stvarni podaci u testu. Kopirati produkcijsku bazu s imenima, adresama i karticama kupaca na testni server je zabranjeno zakonima o zaštiti podataka (GDPR u EU, zakon o zaštiti ličnih podataka u BiH) i opasno. Testni podaci se izmišljaju ili se stvarni podaci anonimizuju. Ista zabrana važi za AI razgovore: stvarni podaci kupaca ne idu u AI alat.

Ukratko.

  • Sistem prolazi kroz razvojno, testno, predprodukcijsko i produkcijsko okruženje.
  • Sistemski test ima smisla samo u okruženju koje liči na produkciju.
  • Razlike u okruženju (verzije, količina podataka, podešavanja) česte su uzrok grešaka koje se vide tek u produkciji.
  • Stvarni lični podaci ne smiju u testno okruženje ni u AI alat; koriste se izmišljeni ili anonimizovani.

10.3 Put narudžbe od kraja do kraja

Šta sve mora raditi da bi N-1001 stigla kupcu? Server mora biti pokrenut, mreža mora raditi, API mora primiti JSON, pravila moraju izračunati 24,50 €, narudžba mora proći pet stanja, i kupac mora vidjeti istoriju. Sistemski test sve to provjerava jednim tokom, kao pravi korisnik.

Fajl primjeri/deliveryapp/tests_sistem/test_sistem.py za to pokrene pravi server (uvicorn api:app) kao poseban proces i šalje mu HTTP zahtjeve preko mreže, običnom bibliotekom urllib, kao što bi to radio bilo koji klijent:

@pytest.fixture(scope="module")
def server():
    """Pokrene server, sačeka da odgovori, a na kraju ga ugasi."""
    proces = subprocess.Popen([sys.executable, "-m", "uvicorn", "api:app", "--port", "8765"], cwd=FOLDER)
    ...                                      # čeka dok server ne odgovori
    yield ADRESA
    proces.terminate()


def test_put_narudzbe_od_pocetka_do_kraja(server):
    # 1. kupac pogleda jelovnik Picerije
    status, jela = zahtjev("GET", "/v1/jela?restoran=R-1")
    assert status == 200 and len(jela) == 3

    # 2. naruči N-1001: 21,50 € + 3,00 € dostave
    status, n = zahtjev("POST", "/v1/narudzbe", N1001)
    assert status == 201
    assert (n["stanje"], n["ukupno"]) == ("NOVA", 2450)
    broj = n["broj"]

    # 3. restoran, kuhinja i dostavljač vode narudžbu do kupca
    for stanje in ["PRIHVAĆENA", "U_PRIPREMI", "NA_PUTU", "ISPORUČENA"]:
        status, _ = zahtjev("PATCH", f"/v1/narudzbe/{broj}/stanje", {"stanje": stanje})
        assert status == 200, stanje

    # 4. kupac vidi cijelu istoriju
    status, n = zahtjev("GET", f"/v1/narudzbe/{broj}")
    assert n["istorija"] == ["NOVA", "PRIHVAĆENA", "U_PRIPREMI", "NA_PUTU", "ISPORUČENA"]

Fixture ima scope="module": server se pokrene jednom za sve testove u fajlu, a ne za svaki test posebno, jer pokretanje traje.

pytest -q tests_sistem
...                                                                      [100%]
3 passed in 0.92s

Nad prvom verzijom (DeliveryApp=v0) padaju put narudžbe (ukupno 24,00 € umjesto 24,50 €, D-02) i otkazivanje u pripremi (200 umjesto 409, D-04):

FAILED tests_sistem/test_sistem.py::test_put_narudzbe_od_pocetka_do_kraja - A...
FAILED tests_sistem/test_sistem.py::test_otkazivanje_u_pripremi_odbijeno - as...
2 failed, 1 passed in 0.70s

Isti nalazi kao kroz TestClient u poglavlju 9? Da, i to je dobro: sistemski test ne treba da nalazi nove greške u pravilima, njih su našli niži nivoi. Njegov posao je da nađe ono što niži nivoi ne vide: server se ne pokreće, port je zauzet, JSON se pogrešno kodira preko mreže, slova Ć i Ž se pokvare, nedostaje biblioteka na serveru. Zato je sistemskih testova malo (vrh piramide iz poglavlja 4), ali moraju postojati.

Na času. Šta bi sistemski test otkrio, a TestClient test ne bi? Navedite tri primjera. – Na primjer: server ne može da se pokrene jer nedostaje uvicorn; port 8000 je zauzet drugim programom; pogrešno podešen zapis znakova pa „PRIHVAĆENA” stiže kao „PRIHVAĆENA”; zaštitni zid (firewall) blokira zahtjeve.

Ukratko.

  • Sistemski test prolazi cijeli put korisnika jednim tokom: jelovnik, narudžba, sva stanja, istorija.
  • Test u DeliveryApp pokreće pravi server kao poseban proces i šalje zahtjeve preko mreže.
  • Fixture s scope="module" pokreće server jednom za sve testove u fajlu.
  • Sistemskih testova je malo, a hvataju ono što niži nivoi ne vide: pokretanje, mrežu, kodiranje, okruženje.

Provjeri se.

  1. Po čemu se sistemsko testiranje razlikuje od integracionog? – Testira cijeli sistem u okruženju sličnom produkciji, s pravim servisima, i provjerava i nefunkcionalne zahtjeve.
  2. Smiju li se u testnom okruženju koristiti stvarni podaci kupaca? – Ne, samo izmišljeni ili anonimizovani.
  3. Zašto fixture servera ima scope="module"? – Da se server pokrene jednom za sve testove u fajlu, jer pokretanje traje.

Dio 2 – Nefunkcionalno testiranje

10.4 Performanse i Locust

Funkcionalni test pita: „Da li je cijena N-1001 24,50 €?” Test performansi pita: „Da li je cijena N-1001 24,50 € i kad u isto vrijeme pita još 199 ljudi, i koliko dugo svako čeka?”

Za DeliveryApp imamo nefunkcionalni zahtjev:

NZ-1. Pri 200 istovremenih korisnika, 95 % zahtjeva mora završiti za najviše 2 sekunde, bez grešaka.

Primijetite oblik zahtjeva: ima broj korisnika, procenat i granicu vremena. Zahtjev „stranica mora biti brza” ne može se testirati; NZ-1 može.

Postoje četiri glavne vrste testova performansi (slika 10.2):

Slika 10.2 – Četiri vrste testova performansi: broj korisnika kroz vrijeme
Vrsta Pitanje DeliveryApp
opterećenje (load) radi li sistem pri očekivanom broju korisnika? 200 korisnika, NZ-1
stres (stress) gdje je granica i šta se desi kad je pređemo? povećavati dok ne padne
skok (spike) podnosi li nagli skok? petak u 19 h, reklama na TV-u
izdržljivost (soak, endurance) radi li satima i danima bez kvarenja? 150 korisnika 8 sati; traži „curenje” memorije

Za mjerenje treba alat koji glumi stotine korisnika. Locust je takav alat za Python: korisnik se opisuje kao klasa, a Locust pokrene koliko god kopija treba. Fajl primjeri/deliveryapp/locustfile.py:

class Kupac(HttpUser):
    """Jedan virtuelni kupac: gleda jelovnik, provjerava cijenu i ponekad naruči."""

    wait_time = between(1, 3)        # kupac „razmišlja” 1 do 3 sekunde između klikova

    @task(5)
    def jelovnik(self):
        self.client.get("/v1/jela", params={"restoran": random.choice(["R-1", "R-2", "R-3"])})

    @task(3)
    def cijena(self):
        with self.client.post("/v1/cijena", json=N1001, catch_response=True) as r:
            if r.status_code != 200 or r.json().get("ukupno") != 2450:
                r.failure(f"pogrešna cijena: {r.status_code} {r.text[:80]}")

    @task(1)
    def narudzba(self):
        self.client.post("/v1/narudzbe", json=N1001)

Brojevi u @task(5), @task(3), @task(1) su težine: od 9 radnji, prosječan kupac 5 puta pogleda jelovnik, 3 puta provjeri cijenu i jednom naruči, kao pravi kupci. Primijetite i da test performansi provjerava rezultat: cijena mora biti 24,50 €. Brz odgovor s pogrešnim iznosom nije uspjeh.

Pokretanje: u jednom terminalu server, u drugom Locust bez grafičkog prikaza (200 korisnika, 20 novih u sekundi, 30 sekundi):

uvicorn api:app --port 8000
locust -f locustfile.py --headless -u 200 -r 20 -t 30s --host http://127.0.0.1:8000

Skraćen rezultat jednog mjerenja na laptopu:

Type     Name                    # reqs      # fails |    Avg     Min     Max    Med |   req/s
POST     /v1/cijena                 864     0(0.00%) |      4       0      37      3 |   29.44
POST     /v1/narudzbe               285     0(0.00%) |      4       0      36      3 |    9.71
         Aggregated                2584     0(0.00%) |      4       0      43      3 |   88.05

Response time percentiles (approximated)
Type     Name                      50%    66%    75%    80%    90%    95%    98%    99%   100%
         Aggregated                  3      4      5      6      8     16     18     22     44

Slika 10.3 – Percentili vremena odziva: 95 % zahtjeva završilo je za najviše 16 ms

Kako čitati:

  • 2584 zahtjeva, 0 grešaka, oko 88 zahtjeva u sekundi (propusnost);
  • medijan 3 ms: pola zahtjeva završi za 3 ms ili brže;
  • 95. percentil 16 ms: to je broj iz NZ-1, i daleko je ispod 2000 ms; zahtjev je ispunjen;
  • najsporiji zahtjev 44 ms.

Zašto percentil, a ne prosjek? Prosjek sakriva loše slučajeve. Ako 95 kupaca čeka 0,1 s, a 5 čeka 30 s, prosjek je oko 1,6 s i „izgleda dobro”, a pet ljudi je zatvorilo aplikaciju. 95. percentil bi pokazao 30 s.

Česta zabluda. „Naš API odgovara za 16 ms, dakle DeliveryApp podnosi 200 korisnika.” Ovaj API čuva podatke u memoriji, nema bazu, nema pravi servis mapa ni banku, i radi na istom laptopu kao Locust. U stvarnom sistemu svaki od tih dijelova dodaje vrijeme, i baš oni obično prvi „pucaju”. Rezultat testa performansi važi samo za okruženje u kojem je izmjeren. Zato se ozbiljni testovi performansi rade u predprodukciji, na kopiji pravog sistema.

Ukratko.

  • Test performansi provjerava brzinu i ispravnost pri mnogo istovremenih korisnika, prema mjerljivom zahtjevu kao NZ-1.
  • Vrste su opterećenje, stres, skok i izdržljivost.
  • Locust opisuje korisnika kao klasu s težinskim radnjama i pokreće stotine kopija.
  • Percentil je bolji od prosjeka jer pokazuje koliko čekaju najsporiji korisnici; za DeliveryApp je 95. percentil 16 ms.
  • Rezultat važi samo za okruženje u kojem je izmjeren.

10.5 Sigurnost

Sigurnosno testiranje pita: šta može uraditi neko ko ne bi smio? Za web aplikacije postoji poznat spisak, OWASP Top 10, deset najčešćih kategorija propusta, koji se povremeno ažurira. Nekoliko kategorija na primjeru DeliveryApp:

Kategorija Pitanje za DeliveryApp Stanje u našem API-ju
pokvarena kontrola pristupa može li kupac K-02 promijeniti narudžbu kupca K-01? Može li kupac sam proglasiti narudžbu isporučenom? da, može: API nema prijavu ni provjeru ko šalje zahtjev
ubacivanje (injection) šta ako polje kupac sadrži SQL ili HTML kod? nema baze ni HTML-a, ali polje se ne provjerava
nesiguran dizajn vjeruje li server cijeni koju šalje klijent? ne: server sam računa cijenu (dobro)
pogrešna podešavanja da li greška otkriva detalje servera? FastAPI ne vraća trag greške korisniku (dobro)
ranjive komponente koriste li se biblioteke s poznatim ranjivostima? treba redovno provjeravati (pip-audit)

Prvi red je pravi nalaz: u našem API-ju svako može poslati PATCH /v1/narudzbe/N-1001/stanje sa "ISPORUČENA". Za učionicu je to u redu, ali za pravi sistem je to ozbiljan propust: treba prijava (ko si?) i ovlaštenje (smiješ li to?). Kupac smije samo otkazati, restoran prihvatiti i odbiti, dostavljač preuzeti i isporučiti.

Takav nalaz se zapisuje kao izvještaj o defektu (poglavlje 3), s ozbiljnošću „visoka” i koracima za ponavljanje. Sigurnosni test za njega bi izgledao ovako (kad prijava postoji): kupac K-02 pokuša promijeniti narudžbu kupca K-01, očekivano 403 Forbidden.

Iz stvarnog svijeta. Heartbleed (poglavlje 7) je bio „ranjiva komponenta” za svaku firmu koja je koristila OpenSSL: tuđa greška u biblioteci postala je njihova. Zato timovi danas automatski provjeravaju biblioteke koje koriste, alatima kao pip-audit za Python ili Dependabot na GitHubu.

Ukratko.

  • Sigurnosno testiranje pita šta može uraditi neko ko ne bi smio.
  • OWASP Top 10 je spisak najčešćih kategorija propusta u web aplikacijama.
  • API DeliveryApp ima pokvarenu kontrolu pristupa: svako može promijeniti stanje bilo koje narudžbe.
  • Server dobro radi što sam računa cijenu i ne otkriva detalje greške.

10.6 Upotrebljivost i pristupačnost

Sistem može biti tačan, brz i siguran, a da ga ljudi ne znaju koristiti. Upotrebljivost (usability) pita: može li prosječan kupac naručiti N-1001 bez pomoći, brzo i bez greške? Testira se tako što se stvarni ljudi posmatraju dok rade zadatak („naručite dvije Margarite i sok na adresu Lipa 12 s kodom za studente”), a mjeri se:

  • da li su uspjeli,
  • koliko je trajalo,
  • gdje su zastali ili pogriješili,
  • koliko su zadovoljni (kratka anketa).

Već pet korisnika obično pokaže najveće probleme. Primjer nalaza: korisnici ne vide polje za kod popusta jer je ispod dugmeta „Naruči”.

Pristupačnost (accessibility) pita: mogu li stranicu koristiti i ljudi sa slabijim vidom, slijepi (uz čitač ekrana), ljudi koji ne mogu koristiti miš? Pravila su u međunarodnom standardu WCAG. Primjeri: dugmad imaju tekstualni opis (u stranici DeliveryApp aria-label="Dodaj Pizza Margarita"), kontrast teksta je dovoljan, sve se može uraditi tastaturom. Pristupačnost djelimično provjeravaju automatski alati, a detaljnije je radimo u poglavlju 13.

Ukratko.

  • Upotrebljivost se testira posmatranjem stvarnih ljudi dok rade zadatak; već pet korisnika pokaže najveće probleme.
  • Mjeri se uspjeh, vrijeme, mjesta zastoja i zadovoljstvo.
  • Pristupačnost znači da stranicu mogu koristiti i ljudi s invaliditetom, po standardu WCAG.
  • Dio pristupačnosti provjeravaju automatski alati, što radimo u poglavlju 13.

Provjeri se.

  1. Zašto je 95. percentil bolji od prosjeka? – Prosjek sakriva najsporije korisnike, a percentil pokazuje koliko oni čekaju.
  2. Koja je razlika između testa opterećenja i testa stresa? – Opterećenje provjerava očekivani broj korisnika; stres povećava broj dok sistem ne padne, da se nađe granica.
  3. Koji sigurnosni propust ima API DeliveryApp? – Pokvarena kontrola pristupa: svako može promijeniti stanje bilo koje narudžbe.

Dio 3 – Prihvatno testiranje i BDD

10.7 Prihvatno testiranje

Sistem je prošao sve testove tima. Da li je gotov? Ne dok ga ne prihvati onaj za koga je napravljen. Prihvatno testiranje je posljednji nivo V-modela, naspram korisničkih zahtjeva, i ne pita „da li radi po specifikaciji”, nego „da li je ovo ono što nam treba”.

Vrste prihvatnog testiranja:

Vrsta Ko testira Primjer za DeliveryApp
korisničko (UAT) stvarni korisnici ili njihovi predstavnici vlasnici tri restorana probaju aplikaciju sedmicu dana
operativno administratori i podrška rezervne kopije, vraćanje poslije pada, nadzor servera
ugovorno naručilac prema ugovoru grad koji je naručio aplikaciju provjerava stavke ugovora
regulatorno prema zakonu ili standardu zaštita ličnih podataka, fiskalni računi
alfa korisnici, kod proizvođača studenti EUBD-a naručuju u učionici
beta korisnici, u stvarnom svijetu 100 kupaca koristi aplikaciju mjesec dana prije svih

Prihvatni testovi se ne pišu na kraju. Pišu se na početku, zajedno sa zahtjevima, kao kriteriji prihvatanja: konkretni, provjerljivi uslovi koje priča mora ispuniti. Korisnička priča za DeliveryApp:

Priča. Kao kupac, želim unaprijed znati koliko plaćam dostavu, da bih mogao odlučiti koliko naručujem.

Kriteriji prihvatanja.

  1. Za korpu od 30,00 € ili više dostava je 0,00 €.
  2. Za korpu od 29,99 € na 5 km dostava je 3,00 €.
  3. Kod STUDENT10 ne ukida besplatnu dostavu: 32,00 € s kodom košta 28,80 €.
  4. Cijena dostave se vidi prije dugmeta „Naruči”.

Prva tri kriterija su upravo testovi iz poglavlja 8. Četvrti je kriterij upotrebljivosti i provjerava se ručno ili testom web stranice (poglavlje 13). Kad kriteriji postoje prije koda, svi znaju šta znači „gotovo”.

Ukratko.

  • Prihvatno testiranje odlučuje da li korisnik ili naručilac prihvata sistem.
  • Postoje korisničko, operativno, ugovorno, regulatorno, alfa i beta prihvatno testiranje.
  • Kriteriji prihvatanja su konkretni, provjerljivi uslovi koji se pišu zajedno s korisničkom pričom.
  • Kad kriteriji postoje prije koda, svi znaju šta znači da je priča gotova.

10.8 BDD: primjeri koje razumiju svi

Kriteriji prihvatanja su napisani običnim jezikom. Testovi su napisani u Pythonu. Između njih je jaz: vlasnik restorana ne čita Python, a programer može pogrešno prevesti rečenicu u kod. BDD (Behavior-Driven Development, razvoj vođen ponašanjem) zatvara taj jaz: kriteriji se pišu kao scenariji u strogo propisanom obliku, koji je i dalje običan jezik, ali ga alat može izvršiti kao test.

Jezik scenarija zove se Gherkin. Ima nekoliko ključnih riječi, a postoji i na bosanskom:

Engleski Bosanski Značenje
Feature Karakteristika šta opisujemo (korisnička priča)
Scenario Scenario jedan primjer ponašanja
Given Dato početno stanje (pripremi)
When Kada radnja korisnika (izvrši)
Then Zatim očekivani ishod (provjeri)
And, But I, Ali nastavak prethodnog koraka
Scenario Outline, Examples Scenariju-obris, Primjeri jedan scenarij za tabelu primjera

Prepoznajete li obrazac? Dato – Kada – Zatim je isto što i pripremi – izvrši – provjeri iz poglavlja 5, samo napisano za ljude.

Fajl primjeri/deliveryapp/tests_bdd/features/dostava.feature:

# language: bs
Karakteristika: Cijena dostave i otkazivanje
  Kao kupac DeliveryApp
  želim unaprijed znati koliko plaćam dostavu
  da bih mogao odlučiti koliko naručujem

  Scenario: Narudžba od tačno 30,00 € ima besplatnu dostavu
    Dato korpa od 30,00 €
    I adresa udaljena 5 km
    Kada kupac zatraži cijenu
    Zatim dostava je 0,00 €
    I ukupno za plaćanje je 30,00 €

  Scenario: Studentski popust ne ukida besplatnu dostavu
    Dato korpa od 32,00 €
    I adresa udaljena 2,5 km
    I kod popusta "student10"
    Kada kupac zatraži cijenu
    Zatim dostava je 0,00 €
    I ukupno za plaćanje je 28,80 €

  Scenariju-obris: Dostava zavisi od iznosa i udaljenosti
    Dato korpa od <iznos> €
    I adresa udaljena <km> km
    Kada kupac zatraži cijenu
    Zatim dostava je <dostava> €

    Primjeri:
      | iznos | km  | dostava |
      | 10,00 | 3   | 2,00    |
      | 10,00 | 3,1 | 2,50    |
      | 21,50 | 4,2 | 3,00    |
      | 29,99 | 5   | 3,00    |
      | 30,00 | 5   | 0,00    |

Prvi red, # language: bs, kaže alatu da su ključne riječi na bosanskom. Ovaj fajl može pročitati, ispraviti i potpisati vlasnik restorana. Iznosi su napisani kako ih piše kupac, s decimalnim zarezom i znakom €.

Ukratko.

  • BDD piše kriterije prihvatanja kao scenarije koje razumiju svi, a alat ih izvršava kao testove.
  • Jezik scenarija je Gherkin, s ključnim riječima Karakteristika, Scenario, Dato, Kada, Zatim, I.
  • Dato – Kada – Zatim je isti obrazac kao pripremi – izvrši – provjeri.
  • Scenariju-obris s tabelom Primjeri pravi jedan scenarij za svaki red tabele.

10.9 Koraci u Pythonu i pytest-bdd

Da bi scenarij postao test, svaki korak treba povezati s malom Python funkcijom. To radi biblioteka pytest-bdd (pip install pytest-bdd). Fajl tests_bdd/test_dostava_bdd.py:

from pytest_bdd import given, parsers, scenarios, then, when

from deliveryapp import Narudzba, cijena_dostave, popust

scenarios("features/dostava.feature")


def centi(tekst):
    """'30,00' -> 3000 (iznos kako ga piše kupac -> centi)."""
    eura, _, c = tekst.strip().partition(",")
    return int(eura) * 100 + int(c or 0)


@given(parsers.parse("korpa od {iznos} €"))
def korpa(narudzba_podaci, iznos):
    narudzba_podaci["iznos"] = centi(iznos)


@when("kupac zatraži cijenu")
def zatrazi_cijenu(narudzba_podaci):
    d = narudzba_podaci
    d["dostava"] = cijena_dostave(d["km"], d["iznos"])
    d["ukupno"] = d["iznos"] - popust(d["iznos"], d["kod"]) + d["dostava"]


@then(parsers.parse("dostava je {iznos} €"))
def provjeri_dostavu(narudzba_podaci, iznos):
    assert narudzba_podaci["dostava"] == centi(iznos)

Kako radi:

  • scenarios(...) pročita fajl i od svakog scenarija napravi pytest test;
  • @given(parsers.parse("korpa od {iznos} €")) kaže: korak koji počinje sa „Dato korpa od …” izvršava ova funkcija, a ono što piše na mjestu {iznos} stiže kao parametar;
  • funkcija centi pretvara iznos kako ga piše kupac (30,00) u cente (3000), jer pravila rade u centima (poglavlje 5).
pytest -v tests_bdd
tests_bdd/test_dostava_bdd.py::test_studentski_popust_ne_ukida_besplatnu_dostavu PASSED
tests_bdd/test_dostava_bdd.py::test_dostava_zavisi_od_iznosa_i_udaljenosti[10,00-3,1-2,50] PASSED
...
8 passed in 0.06s

Četiri scenarija daju 8 testova (obris s pet primjera daje pet). Nad prvom verzijom pada 6 od 8: tačno 30,00 € (D-01), kod malim slovima (D-03), 3,1 km i 4,2 km (D-02), 30,00 € u tabeli (D-01) i otkazivanje u pripremi (D-04). Dva prolaze: 3 km i 29,99 €, jer tu prva verzija slučajno računa tačno.

Najveća vrijednost BDD-a nije u alatu, nego u razgovoru. Scenariji nastaju na sastanku na kojem sjede vlasnik proizvoda, programer i tester (tzv. „tri amigosa”) i zajedno smišljaju primjere: „A šta ako je korpa tačno 30,00 €? A ako s popustom padne ispod 30,00 €?” Svako pitanje koje se tu postavi je greška koja se neće napisati.

Česta zabluda. „BDD znači pisati sve testove u Gherkinu.” Ne. Gherkin je za ponašanje važno korisniku, desetine scenarija, a ne stotine. Granice od 0,01 km, 49 prelaza stanja i 200 nizova modela ostaju u običnim pytest testovima, gdje su kraći i brži. Ako vlasnik proizvoda ne bi htio pročitati scenarij, on ne pripada u .feature fajl.

Ukratko.

  • Biblioteka pytest-bdd povezuje svaki korak scenarija s Python funkcijom i pravi pytest testove.
  • Parametri koraka, kao {iznos}, prenose vrijednosti iz rečenice u funkciju.
  • Četiri scenarija DeliveryApp daju osam testova, a nad prvom verzijom šest pada.
  • Najveća vrijednost BDD-a je zajednički razgovor o primjerima prije pisanja koda.
  • U Gherkin idu scenariji važni korisniku, a detaljni testovi ostaju u pytestu.

Provjeri se.

  1. Šta je kriterij prihvatanja? – Konkretan, provjerljiv uslov koji korisnička priča mora ispuniti da bi bila gotova.
  2. Kako se na bosanskom pišu Given, When, Then u Gherkinu? – Dato, Kada, Zatim.
  3. Koliko testova pravi Scenariju-obris s tabelom od pet redova? – Pet, jedan za svaki red.

Praktična nastava – Sistem, opterećenje i prihvatanje

Cilj je pokrenuti sistemski test s pravim serverom, izmjeriti performanse DeliveryApp Locustom, prijaviti sigurnosni nalaz i napisati BDD scenarije za novu korisničku priču. Radi se u parovima. Prije početka: pip install fastapi uvicorn locust pytest-bdd.

Korak 1 – Sistemski test (30 minuta)

  1. U primjeri/deliveryapp pokrenite pytest -v tests_sistem nad ispravnom i nad prvom verzijom. Zapišite rezultate.
  2. Dodajte sistemski test: narudžba N-1002 (4 × Ćevapi, 2,5 km) prolazi put NOVA → PRIHVAĆENA → OTKAZANA. Očekivani iznos i stanja zapišite prije pokretanja.
  3. Pokušajte pokrenuti test dok je port 8765 zauzet (pokrenite prije toga uvicorn api:app --port 8765 u drugom terminalu). Šta se desi? Kakav je to nalaz: greška u kodu ili u okruženju?

Korak 2 – Locust (45 minuta)

  1. Pokrenite server (uvicorn api:app --port 8000) i Locust s grafičkim prikazom (locust -f locustfile.py --host http://127.0.0.1:8000), otvorite http://localhost:8089, unesite 200 korisnika i 20 u sekundi.
  2. Poslije jedne minute zapišite: broj zahtjeva, greške, propusnost, medijan, 95. percentil. Da li je NZ-1 ispunjen?
  3. Stres: povećavajte broj korisnika (500, 1000, 2000…) dok vrijeme odziva ne poraste ili se ne pojave greške. Zapišite gdje je granica na vašem računaru i ko je „pukao” prvi: server ili sam Locust (pogledajte opterećenje procesora u Task Manageru).
  4. Dodajte u locustfile.py radnju koja provjerava kod STUDENT10 (ukupno mora biti 2235) i pokrenite ponovo.

Korak 3 – Sigurnost (30 minuta)

  1. Preko stranice /docs napravite narudžbu, pa je, bez ikakve prijave, proglasite isporučenom. Napišite izvještaj o defektu (šablon iz poglavlja 3) s ozbiljnošću, prioritetom i koracima.
  2. Pošaljite narudžbu u kojoj je kupac vrlo dug tekst (10 000 znakova) i tekst <script>alert(1)</script>. Šta API vraća? Šta bi se moglo desiti kad web stranica prikaže to ime? Zapišite kao pitanje za tim.

Korak 4 – BDD (60 minuta)

  1. Pokrenite pytest -v tests_bdd nad obje verzije i uporedite padove s listom D-01 … D-05 iz poglavlja 8.
  2. U trojkama (vlasnik proizvoda, programer, tester) napišite fajl features/napojnica.feature za priču: „Kao kupac, želim ostaviti napojnicu dostavljaču, da bih ga nagradio za brzu dostavu.” Kriteriji iz PP-8. Bar jedan Scenario i jedan Scenariju-obris s pet primjera (među njima granice 66,00 € i 66,01 € za 15 %, vježba 2 poglavlja 8).
  3. Napišite korake u test_napojnica_bdd.py koji koriste funkciju napojnica iz poglavlja 5 i pokrenite ih.
  4. Dajte .feature fajl kolegi iz druge grupe koji nije učestvovao. Može li ga razumjeti bez objašnjenja? Šta je bilo nejasno?

Rezultat koji se predaje: fajl rezultati-10.md s rezultatima sistemskog testa i opisom problema sa zauzetim portom; tabela mjerenja Locusta za 200 korisnika i granica iz testa stresa; izvještaj o sigurnosnom defektu; features/napojnica.feature i test_napojnica_bdd.py s rezultatom pokretanja; komentar kolege o razumljivosti scenarija.

Vježbe

Vježba za studenta 1 – vrsta testa.

Koja je vrsta testa: a) 150 korisnika 12 sati, prati se memorija servera; b) za 10 sekundi broj korisnika skoči sa 50 na 2000; c) korisnici se dodaju dok vrijeme odziva ne pređe 5 s; d) 200 korisnika, provjerava se NZ-1; e) vlasnik restorana R-2 sedmicu dana prima narudžbe preko aplikacije.

Rješenje. a) izdržljivost; b) skok; c) stres; d) opterećenje; e) korisničko prihvatno testiranje (UAT), ili beta, ako su uključeni i pravi kupci.

Vježba za studenta 2 – percentili.

Deset zahtjeva trajalo je (u ms): 20, 22, 25, 25, 30, 31, 35, 40, 45, 3000. Koliki je prosjek, a koliki medijan? Šta bolje opisuje iskustvo korisnika?

Rješenje. Zbir je 3273 ms, prosjek 327,3 ms. Medijan (prosjek 5. i 6. vrijednosti) je 30,5 ms. Medijan opisuje tipičnog korisnika (oko 30 ms), a prosjek je „naduvan” jednim zahtjevom od 3 s. Ali ni medijan ne pokazuje tog jednog korisnika koji je čekao 3 s; zato se gledaju i visoki percentili (95., 99.) i maksimum.

Vježba za studenta 3 – nefunkcionalni zahtjev.

Prepravite zahtjev „Aplikacija mora biti brza i podnositi mnogo korisnika” tako da se može testirati.

Rješenje. Npr.: „Pri 500 istovremenih korisnika, od kojih svaki napravi radnju svakih 1 do 3 sekunde, 95 % zahtjeva za cijenu i narudžbu završava za najviše 1 sekundu, 99 % za najviše 3 sekunde, a udio grešaka je manji od 0,1 %.” Zahtjev ima broj korisnika, ponašanje korisnika, percentile, granice vremena i dozvoljene greške.

Vježba za studenta 4 – Gherkin.

Napišite scenarij na bosanskom za pravilo PP-1 (najmanja narudžba 5,00 €), s granicom.

Rješenje.

# language: bs
Karakteristika: Najmanja narudžba
  Scenariju-obris: Narudžba ispod 5,00 € se odbija
    Dato korpa od <iznos> €
    I adresa udaljena 2 km
    Kada kupac zatraži cijenu
    Zatim narudžba je <ishod>

    Primjeri:
      | iznos | ishod      |
      | 4,99  | odbijena   |
      | 5,00  | prihvaćena |

Za korak „narudžba je …” treba nova funkcija koraka koja hvata ValueError (kao korak za otkazivanje u test_dostava_bdd.py).

Vježba za studenta 5 – okruženje.

Tim je za testno okruženje kopirao produkcijsku bazu DeliveryApp sa svim kupcima, adresama i brojevima telefona, „da testovi budu realni”. Šta nije u redu i kako to uraditi ispravno?

Rješenje. Stvarni lični podaci kupaca ne smiju biti u testnom okruženju bez zakonskog osnova i zaštite (GDPR, zakon o zaštiti ličnih podataka u BiH): više ljudi ima pristup, zaštita je slabija, a podaci mogu procuriti. Ispravno: izmišljeni podaci (K-01 … K-05, adrese kao „Lipa 12”) ili anonimizovana kopija u kojoj su imena, adrese i telefoni zamijenjeni, a zadržani su samo struktura i količina podataka potrebni za test performansi.

Sažetak u pet rečenica

  1. Healthcare.gov je 2013. pao prvog dana jer cijeli sistem nije na vrijeme testiran pod pravim opterećenjem, a test koji je pokazao pad s nekoliko stotina korisnika nije zaustavio otvaranje.
  2. Sistemsko testiranje provjerava sklopljen sistem u okruženju sličnom produkciji, a sistemski test DeliveryApp pokreće pravi server i prolazi cijeli put narudžbe preko mreže.
  3. Nefunkcionalno testiranje mjeri performanse (opterećenje, stres, skok, izdržljivost), sigurnost i upotrebljivost, a Locust je pokazao da 95 % zahtjeva DeliveryApp završi za 16 ms pri 200 korisnika, uz napomenu da rezultat važi samo za to okruženje.
  4. Sigurnosni pregled po OWASP Top 10 otkrio je pravi propust u API-ju DeliveryApp: svako može promijeniti stanje bilo koje narudžbe.
  5. Prihvatno testiranje odlučuje da li korisnik prihvata sistem, a BDD piše kriterije prihvatanja kao Gherkin scenarije na bosanskom (Dato, Kada, Zatim), koje razumiju svi i koje pytest-bdd izvršava kao testove.

Pitanja za ponavljanje

  1. Zašto je Healthcare.gov pao i šta to govori o sistemskom testiranju?
  2. Šta je sistemsko testiranje i po čemu se razlikuje od integracionog?
  3. Koja okruženja postoje i zašto testno okruženje mora ličiti na produkciju?
  4. Zašto stvarni podaci kupaca ne smiju u testno okruženje?
  5. Šta radi sistemski test DeliveryApp i šta može otkriti, a TestClient ne može?
  6. Kako izgleda dobro napisan nefunkcionalni zahtjev? Navedite NZ-1.
  7. Objasnite četiri vrste testova performansi.
  8. Kako se u Locustu opisuje korisnik? Šta znače težine u @task?
  9. Šta je 95. percentil i zašto je bolji od prosjeka?
  10. Zašto rezultat testa performansi API-ja DeliveryApp ne znači da će pravi sistem podnijeti 200 korisnika?
  11. Šta je OWASP Top 10? Koji propust ima API DeliveryApp?
  12. Kako se testira upotrebljivost, a šta je pristupačnost?
  13. Navedite vrste prihvatnog testiranja. Šta su kriteriji prihvatanja?
  14. Šta je BDD? Objasnite Gherkin scenarij i kako ga pytest-bdd pretvara u test.

Odgovori na pitanja za ponavljanje.

  1. Cijeli sistem nije na vrijeme testiran pod pravim opterećenjem: dijelovi su testirani odvojeno, za testiranje cjeline ostalo je oko dvije sedmice, test nekoliko dana prije otvaranja pokazao je pad s nekoliko stotina korisnika, a došlo ih je oko 250 000. Sistemsko testiranje i testiranje performansi ne smiju se ostaviti za kraj, a njihovi rezultati moraju uticati na odluku o puštanju.
  2. Sistemsko testiranje provjerava cijeli sklopljen sistem u okruženju sličnom produkciji, s pravim servisima, i funkcionalne i nefunkcionalne zahtjeve; obično ga radi nezavisan tim. Integraciono provjerava saradnju dijelova, često s dvojnicima, na računaru programera.
  3. Razvojno, testno, predprodukcijsko i produkcijsko. Testno mora ličiti na produkciju (verzije, baza, podešavanja, količina podataka), jer inače test prolazi, a sistem u produkciji ne radi.
  4. Jer to zabranjuju zakoni o zaštiti ličnih podataka (GDPR, zakon u BiH), testno okruženje je slabije zaštićeno i više ljudi mu ima pristup. Koriste se izmišljeni ili anonimizovani podaci; isto važi za AI alate.
  5. Pokrene pravi server kao poseban proces i preko mreže prolazi jelovnik, narudžbu N-1001, četiri promjene stanja i istoriju. Može otkriti probleme pokretanja servera, zauzet port, pogrešno kodiranje znakova preko mreže, nedostajuće biblioteke i zaštitni zid.
  6. Ima broj korisnika, procenat zahtjeva i granicu vremena, pa je mjerljiv. NZ-1: pri 200 istovremenih korisnika 95 % zahtjeva završava za najviše 2 sekunde, bez grešaka.
  7. Opterećenje: očekivani broj korisnika. Stres: broj raste dok sistem ne padne, da se nađe granica. Skok: nagli porast korisnika. Izdržljivost: normalno opterećenje satima i danima, da se nađe npr. curenje memorije.
  8. Kao klasa koja nasljeđuje HttpUser, s vremenom čekanja između radnji (wait_time) i metodama označenim sa @task. Težine kažu koliko se često radnja bira: @task(5) pet puta češće od @task(1).
    1. percentil je vrijeme za koje završi 95 % zahtjeva. Bolji je od prosjeka jer prosjek sakriva najsporije korisnike, a percentil pokazuje koliko oni čekaju.
  9. Jer API čuva podatke u memoriji, nema bazu ni prave spoljne servise i radi na istom laptopu kao Locust; rezultat važi samo za okruženje u kojem je izmjeren, a pravi test se radi u predprodukciji.
  10. OWASP Top 10 je spisak deset najčešćih kategorija sigurnosnih propusta u web aplikacijama. API DeliveryApp ima pokvarenu kontrolu pristupa: bez prijave svako može promijeniti stanje bilo koje narudžbe, npr. proglasiti je isporučenom.
  11. Upotrebljivost se testira posmatranjem stvarnih korisnika dok rade zadatak, mjerenjem uspjeha, vremena, zastoja i zadovoljstva. Pristupačnost znači da stranicu mogu koristiti i ljudi s invaliditetom (čitač ekrana, tastatura, kontrast), po standardu WCAG.
  12. Korisničko (UAT), operativno, ugovorno, regulatorno, alfa i beta. Kriteriji prihvatanja su konkretni, provjerljivi uslovi koje korisnička priča mora ispuniti, pisani zajedno s pričom, prije koda.
  13. BDD piše zahtjeve kao primjere ponašanja razumljive svima, koji postaju automatski testovi. Gherkin scenarij ima korake Dato (početno stanje), Kada (radnja) i Zatim (očekivani ishod). pytest-bdd funkcijom scenarios od svakog scenarija pravi pytest test, a svaki korak povezuje s Python funkcijom označenom sa @given, @when ili @then.