Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Testiranje pravilnosti kode, uporabniški vmesnik

Fakulteta za strojništvo, Univerza v Ljubljani

Testiranje pravilnosti kode

Kadar razvijamo bolj obsežno kodo in pri tem sodeluje več oseb, se pojavi potreba po avtomatskem testiranju pravilnosti kode. To pomeni, da poleg kode, ki ima določen rezultat, definiramo tudi testirne funkcije; slednje preverjajo ali prve vračajo pričakovani rezultat.

Preprosti primer je:

## vsebina datoteke test_prvi.py (mapa moduli)
def kvadrat(x):
    return x**2 + 1 # očitna napaka glede na ime funkcije

def test_kvadrat():
    assert kvadrat(2) == 4

Če poženemo funkcijo test_kvadrat() se testira pravilnost kvadrat(2) == 4; v primeru, da je rezultat True se ne zgodi nič, če pa je rezultat False se sproži AssertionError() (glejte dokumentacijo za ukaz assert). Ker funkcija kvadrat() vrne x**2+1, bo testiranje neuspešno.

Verjetno se sprašujete v čem je smisel takega testiranja pravilnosti. Ko koda postaja obsežna in na njej dela veliko oseb, postane nujno tudi prepletena. Tako se lahko zgodi, da razvijalec doda novo funkcionalnost in nehote poruši obstoječo. Če so vse funkcionalnosti testirane, bo testiranje zaznalo neustreznost predlagane spremembe.

Večina paketov ima tako dodano testiranje pravilnosti; za primer si lahko pogledate izvorno kodo paketa NumPy, ki se nahaja na githubu; če pogledate vsebino podmodula numpy.linalg za linearno algebro:

Numpy linalg

opazimo podmapo tests. Slednja je v celoti namenjena testiranju in vsebuje množico Python datotek, ki preverjajo ustreznost podmodula.

Obstaja več možnosti testiranja kode, pogosto se uporabljata sledeči:

Oba pristopa imata podobno funkcionalnost. Najpogosteje se uporablja pytest (npr. NumPy/SciPy), katerega osnove si bomo tukaj pogledali.

pytest

Modul pytest namestimo z ukazom pip.

Nekatere lastnosti:

  • vrne opis testa, ki ni bil uspešen,

  • samodejno iskanje testnih modulov in funkcij,

  • lahko poganja tudi unittest teste.

Če v ukazni vrstici poženete pytest, bo program sam poiskal trenutno mapo in vse podmape za datoteke oblike test_*.py ali *_test.py (napredno iskanje je navedeno v dokumentaciji).

Če v ukazni vrstici poženemo ukaz:

pytest

bomo dobili tako poročilo:

============================= test session starts =============================
platform win32 -- Python 3.6.2, pytest-3.2.1, py-1.4.34, pluggy-0.4.0
rootdir: c:\pypinm\moduli, inifile:
collected 4 items

test_orodja.py ...
test_prvi.py F

================================== FAILURES ===================================
________________________________ test_kvadrat _________________________________

    def test_kvadrat():
>       assert kvadrat(2) == 4
E       assert 5 == 4
E        +  where 5 = kvadrat(2)

test_prvi.py:5: AssertionError
===================== 1 failed, 3 passed in 0.65 seconds ======================

Iz poročila vidimo, da je program našel dve datoteki (test_orodja.py in test_prvi.py) in da je prišlo do napake pri eni funkciji (test_kvadrat), tri funkcije pa so uspešno prestale test.

Za osnovno uporabo pri numeričnih izračunih je treba še izpostaviti modul numpy.testing (dokumentacija), ki nudi podporo za testiranje numeričnih polj.

Izbrane funkcije so:

  • assert_allclose(dejansko, pričakovano[, rtol, ...]) (dokumentacija) preveri enakost do zahtevane natančnosti,

  • assert_array_less(x, y[, err_msg, verbose]) (dokumentacija) preveri, ali so elementi x manjši od elementov y,

  • assert_string_equal(dejansko, pričakovano) (dokumentacija) preveri enakost niza.

Zgled: test_orodja.py:

V datoteki test_orodja.py so pripravljene funkcije za testiranje modula orodja.py.

Tako so najprej pripravljeni podatki:

zacetna = np.asarray([[1, 2, 3],
                      [4, 5, 6],
                      [7, 8, 9]])
zamenjana_0_1_stolpca = np.asarray([[2, 1, 3],
                                    [5, 4, 6],
                                    [8, 7, 9]])

Potem je definirana funkcija, ki kliče funkcijo orodja.zamenjaj_stolpca() in rezultat primerja s pričakovanim zamenjana_0_1_stolpca:

def test_stolpec():
    a = zacetna.copy() # naredimo kopijo podatkov
    b = orodja.zamenjaj_stolpca(a, 0, 1) # b (in tudi a) imata zamenjane stolpce
    np.testing.assert_allclose(b, zamenjana_0_1_stolpca)

Če funkcija orodja.zamenjaj_stolpca() deluje pravilno, se klicanje pytest v ukazni vrstici uspešno zaključi. Za preostale teste poglejte datoteko test_orodja.py in dokumentacijo paketa pytest.

Grafični uporabniški vmesnik

Uporabniški vmesnik se uporablja za interakcijo uporabnika s programsko kodo. Uporabniški vmesniki so lahko preko ukazne vrstice ali grafični. Za preproste uporabniške vmesnike preko ukazne vrstice je najbolje, da uporabimo kar argparse, sicer pa se bomo tukaj predvsem osredotočili na grafični uporabniški vmesnik.

Grafični uporabniški vmesnik znotraj brskalnika

V zadnjem obdobju je opaziti veliko napora pri podpori razvoja grafičnih vmesnikov znotraj okolja Jupyter notebook ali Jupyter lab. Takšen pristop ima predvsem to prednost, da je nadgradnja v obliki spletne aplikacije relativno enostavna, glejte npr.: Dashboarding with Jupyter Notebooks, Voila and Widgets | SciPy 2019 | M. Breddels and M. Renou.

Grafični uporabniški vmesnik znotraj operacijskega sistema

v okviru tega poglavja si bomo pogledali klasične uporabniške vmesnike, ki jih poganjamo v okviru določenega operacijskega sistema. Tudi za programiranje grafičnega uporabniškega vmesnika obstaja veliko različnih načinov/modulov. Nekaj najpogosteje uporabljenih:

  • PySide6 je uradna Python vezava (Qt for Python) za Qt, ki predstavlja najbolj razširjeno platformo za uporabniške vmesnike; licenca LGPL dovoljuje tudi komercialno uporabo,

  • PyQt6 je starejša, neodvisna vezava za Qt s praktično enakim vmesnikom (razlikuje se le uvoz), vendar z licenco GPL (za komercialno uporabo je plačljiva),

  • Kivy za hiter razvoj modernih uporabniških vmesnikov (ni tako zrel kakor npr. Qt),

  • wxWidgets široko uporabljena prosta platforma za izdelavo uporabniških vmesnikov.

Med vsemi naštetimi si bomo podrobneje pogledali PySide6, ki je uradna in danes najbolj dozorela izbira. Namestimo ga z ukazom pip install PySide6. (Če spodnjo kodo želite uporabiti s PyQt6, zamenjajte le PySide6 v uvozih s PyQt6.)

Velja omeniti, da uporabniški vmesnik lahko:

Tukaj bomo uporabniški vmesnik kodirali.

Zgled

Najprej uvozimo paket za interakcijo in z grafičnimi objekti:

Potrebovali bomo tudi modul sys za poganjanje programa:

Uporabniški vmesnik tipično gradimo na razredu QtWidgets.QMainWindow (dokumentacija), ki ima spodaj prikazano strukturo:

Window layout

Bistveni grafični elementi, ki jih pri takem uporabniškem vmesniku uporabimo, so:

  • Menu Bar,

  • Toolbars,

  • Dock Widgets,

  • Central Widget,

  • Status bar.

Ni potrebno definirati vseh; spodaj si bomo pogledali primer, ko bomo definirali Status bar, Central Widget in Menu Bar. Ponavadi vse elemente definiramo pri inicializaciji instance razreda (metoda __init__).

Tukaj izpostavimo, da grafični vmesnik temelji na t. i. Widgetih, (za na primer gumb, tabelo, datum itd.). Prikaz nekaterih možnosti je prikazan v:

Zelo enostaven uporabniški vmesnik (z vrstičnimi komentarji kode) je prikazan spodaj.

Program potem poženemo znotraj stavka try:

Za naprednejši zgled glejte datoteki:

Nekaj komentarjev na uporabniski_vmesnik.py:

  1. Poglejte prepis dogodka mouseDoubleClickEvent in prepišite podedovan dogodek keyPressEvent, ki naj ob pritisku katerekoli tipke zapre program (če se nahajate v TextEdit polju, potem seveda pritisk tipke izpiše vrednost te tipke).

  2. Dodajte še kakšen Widget s seznama.

  3. Spremenite program, da se bo vedno izrisovala funkcija sinus, v vpisno polje function_text pa boste zapisali število diskretnih točk (sedaj je točk 100). Povežite polje z ustreznimi funkcijami.

  4. Uredite lovljenje napak pri zgornji spremembi.

Nekaj vprašanj za razmislek!

  1. V VisualStudioCode pripravite modul, ki bo imel dve funkciji:

    • za množenje matrike in vektorja,

    • za množenje dveh matrik.

  2. Za modul zgoraj pripravite skripto za testiranje (uporabite numpy.testing).

  3. V preprosti_uporabniski_vmesnik.py inicializijski metodi __init__ zakomentirajte vse klice na metode self.init ... razen na metodo: self.init_status_bar(). Poženite program v navadnem načinu. Nastavite break točko na self.setGeometry(...) in poženite program v debug načinu.

  4. Nadaljujte prejšnjo točko in poiščite bližnjico za pomikanje po vrsticah:

    • s preskokom vrstice,

    • z vstopom v vrstico.

    Vstopite v init_status_bar(self) in se ustavite pri vrstici self.setStatusBar(self.status_bar). Odprite konzolo (console) in prek ukazne vrstice spremenite vrednost self.status_bar.showMessage().

  5. Odkomentirajte prej (zgoraj) zakomentirane vrstice. Dodajte tretji gumb, ki naj program zapre.

  6. Dodajte še kakšen Widget s seznama.