Obsah
1. Testování nativních funkcí s využitím programovacího jazyka Python
2. Zdrojový kód funkce, která se má přeložit do dynamické knihovny
3. Zdrojový kód Pythonovského skriptu, který má dynamickou knihovnu využít
4. Překlad projektu a pokus o zavolání nativní funkce z dynamické knihovny
5. Malá úprava demonstračního příkladu – obalení nativní funkce Pythonovským kódem
6. Jednotkové testy naprogramované v Pythonu, které volají a testují nativní funkci
7. Spuštění jednotkových testů
8. Rozdíly mezi datovými typy jazyka C a Pythonu
9. Přetečení při operacích s celými čísly v C
10. Jednoduchý test volající funkce ze standardní knihovny jazyka C
11. Volání céčkových funkcí akceptujících odlišné typy argumentů
12. Práce s datovými strukturami
13. Porovnání komplexních čísel
14. Rozšíření knihovny pro práci s komplexními čísly
15. Nativní knihovny a behavior-driven testing založený na použití knihovny Behave
16. Projekt, v němž budeme testovat vlastnosti nativní funkce
17. Modifikace testovacího scénáře – určení typů parametrů předávaných nativní funkci
18. Repositář s demonstračními příklady
19. Předchozí články s tématem testování (nejenom) v Pythonu
1. Testování nativních funkcí s využitím programovacího jazyka Python
V dnešní části seriálu o testování aplikací s využitím programovacího jazyka Python se budeme zabývat možná poněkud méně známým tématem. Bude se jednat o testování nativních funkcí (či celých nativních knihoven nebo dokonce aplikací) s využitím Pythonu. Dnes použité demonstrační příklady, resp. přesněji řečeno jejich část, která má být překládána do nativního (strojového) kódu, budou psány v programovacím jazyku C. Propojíme tak céčko (což je stále nejpopulárnější nízkoúrovňový jazyk) s dnes pravděpodobně nejpopulárnějším vysokoúrovňovým programovacím jazykem současnosti. Ovšem stejně dobře lze použít i další překládané jazyky, zejména Rust a C++ – ovšem u obou těchto jazyků je nutné zakázat name mangling, tedy „dekorování“ jmen symbolů ve vytvářených objektových souborech i staticky či dynamicky linkovaných knihovnách.
Na straně Pythonu lze pro volání nativních funkcí použít například standardní modul ctypes nebo sice méně známý, ale o to povedenější modul pojmenovaný CFFI. Dnes použité příklady budou pro jednoduchost používat modul ctypes, který ve svém systému již pravděpodobně máte nainstalovaný. Modulem CFFI se budeme podrobněji zabývat v navazujícím článku.
Prezentovány budou dva typy testů. První typ je založen na nástroji pytest, což znamená, že samotné testy svoji strukturou připomínají jednotkové testy, stejné je jejich vyhodnocení apod. Pochopitelně – pokud tedy nepoužijeme ještě další nástroje – ovšem nezjistíme pokrytí nativního kódu těmito testy. V závěrečné části článku si ovšem ukážeme i behaviorální testy (BDD), které budou založeny na použití frameworku Behave a doménově specifického jazyka Gherkin. Obě zmíněné technologie již na Rootu byly představeny, takže závěrečná část článku je spíše shrnutím již známých věcí.
2. Zdrojový kód funkce, která se má přeložit do dynamické knihovny
Základem několika dále použitých demonstračních příkladů bude zdrojový kód vyvinutý v programovacím jazyku C, který je uložený do souboru pojmenovaného adder.c. Tento zdrojový kód bude obsahovat pouze jedinou funkci se jménem add a triviálním kódem:
extern int add(int x, int y) { return x+y; }
Jak je ze zdrojového kódu patrné, akceptuje tato funkce dva parametry typu int a vrací hodnotu, která je taktéž typu int. To může být poněkud problematické, protože standard programovacího jazyka C sice poměrně přesně a striktně určuje vlastnosti datového typu int, ale již neříká, jaká je konkrétní bitová délka (což je ostatně ve většině případů dobře a v případě potřeby lze datový typ specifikovat přesněji, minimálně ve standardu C99). Podle použité architektury se může jednat o šestnáctibitová čísla, většinou půjde o čísla 32bitová, ovšem na specializovaných architekturách nalezneme například i 24bitové hodnoty (typické je použití 24bitových akumulátorů u některých digitálně-signálových procesorů neboli DSP, příkladem může být slavná řada TMS320).
3. Zdrojový kód Pythonovského skriptu, který má dynamickou knihovnu využít
Nyní je nutné vytvořit druhou část projektu naprogramovanou pro změnu ve vysokoúrovňovém Pythonu. Tato část bude prozatím velmi jednoduchá, protože sestává z jediného souboru pojmenovaného call_from_python.py uloženého v adresáři s projektem (což je opět řešení zvolené pouze pro jeho jednoduchost, v reálném světě bude situace poněkud odlišná):
"""Základní použití balíčku ctypes.""" import ctypes def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def main(): """Otestování, jestli je možné zavolat nativní funkci.""" library = load_library("libadder.so") print(library) result = library.add(1, 2) print(f"1+2=", result, sep="") if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Povšimněte si, že se nejdříve ve funkci load_library pokusíme otevřít dynamickou knihovnu, k níž je uvedena plná cesta. To není obvyklý způsob, neboť v praxi je lepší se spolehnout na proměnnou prostředí LD_LIBRARY_PATH, což si ukážeme v dalších demonstračních projektech. Dále zavoláme nativní funkci add načtenou z této knihovny, získáme její výsledek a ten vypíšeme na standardní výstup.
Pokud se nyní pokusíme skript call_from_python.py spustit v interpretru Pythonu, dojde k chybě, protože načítaná knihovna nebude nalezena (existuje prozatím pouze ve zdrojové podobě, ale nebyla přeložena):
$ python3 call_from_python.py Traceback (most recent call last): File "call_from_python.py", line 21, in main() File "call_from_python.py", line 13, in main library = load_library("libadder.so") File "call_from_python.py", line 8, in load_library return ctypes.CDLL(library_name) File "/usr/lib64/python3.6/ctypes/__init__.py", line 343, in __init__ self._handle = _dlopen(self._name, mode) OSError: libadder.so: cannot open shared object file: No such file or directory
4. Překlad projektu a pokus o zavolání nativní funkce z dynamické knihovny
Soubor adder.c nyní přeložíme do objektového souboru, který bude pojmenován adder.o. Povšimněte si použití volby PIC, kterou se zapíná takzvaný Position Independent Code (tedy instrukcí nepoužívajících absolutní skoky) a která se používá při vytváření sdílených knihoven na některých architekturách (na x86_64 však v našem jednoduchém příkladu dostaneme stejný výsledek i bez použití této volby):
$ gcc -Wall -ansi -c -fPIC adder.c -o adder.o
Následně z tohoto objektového souboru vytvoříme sdílenou knihovnu (shared library) pojmenovanou libadder.so (přípona .so značí „shared object“):
$ gcc -shared -Wl,-soname,libadder.so -o libadder.so adder.o
Přesvědčíme se, že soubor libadder.so skutečně vznikl:
$ file libadder.so libadder.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=85fe85114c2f6582a8764ca22ba578df35381fec, not stripped
Popř. se můžeme podívat i na symboly, které jsou v tomto souboru definovány. Použijeme k tomu nástroj nm:
00000000000005ca T add 0000000000201018 B __bss_start 0000000000201018 b completed.6984 w __cxa_finalize@@GLIBC_2.2.5 00000000000004f0 t deregister_tm_clones 0000000000000580 t __do_global_dtors_aux 0000000000200e30 t __do_global_dtors_aux_fini_array_entry 0000000000200e38 d __dso_handle 0000000000200e40 d _DYNAMIC 0000000000201018 D _edata 0000000000201020 B _end 00000000000005e0 T _fini 00000000000005c0 t frame_dummy 0000000000200e28 t __frame_dummy_init_array_entry 0000000000000688 r __FRAME_END__ 0000000000201000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 00000000000005ec r __GNU_EH_FRAME_HDR 00000000000004b8 T _init w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 0000000000000530 t register_tm_clones 0000000000201018 d __TMC_END__
Můžeme se dokonce podívat, jakým způsobem se funkce add přeložila do strojového kódu. K tomuto účelu použijeme nástroj objdump a pro získání disassemblovaného textu zvolené funkce použijeme trik s awk, který z výpisu „vykousne“ pouze požadovanou funkci:
$ objdump -d -M intel libadder.so | awk -F"\n" -v RS="\n\n" '$1 ~ /add/' 00000000000005ca <add>: 5ca: 55 push rbp 5cb: 48 89 e5 mov rbp,rsp 5ce: 89 7d fc mov DWORD PTR [rbp-0x4],edi 5d1: 89 75 f8 mov DWORD PTR [rbp-0x8],esi 5d4: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] 5d7: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 5da: 01 d0 add eax,edx 5dc: 5d pop rbp 5dd: c3
$ LD_LIBRARY_PATH=. python3 call_from_python.py
Tento řádek je uložen ve skriptu run.sh, který je možné přímo použít:
$ ./run.sh <CDLL 'libadder.so', handle 55ac5f4a1bf0 at 0x7fde59f99080> 1+2=3
5. Malá úprava demonstračního příkladu – obalení nativní funkce Pythonovským kódem
Předchozí demonstrační příklad upravíme, a to takovým způsobem, že se nativní funkce add obalí Pythonovskou funkcí taktéž nazvanou add. Navíc do nového modulu adder.py přidáme kód pro načtení a inicializaci dynamicky sdílené knihovny:
"""Základní použití balíčku ctypes, modul pro import.""" import ctypes library = None def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def add(x, y): """Zavolání externí funkce.""" return library.add(x, y) def init(): global library library = load_library("libadder.so") def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() print(library) result = add(1, 2) print(f"1+2=", result, sep="") if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Nový modul adder se použije relativně snadno – pouze musíme zavolat inicializační funkci a následně je již možné používat funkci add:
"""Základní použití balíčku ctypes.""" from adder import init, add def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() result = add(1, 2) print(f"1+2=", result, sep="") if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Překlad céčkového kódu do nativní sdílené knihovny opět zajišťuje příslušný skript:
$ ./make_library.sh
Pokud k překladu a vytvoření knihovny došlo, dopadne spuštění příkladu podle očekávání – vypíše se výsledek součtu:
$ ./run.sh 1+2=3
V předchozím textu bylo uvedeno, že funkce add nijak nekontroluje typ parametrů – pouze je předá do nativního kódu. To například umožňuje, aby se tato funkce zavolala s dvojicí řetězců, přičemž výsledkem pochopitelně bude nesmysl (sečtou se hodnoty dvou ukazatelů nebo jejich částí):
def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() result = add("foo", "bar") print(f"'foo'+'bar'=", result, sep="")
Špatné chování si můžeme snadno otestovat:
$ ./run_bad.sh 'foo'+'bar'=155795872
Druhý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/tree/master/native_libs/project2.
6. Jednotkové testy naprogramované v Pythonu, které volají a testují nativní funkci
Naši nativní céčkovou funkci pojmenovanou add:
extern int add(int x, int y) { return x+y; }
která je obalena vlastním Pythonovským balíčkem:
"""Základní použití balíčku ctypes, modul pro import.""" import ctypes library = None def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def add(x, y): """Zavolání externí funkce.""" return library.add(x, y) def init(): global library library = load_library("libadder.so") def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() print(library) result = add(1, 2) print(f"1+2=", result, sep="") if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
pochopitelně můžeme relativně snadno otestovat přímo z Pythonu, a to pomocí nám již známého nástroje pytest. Kostra jednotkového testu může vypadat následovně:
"""Implementace jednotkových testů.""" import pytest from adder import init, add def test_add_basic(): """Otestování výpočtu součtu dvou celých čísel.""" init() result = add(1, 2) expected = 3 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
7. Spuštění jednotkových testů
Jednotkové testy (resp. přesněji řečeno jediný jednotkový test, který jsme prozatím vytvořili) lze spustit příkazem pytest. Nesmíme ovšem zapomenout na nastavení proměnné prostředí LD_LIBRARY_PATH. Pokud by tato proměnná prostředí nebyla nastavena (popř. byla nastavena nesprávně), nebylo by možné načíst nativní knihovnu a testy by ihned na začátku zhavarovaly. Spuštění jednotkových testů by tedy mohlo vypadat takto:
$ LD_LIBRARY_PATH=.;pytest -v
V případě, že nativní knihovna byla nalezena, měly by testy projít bez pádu:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project3 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 1 item test_add.py::test_add_basic PASSED [100%] ============================== 1 passed in 0.01s ===============================
8. Rozdíly mezi datovými typy jazyka C a Pythonu
Mezi programovacími jazyky C a Pythonem existuje velké množství rozdílů. Ty se týkají mj. i datových typů obou jazyků. A právě při volání nativních funkcí se někdy musí provádět konverze mezi různými datovými typy. Výjimek je jen několik a týkají se celých čísel (int) v 32bitovém či 64bitovém rozsahu, popř. polí bajtů, které jsou přenášeny jako řetězce (Unicode řetězce jako ukazatele na wchar_t). Při použití ostatních typů je nutné provádět konverze. K tomu slouží speciální datové typy knihovny ctypes, každý se svým konstruktorem:
# | Typ v knihovně ctypes | Typ v céčku | Typ v Pythonu |
---|---|---|---|
1 | c_bool | _Bool | bool |
2 | c_char | char | 1-character bytes object |
3 | c_wchar | wchar_t | 1-character string |
4 | c_byte | char | int |
5 | c_ubyte | unsigned char | int |
6 | c_short | short | int |
7 | c_ushort | unsigned short | int |
8 | c_int | int | int |
9 | c_uint | unsigned int | int |
10 | c_long | long | int |
11 | c_ulong | unsigned long | int |
12 | c_longlong | __int64 nebo long long | int |
13 | c_ulonglong | unsigned __int64 nebo unsigned long long | int |
14 | c_size_t | size_t | int |
15 | c_ssize_t | ssize_t nebo Py_ssize_t | int |
16 | c_float | float | float |
17 | c_double | double | float |
18 | c_longdouble | long double | float |
19 | c_char_p | char * (NUL terminated) | bytes object nebo None |
20 | c_wchar_p | wchar_t * (NUL terminated) | string nebo None |
21 | c_void_p | void * | int nebo None |
$ python3 Python 3.6.6 (default, Jul 19 2018, 16:29:00) [GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ctypes >>> ctypes.c_float(1/2) c_float(0.5)
9. Přetečení při operacích s celými čísly v C
V předchozí kapitole jsme si řekli, že typ int v Pythonu má prakticky neomezený rozsah. To však v žádném případě neplatí pro stejně pojmenovaný datový typ int z céčka, který může mít šířku typicky šestnáct bitů, 32bitů, 64bitů ale i 24 bitů (dříve podle použité platformy, dnes někdy i na základě tradice). Proto se nativní funkce add bude chovat rozdílně oproti operátoru + přímo v Pythonu. To si ostatně můžeme velmi snadno ověřit rozšířením jednotkových testů o sčítání hodnot, které se blíží 32bitovému limitu:
"""Implementace jednotkových testů.""" import pytest from adder import init, add def test_add_basic(): """Otestování výpočtu součtu dvou celých čísel.""" init() result = add(1, 2) expected = 3 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_add_large_ints(): """Otestování výpočtu součtu dvou větších celých čísel.""" init() result = add(2**31-2, 1) expected = 2**31-1 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_add_even_larger_ints(): """Otestování výpočtu součtu dvou velkých celých čísel.""" init() result = add(2**31-1, 1) expected = 2**31 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Při spuštění těchto jednotkových testů se může stát, že třetí test havaruje, a to právě kvůli přetečení výsledku v nativní funkci:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project4 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 3 items test_add.py::test_add_basic PASSED [ 33%] test_add.py::test_add_large_ints PASSED [ 66%] test_add.py::test_add_even_larger_ints FAILED [100%] =================================== FAILURES =================================== __________________________ test_add_even_larger_ints ___________________________ def test_add_even_larger_ints(): """Otestování výpočtu součtu dvou velkých celých čísel.""" init() result = add(2**31-1, 1) expected = 2**31 > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 2147483648, vráceno -2147483648 E assert -2147483648 == 2147483648 E +-2147483648 E -2147483648 test_add.py:29: AssertionError =========================== short test summary info ============================ FAILED test_add.py::test_add_even_larger_ints - AssertionError: Očekávaná hod... ========================= 1 failed, 2 passed in 0.03s ==========================
#include <stdio.h> int main(void) { printf("Bit size of int: %lu\n", 8*sizeof(int)); return 0; }
10. Jednoduchý test volající funkce ze standardní knihovny jazyka C
Kromě vlastních nativních knihoven pochopitelně můžeme z Pythonu volat i funkce ze standardní knihovny programovacího jazyka C. Na Linuxu jsou standardní funkce dostupné v knihovně libc.so.6 typicky umístěné v /usr/lib64/ nebo /usr/lib/. Všechny externí (volatelné) symboly (tedy především funkce) si ostatně můžeme zobrazit:
$ nm -g /usr/lib64/libc.so.6 0000000000042390 T a64l 0000000000035be0 T abort 00000000003b1d00 B __abort_msg 0000000000037880 T abs 00000000000f6070 T accept 00000000000f6a40 T accept4 00000000000e69f0 W access 00000000000ece00 T acct ... ... ... 0000000000127710 T xdr_vector 0000000000127880 T xdr_void 0000000000128240 T xdr_wrapstring 0000000000127380 T xencrypt 00000000000e6190 T __xmknod 00000000000e61f0 T __xmknodat 0000000000043c60 T __xpg_basename 0000000000034e50 W __xpg_sigpause 000000000008cb30 T __xpg_strerror_r 00000000001254c0 T xprt_register 00000000001255f0 T xprt_unregister 00000000000e60a0 T __xstat 00000000000e60a0 T __xstat64
Je tedy možné zavolat a otestovat základní funkcionalitu standardních funkcí time() a abs(). U první funkce ověříme, že vrácený čas (počet sekund od začátku Unixové éry) platí pro rok 2020 a další roky (vlastní výpočet je ovšem značně nepřesný – nepočítá například přestupné roky). U druhé funkce ověříme, jestli se skutečně počítá a vrací absolutní hodnota:
"""Implementace jednotkových testů.""" import pytest import ctypes libc = None def setup_module(module): """Zavoláno při inicializaci modulu s testem.""" global libc libc = ctypes.CDLL("libc.so.6") def test_time(): """Otestování nativní funkce time().""" t = libc.time(None) # velmi nepřesný odhad počtu sekund pro 2000-01-01 t2020 = (2020-1970)*365*24*60*60 assert t > t2020, "Neočekávaná hodnota {}".format(t) def test_abs(): """Otestování nativní funkce abs().""" x = libc.abs(-1) assert x == 1, "Neočekávaná hodnota {}".format(x) x = libc.abs(1) assert x == 1, "Neočekávaná hodnota {}".format(x)
Jednotkové testy nyní můžeme spustit bez nutnosti nastavování proměnné prostředí LD_LIBRARY_PATH:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project5 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 2 items test_stdlib.py::test_time PASSED [ 50%] test_stdlib.py::test_abs PASSED [100%] ============================== 2 passed in 0.01s ===============================
11. Volání céčkových funkcí akceptujících odlišné typy argumentů
Pokusme se nyní zavolat standardní funkci strlen() a předat jí řetězec v Pythonu:
def test_strlen(): """Otestování nativní funkce strlen().""" x = libc.strlen("Hello") assert x == 5, "Neočekávaná hodnota {}".format(x)
Tento jednotkový test zařadíme do demonstračního příkladu k předchozím dvěma testům:
"""Implementace jednotkových testů.""" import pytest import ctypes libc = None def setup_module(module): """Zavoláno při inicializaci modulu s testem.""" global libc libc = ctypes.CDLL("libc.so.6") def test_time(): """Otestování nativní funkce time().""" t = libc.time(None) # velmi nepřesný odhad počtu sekund pro 2000-01-01 t2020 = (2020-1970)*365*24*60*60 assert t > t2020, "Neočekávaná hodnota {}".format(t) def test_abs(): """Otestování nativní funkce abs().""" x = libc.abs(-1) assert x == 1, "Neočekávaná hodnota {}".format(x) x = libc.abs(1) assert x == 1, "Neočekávaná hodnota {}".format(x) def test_strlen(): """Otestování nativní funkce strlen().""" x = libc.strlen("Hello") assert x == 5, "Neočekávaná hodnota {}".format(x)
Vidíme, že test kontroluje, jestli je délka řetězce „Hello“ skutečně rovna pěti znakům. Ovšem při pokusu o spuštění testů dojde k chybě:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project6 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 3 items test_stdlib.py::test_time PASSED [ 33%] test_stdlib.py::test_abs PASSED [ 66%] test_stdlib.py::test_strlen FAILED [100%] =================================== FAILURES =================================== _________________________________ test_strlen __________________________________ def test_strlen(): """Otestování nativní funkce strlen().""" x = libc.strlen("Hello") > assert x == 5, "Neočekávaná hodnota {}".format(x) E AssertionError: Neočekávaná hodnota 1 E assert 1 == 5 E +1 E -5 test_stdlib.py:35: AssertionError =========================== short test summary info ============================ FAILED test_stdlib.py::test_strlen - AssertionError: Neočekávaná hodnota 1 ========================= 1 failed, 2 passed in 0.03s ==========================
Proč tomu tak je? V Pythonu 3 se používají Unicode řetězce, které jsou do céčkových funkcí přenášeny přes ukazatel na wchar_t. My však pracujeme s klasickými ASCIIZ řetězci céčka, musíme tedy použít pole bajtů (opět – platí pro Python 3):
"""Implementace jednotkových testů.""" import pytest import ctypes libc = None def setup_module(module): """Zavoláno při inicializaci modulu s testem.""" global libc libc = ctypes.CDLL("libc.so.6") def test_time(): """Otestování nativní funkce time().""" t = libc.time(None) # velmi nepřesný odhad počtu sekund pro 2000-01-01 t2020 = (2020-1970)*365*24*60*60 assert t > t2020, "Neočekávaná hodnota {}".format(t) def test_abs(): """Otestování nativní funkce abs().""" x = libc.abs(-1) assert x == 1, "Neočekávaná hodnota {}".format(x) x = libc.abs(1) assert x == 1, "Neočekávaná hodnota {}".format(x) def test_strlen(): """Otestování nativní funkce strlen().""" x = libc.strlen(b"Hello") assert x == 5, "Neočekávaná hodnota {}".format(x)
Nyní již jednotkové testy projdou bez problémů:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project7 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 3 items test_stdlib.py::test_time PASSED [ 33%] test_stdlib.py::test_abs PASSED [ 66%] test_stdlib.py::test_strlen PASSED [100%] ============================== 3 passed in 0.01s ===============================
12. Práce s datovými strukturami
Ve druhé části dnešního článku si ukážeme způsob předávání datových struktur mezi jazykem C a Pythonem. Připomeňme si, že v C představují struktury základní (a do značné míry jedinou) technologii určenou pro tvorbu uživatelsky definovaných heterogenních datových struktur (naproti tomu pole jsou struktury homogenní). Oproti primitivním datovým typům je předávání datových struktur poněkud složitější, a to zejména na straně Pythonu, protože je nutné explicitně specifikovat typy prvků a samozřejmě i jejich pořadí. Další problém, který je někdy nutné řešit, představuje předávání struktur odkazem, tj. s využitím ukazatelů. I s touto problematikou se postupně seznámíme, ovšem až v navazujícím článku.
Ukažme si nyní velmi jednoduchou aplikaci, v níž bude používána datová struktura reprezentující komplexní číslo a v C bude navíc implementována funkce pro součet dvou komplexních čísel.
Část aplikace psaná v programovacím jazyku C bude nejprve obsahovat deklaraci struktury nazvané Complex:
typedef struct { float real; float imag; } Complex;
Ve stejném zdrojovém kódu je taktéž uvedena funkce určená pro součet dvou komplexních čísel. Jedná se o skutečnou funkci, která nijak nemění své parametry, ale vytváří nové komplexní číslo (to ovšem nemusí být příliš efektivní, například při práci s vektory či maticemi komplexních čísel):
extern Complex add(Complex x, Complex y) { Complex result; result.real = x.real + y.real; result.imag = x.imag + y.imag; return result; }
Skript naprogramovaný v Pythonu, který bude volat céčkovou funkci pro součet komplexních čísel, je již poměrně složitý. Nejdříve si uveďme je úplnou podobu a potom se zaměříme na popis jednotlivých částí:
"""Základní použití balíčku ctypes, modul pro import.""" import ctypes library = None def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def add(x, y): """Zavolání externí funkce.""" return library.add(x, y) def init(): global library library = load_library("libadder.so") library.add.argtypes = (Complex, Complex) library.add.restype = Complex class Complex(ctypes.Structure): _fields_ = [("real", ctypes.c_float), ("imag", ctypes.c_float)] def __str__(self): return "Complex: %f + i%f" % (self.real, self.imag) def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() print(library) c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) c3 = add(c1, c2) print(c1) print(c2) print(c3) if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Na začátku pouze naimportujeme funkce a typy z modulu ctypes:
import ctypes
Následně se pokusíme načíst novou dynamickou knihovnu s deklarací struktury Complex i s funkcí add(). Samozřejmě zde můžete odstranit cestu ke knihovně a použít přístup s proměnnou prostředí LD_LIBRARY_PATH:
library = load_library("libadder.so")
Další část je poměrně záludná, ale důležitá, a to konkrétní určení typů parametrů funkce add() a taktéž přesného návratového typu této funkce::
library.add.argtypes = (Complex, Complex) library.add.restype = Complex
Následuje poměrně složitá část skriptu, v níž (znovu) deklarujeme datovou strukturu Complex, tentokrát ovšem takovým způsobem, aby pořadí a typy atributů (fields) přesně odpovídaly rustovské deklaraci. Všimněte si, jak se atributy popisují – uvádí se jejich jméno a datový typ (což je důležité, aby interpret Pythonu mohl strukturu vytvořit tak, aby byla binárním obrazem céčkovské či céčkové struktury). Navíc si – zcela nezávisle na původní struktuře – můžeme přidat metody, například metodu __str__, která nám umožní nechat si vypsat obsah komplexního číslo (tedy reálné a imaginární složky).
class Complex(ctypes.Structure): _fields_ = [("real", ctypes.c_float), ("imag", ctypes.c_float)] def __str__(self): return "Complex: %f + i%f" % (self.real, self.imag)
Nyní se již můžeme začít chovat ke třídě Complex i k funkci add() běžným způsobem – následující kód již neobsahuje žádné speciality a při pohledu na něj ani nelze říct, že by třída Complex či funkce add_complex() byla něčím výjimečná:
c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) c3 = library.add(c1, c2) print(c1) print(c2) print(c3)
Complex: 1.000000 + i2.000000 Complex: 3.000000 + i4.000000 Complex: 4.000000 + i6.000000
Korektní chování si ověříme i jednotkovými testy:
"""Implementace jednotkových testů.""" import pytest from adder import Complex, init, add def test_add_basic(): """Otestování výpočtu součtu dvou komplexních čísel.""" init() c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) result = add(c1, c2) expected = Complex(4.0, 6.0) assert result.real == expected.real and result.imag == expected.imag, \ "Očekávaná hodnota {}, vráceno {}".format(expected, result)
S výsledkem – ok:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project8 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 1 item test_add.py::test_add_basic PASSED [100%] ============================== 1 passed in 0.01s ===============================
13. Porovnání komplexních čísel
Nyní zdrojový kód pro nativní knihovnu rozšíříme o funkci sloužící k porovnání dvou komplexních čísel. Připomeňme si, že v céčku jsou pravdivostní hodnoty představovány typem int:
typedef struct { float real; float imag; } Complex; extern Complex add(Complex x, Complex y) { Complex result; result.real = x.real + y.real; result.imag = x.imag + y.imag; return result; } extern int equal(Complex x, Complex y) { return x.real == y.real && x.imag == y.imag; }
Příslušnou funkci „obalíme“ stejně pojmenovanou funkcí v Pythonu – opět se bude jmenovat equal:
"""Základní použití balíčku ctypes, modul pro import.""" import ctypes library = None def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def add(x, y): """Zavolání externí funkce.""" return library.add(x, y) def equal(x, y): """Zavolání externí funkce.""" return library.equal(x, y) def init(): global library library = load_library("libadder.so") library.add.argtypes = (Complex, Complex) library.add.restype = Complex library.equal.argtypes = (Complex, Complex) library.equal.restype = bool class Complex(ctypes.Structure): _fields_ = [("real", ctypes.c_float), ("imag", ctypes.c_float)] def __str__(self): return "Complex: %f + i%f" % (self.real, self.imag) def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() print(library) c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) c3 = add(c1, c2) print(c1) print(c2) print(c3) print("c1==c2?", equal(c1, c2)) print("c2==c2?", equal(c2, c2)) if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Jednotkové testy se příslušným způsobem zjednoduší – budeme moci zavolat funkci equal a předat jí dvojici komplexních čísel. Využívá se zde faktu, že i v Pythonu lze pro reprezentaci nepravdy použít nulu a pravdy libovolnou jinou celočíselnou hodnotu:
"""Implementace jednotkových testů.""" import pytest from adder import Complex, init, add, equal def test_add_basic(): """Otestování výpočtu součtu dvou komplexních čísel.""" init() c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) result = add(c1, c2) expected = Complex(4.0, 6.0) assert equal(result, expected), \ "Očekávaná hodnota {}, vráceno {}".format(expected, result)
S výsledkem – znovu ok:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/project8 plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 1 item test_add.py::test_add_basic PASSED [100%] ============================== 1 passed in 0.01s ===============================
14. Rozšíření knihovny pro práci s komplexními čísly
Vzhledem k tomu, že v Pythonu je struktura reprezentující komplexní číslo implementována třídou, nic nám nebrání vytvořit si (tedy přetížit) vlastní verzi operátoru porovnávání (==):
class Complex(ctypes.Structure): _fields_ = [("real", ctypes.c_float), ("imag", ctypes.c_float)] def __str__(self): return "Complex: %f + i%f" % (self.real, self.imag) def __eq__(self, other): return equal(self, other)
Výsledná podoba skriptu:
"""Základní použití balíčku ctypes, modul pro import.""" import ctypes library = None def load_library(library_name): """Načtení nativní knihovny.""" return ctypes.CDLL(library_name) def add(x, y): """Zavolání externí funkce.""" return library.add(x, y) def equal(x, y): """Zavolání externí funkce.""" return library.equal(x, y) def init(): global library library = load_library("libadder.so") library.add.argtypes = (Complex, Complex) library.add.restype = Complex library.equal.argtypes = (Complex, Complex) library.equal.restype = bool class Complex(ctypes.Structure): _fields_ = [("real", ctypes.c_float), ("imag", ctypes.c_float)] def __str__(self): return "Complex: %f + i%f" % (self.real, self.imag) def __eq__(self, other): return equal(self, other) def main(): """Otestování, jestli je možné zavolat nativní funkci.""" init() print(library) c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) c3 = add(c1, c2) print(c1) print(c2) print(c3) print("c1==c2?", equal(c1, c2)) print("c2==c2?", equal(c2, c2)) if __name__ == '__main__': # pouze se ujistíme, že lze zavolat nativní funkci main()
Jeho spušení ověří základní chování přetíženého operátoru:
<CDLL 'libadder.so', handle 55fb1a6a4760 at 0x7fe480018390> Complex: 1.000000 + i2.000000 Complex: 3.000000 + i4.000000 Complex: 4.000000 + i6.000000 c1==c2? False c2==c2? True
Dosti zásadním způsobem se zjednoduší jednotkové testy, v nichž bude v konstrukci assert možné porovnat dvě komplexní čísla pomocí operátoru ==. Toto je finální podoba jednotkových testů:
"""Implementace jednotkových testů.""" import pytest from adder import Complex, init, add, equal def test_add_basic(): """Otestování výpočtu součtu dvou komplexních čísel.""" init() c1 = Complex(1.0, 2.0) c2 = Complex(3.0, 4.0) result = add(c1, c2) expected = Complex(4.0, 6.0) assert result == expected, \ "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_add_failure(): """Otestování výpočtu součtu dvou komplexních čísel.""" init() c1 = Complex(1.0, 2.0) c2 = Complex(0.0, 0.0) result = add(c1, c2) expected = Complex(4.0, 6.0) assert result == expected, \ "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Do skriptu byl naschvál vložen test, který po svém spuštění detekuje chybu, aby bylo patrné, že i konstrukce asssert dokáže díky existenci metod __eq__ a __str__ produkovat velmi čitelné výsledky:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /home/ptisnovs/src/python/testing-in-python/native_libs/projectA plugins: print-0.1.3, voluptuous-1.0.2 collecting ... collected 2 items test_add.py::test_add_basic PASSED [ 50%] test_add.py::test_add_failure FAILED [100%] =================================== FAILURES =================================== _______________________________ test_add_failure _______________________________ def test_add_failure(): """Otestování výpočtu součtu dvou komplexních čísel.""" init() c1 = Complex(1.0, 2.0) c2 = Complex(0.0, 0.0) result = add(c1, c2) expected = Complex(4.0, 6.0) > assert result == expected, \ "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota Complex: 4.000000 + i6.000000, vráceno Complex: 1.000000 + i2.000000 E assert <adder.Comple...x7f05abf32c80> == <adder.Comple...x7f05abf32bf8> E +<adder.Complex object at 0x7f05abf32c80> E -<adder.Complex object at 0x7f05abf32bf8> test_add.py:30: AssertionError =========================== short test summary info ============================ FAILED test_add.py::test_add_failure - AssertionError: Očekávaná hodnota Comp... ========================= 1 failed, 1 passed in 0.04s ==========================
15. Nativní knihovny a behavior-driven testing založený na použití knihovny Behave
S BDD testy jsme se již ve stručnosti seznámili v úvodním článku seriálu o testování s využitím Pythonu. Připomeňme si, že i když je BDD poměrně široký pojem, mnohdy si pod ním představujeme využití doménově specifického jazyka nazvaného Gherkin. Tento jazyk je díky existenci mnoha rozhraní integrovatelný do většiny mainstreamových programovacích jazyků, pochopitelně včetně Pythonu. Dnes si ve stručnosti (znovu) představíme knihovnu Behave, s jejíž pomocí se Gherkin integruje právě do jazyka Python. Ve skutečnosti se bude jednat o téměř ideální spojení, protože Gherkin i Python používají podobný způsob zápisu, v němž i odsazení jednotlivých programových řádků je součástí syntaxe (podobně je řešen i jazyk YAML). A protože se zabýváme nativními knihovnami a jejich testováním, budou (dva) projekty, které si představíme, testovat právě nativní céčkovou knihovnu (obsahující funkci pro součet dvou čísel).

Obrázek 1: Ukázka scénářů napsaných v doménově specifickém jazyce Gherkin.
Jazyk Gherkin je navržen takovým způsobem, aby ho uživatelé (nemusí se totiž nutně jednat pouze o programátory) mohli začít používat prakticky okamžitě, tj. bez nutnosti studia sáhodlouhých manuálů. I z toho důvodu si možnosti tohoto doménově specifického jazyka postupně ukážeme na několika demonstračních příkladech. První příklad bude (prozatím) velmi jednoduchý, protože bude obsahovat jediný modul (naprogramovaný v Pythonu), který budeme chtít otestovat. I přesto se však bude jednat o plnohodnotný projekt, jehož struktura odpovídá struktuře projektů složitějších a sofistikovanějších. Adresář s projektem i s testovacím scénářem by měl vypadat následovně:
├── feature_list.txt ├── features │ ├── smoketest.feature │ ├── basic.feature │ ├── advanced.feature │ ├── ... │ ├── ... │ ├── ... │ ├── environment.py │ └── steps │ ├── common.py │ ├── ... │ ├── ... │ └── ... ├── requirements.in ├── requirements.txt ├── run_tests.sh └── src └── application.py 3 directories, 7 files
V projektu můžeme vidět několik typů souborů:
# | Soubor | Popis |
---|---|---|
1 | src/application.py | vlastní modul, který budeme chtít otestovat |
2 | requirements.in/requirements.txt | soubory pro pip (instalátor balíčků jazyka Python) |
3 | feature_list.txt | seznam testovacích scénářů, které se mají spustit |
4 | features/* | adresář obsahující testovací scénáře i implementaci jednotlivých kroků testů |
5 | features/*.feature | jednotlivé testovací scénáře |
6 | features/environment.py | projekt s definicí funkcí vyvolaných před spuštěním testů, před jednotlivými kroky atd. |
7 | features/steps/ | implementace jednotlivých testovacích kroků |
8 | run_tests.sh | pomocný skript pro spuštění testovacích scénářů |
16. Projekt, v němž budeme testovat vlastnosti nativní funkce
Nyní se podívejme na strukturu projektu, v němž budeme testovat základní vlastnosti vybrané nativní funkce na základě testovacího scénáře. Celá struktura projektu bude založena běžných projektech založených na frameworku Behave, ovšem s tím rozdílem, že se v projektu nově objevil adresář lib obsahující zdrojový kód napsaný v jazyku C a skript určený pro vytvoření nativní knihovny:
. ├── feature_list.txt ├── features │ ├── environment.py │ ├── smoketest.feature │ └── steps │ └── common.py ├── lib │ ├── adder.c │ ├── clean.sh │ └── make_library.sh ├── requirements.in ├── requirements.txt └── run_tests.sh 3 directories, 10 files
Následně je nutné připravit testovací scénář, konkrétně soubor smoketest.feature. Scénář připravíme takovým způsobem, že budeme očekávat přetečení výsledků za předpokladu, že velikost datového typu int je 32 bitů (a jak jsme mohli vidět v první části článku, tato situace běžně nastane). V případě, že tato podmínka nebude splněna, testy selžou:
Feature: Smoke test Scenario: Check the function int add(int, int) Given The library libadder.so is loaded When I call native function add with arguments 1 and 2 Then I should get 3 as a result Scenario Outline: Thorough checking function int add(int, int) Given The library libadder.so is loaded When I call native function add with arguments <x> and <y> Then I should get <result> as a result Examples: result | x | y| result | # basic arithmetic | 0| 0| 0| | 1| 2| 3| | 1|-2| -1| # no overflows at 16 bit limits | 32767| 1| 32768| | 65535| 1| 65536| # integer overflow | 2147483648| 1|-2147483647| |-2147483647|-1|-2147483648| |-2147483648|-1| 2147483647|
Samozřejmě budeme muset připravit i implementaci testovacích kroků. Ovšem ještě předtím vytvoříme nový soubor nazvaný environment.py, který bude uložen do adresáře features, tj. do stejného adresáře, v němž se nachází i všechny soubory .feature. V tomto modulu budou deklarovány pomocné funkce volané automaticky interpretrem jazyka Gherkin. Nejdříve provedeme import potřebných modulů (ctypes pro načtení nativní knihovny, druhý modul pro logování, což prozatím nevyužijeme):
from behave.log_capture import capture import ctypes
V tomto modulu si dále vytvoříme pomocnou funkci určenou pro načtení nativní knihovny a uložení reference na ni do kontextu:
def _load_library(context, library_name): """Načtení a inicializace nativní knihovny.""" if context.tested_library is None: context.tested_library = ctypes.CDLL(library_name)
Důležitá je funkce pojmenovaná before_all. Tato funkce je zavolána automaticky před vlastním testovacím scénářem a typicky se v ní nastavuje počáteční stav kontextu (což je jediný parametr předaný funkci při jejím volání):
def before_all(context): """Perform setup before the first event.""" context.tested_library = None context.load_library = _load_library
Implementace kroků testu se nachází v souboru nazvaném common.py (ovšem může být pojmenovaný i jinak – import všech souborů v daném adresáři se provádí automaticky). Povšimněte si provedených změn, především toho, že v klauzuli given se pokusíme načíst nativní knihovnu zadaného jména. Pokud se načtení nativní knihovny podaří, bude v dalších krocích dostupná v atributu context.tested_library:
from behave import given, then, when @given('The library {library_name} is loaded') def initial_state(context, library_name): context.load_library(context, library_name) @when('I call native function add with arguments {x:d} and {y:d}') def call_add(context, x, y): context.result = context.tested_library.add(x, y) @then('I should get {result:d} as a result') def check_integer_result(context, result): assert context.result == result, "Expected result: {e}, returned value: {r}".format(e=result, r=context.result)
V případě, že nativní funkce byla korektně přeložena, měly by se po spuštění skriptu run_tests.sh na standardním výstupu objevit následující řádky produkované knihovnou Behave:
Feature: Smoke test # features/smoketest.feature:1 @smoketest Scenario: Check the function int add(int, int) # features/smoketest.feature:4 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 1 and 2 # features/steps/common.py:9 Then I should get 3 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.1 result # features/smoketest.feature:17 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 0 and 0 # features/steps/common.py:9 Then I should get 0 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.2 result # features/smoketest.feature:18 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 1 and 2 # features/steps/common.py:9 Then I should get 3 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.3 result # features/smoketest.feature:19 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 1 and -2 # features/steps/common.py:9 Then I should get -1 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.4 result # features/smoketest.feature:21 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 32767 and 1 # features/steps/common.py:9 Then I should get 32768 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.5 result # features/smoketest.feature:22 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 65535 and 1 # features/steps/common.py:9 Then I should get 65536 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.6 result # features/smoketest.feature:24 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments 2147483648 and 1 # features/steps/common.py:9 Then I should get -2147483647 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.7 result # features/smoketest.feature:25 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments -2147483647 and -1 # features/steps/common.py:9 Then I should get -2147483648 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.8 result # features/smoketest.feature:26 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with arguments -2147483648 and -1 # features/steps/common.py:9 Then I should get 2147483647 as a result # features/steps/common.py:14 1 feature passed, 0 failed, 0 skipped 9 scenarios passed, 0 failed, 0 skipped 27 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.003s

Obrázek 2: Výsledek běhu testovacích scénářů ve chvíli, kdy se nativní funkce chová podle očekávání.
V případě, že jsme vynechali krok překladu nativní funkce, popř. se nepodařilo nativní knihovnu vůbec načíst (nebo najít), bude výsledek testů mnohem delší a pesimističtější:
Feature: Smoke test # features/smoketest.feature:1 @smoketest Scenario: Check the function int add(int, int) # features/smoketest.feature:4 Given The library libadder.so is loaded # features/steps/common.py:4 Traceback (most recent call last): File "/usr/local/lib/python3.4/dist-packages/behave/model.py", line 1456, in run match.run(runner.context) File "/usr/local/lib/python3.4/dist-packages/behave/model.py", line 1903, in run self.func(context, *args, **kwargs) File "features/steps/common.py", line 6, in initial_state context.load_library(context, library_name) File "features/environment.py", line 7, in _load_library context.tested_library = ctypes.CDLL(library_name) File "/usr/lib/python3.4/ctypes/__init__.py", line 351, in __init__ self._handle = _dlopen(self._name, mode) OSError: libadder.so: cannot open shared object file: No such file or directory When I call native function add with arguments 1 and 2 # None Then I should get 3 as a result # None
…následují informace o pádu všech dalších testů…
Failing scenarios: features/smoketest.feature:4 Check the function int add(int, int) features/smoketest.feature:17 Thorough checking function int add(int, int) -- @1.1 result features/smoketest.feature:18 Thorough checking function int add(int, int) -- @1.2 result features/smoketest.feature:19 Thorough checking function int add(int, int) -- @1.3 result features/smoketest.feature:21 Thorough checking function int add(int, int) -- @1.4 result features/smoketest.feature:22 Thorough checking function int add(int, int) -- @1.5 result features/smoketest.feature:24 Thorough checking function int add(int, int) -- @1.6 result features/smoketest.feature:25 Thorough checking function int add(int, int) -- @1.7 result features/smoketest.feature:26 Thorough checking function int add(int, int) -- @1.8 result 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 9 failed, 0 skipped 0 steps passed, 9 failed, 18 skipped, 0 undefined Took 0m0.002s

Obrázek 3: Výsledek běhu testovacího scénáře ve chvíli, kdy se nepodařilo načíst nativní funkci.
17. Modifikace testovacího scénáře – určení typů parametrů předávaných nativní funkci
Nyní si testovací scénář nepatrně modifikujeme, protože uvedeme i typy parametrů předávaných do nativní funkce. V následujícím výpisu jsou změněné části zvýrazněny tučně:
Feature: Smoke test Scenario: Check the function int add(int, int) Given The library libadder.so is loaded When I call native function add with integer arguments 1 and 2 Then I should get 3 as a result Scenario Outline: Thorough checking function int add(int, int) Given The library libadder.so is loaded When I call native function add with integer arguments <x> and <y> Then I should get <result> as a result Examples: result |x|y|result| # basic arithmetic | 0| 0| 0| | 1| 2| 3| | 1|-2| -1| # no overflows at 16 bit limits | 32767| 1| 32768| | 65535| 1| 65536| # integer overflow | 2147483648| 1|-2147483647| |-2147483647|-1|-2147483648| |-2147483648|-1| 2147483647|
I jednotlivé implementace testovacích kroků budeme modifikovat. Zejména se to týká kroku:
„I call native function add with integer arguments {x:d} and {y:d}“
který je nahrazen za obecnější krok, v němž se jméno funkce vyskytuje jako proměnný text:
„I call native function {function} with integer arguments {x:d} and {y:d}“
Podívejme se nyní na způsob zavolání funkce, jejíž jméno je proměnné:
from behave import given, then, when @given('The library {library_name} is loaded') def initial_state(context, library_name): context.load_library(context, library_name) @when('I call native function {function} with integer arguments {x:d} and {y:d}') def call_add(context, function, x, y): context.result = getattr(context.tested_library, function)(x, y) @then('I should get {result:d} as a result') def check_integer_result(context, result): assert context.result == result
Po spuštění modifikovaného skriptu s testy by se měly na standardním výstupu objevit tyto zprávy, které nás informují o tom, že se nativní funkce add skutečně chová podle očekávání:
Feature: Smoke test # features/smoketest.feature:1 Scenario: Check the function int add(int, int) # features/smoketest.feature:4 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 1 and 2 # features/steps/common.py:9 Then I should get 3 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.1 result # features/smoketest.feature:17 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 0 and 0 # features/steps/common.py:9 Then I should get 0 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.2 result # features/smoketest.feature:18 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 1 and 2 # features/steps/common.py:9 Then I should get 3 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.3 result # features/smoketest.feature:19 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 1 and -2 # features/steps/common.py:9 Then I should get -1 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.4 result # features/smoketest.feature:21 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 32767 and 1 # features/steps/common.py:9 Then I should get 32768 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.5 result # features/smoketest.feature:22 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 65535 and 1 # features/steps/common.py:9 Then I should get 65536 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.6 result # features/smoketest.feature:24 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments 2147483648 and 1 # features/steps/common.py:9 Then I should get -2147483647 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.7 result # features/smoketest.feature:25 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments -2147483647 and -1 # features/steps/common.py:9 Then I should get -2147483648 as a result # features/steps/common.py:14 Scenario Outline: Thorough checking function int add(int, int) -- @1.8 result # features/smoketest.feature:26 Given The library libadder.so is loaded # features/steps/common.py:4 When I call native function add with integer arguments -2147483648 and -1 # features/steps/common.py:9 Then I should get 2147483647 as a result # features/steps/common.py:14 1 feature passed, 0 failed, 0 skipped 9 scenarios passed, 0 failed, 0 skipped 27 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.003s
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/testing-in-python. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady a jejich části, které naleznete v následující tabulce:
19. Předchozí články s tématem testování (nejenom) v Pythonu
Tématem testování jsme se již na stránkách Rootu několikrát zabývali. Jedná se mj. o následující články:
- Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-od-jednotkovych-testu-az-po-testy-ui/ - Použití Pythonu pro tvorbu testů: použití třídy Mock z knihovny unittest.mock
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-pouziti-tridy-mock-z-knihovny-unittest-mock/ - Použití nástroje pytest pro tvorbu jednotkových testů a benchmarků
https://www.root.cz/clanky/pouziti-nastroje-pytest-pro-tvorbu-jednotkovych-testu-a-benchmarku/ - Nástroj pytest a jednotkové testy: fixtures, výjimky, parametrizace testů
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-fixtures-vyjimky-parametrizace-testu/ - Nástroj pytest a jednotkové testy: životní cyklus testů, užitečné tipy a triky
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-zivotni-cyklus-testu-uzitecne-tipy-a-triky/ - Struktura projektů s jednotkovými testy, využití Travis CI
https://www.root.cz/clanky/struktura-projektu-s-jednotkovymi-testy-vyuziti-travis-ci/ - Omezení stavového prostoru testovaných funkcí a metod
https://www.root.cz/clanky/omezeni-stavoveho-prostoru-testovanych-funkci-a-metod/ - Testování aplikací s využitím nástroje Hypothesis
https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis/ - Testování aplikací s využitím nástroje Hypothesis (dokončení)
https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis-dokonceni/ - Testování webových aplikací s REST API z Pythonu
https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu/ - Testování webových aplikací s REST API z Pythonu (2)
https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu-2/ - Testování webových aplikací s REST API z Pythonu (3)
https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu-3/ - Behavior-driven development v Pythonu s využitím knihovny Behave
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave/ - Behavior-driven development v Pythonu s využitím knihovny Behave (druhá část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-druha-cast/ - Behavior-driven development v Pythonu s využitím knihovny Behave (závěrečná část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-zaverecna-cast/ - Validace datových struktur v Pythonu pomocí knihoven Schemagic a Schema
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-pomoci-knihoven-schemagic-a-schema/ - Validace datových struktur v Pythonu (2. část)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/ - Validace datových struktur v Pythonu (dokončení)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/ - Univerzální testovací nástroj Robot Framework
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/ - Univerzální testovací nástroj Robot Framework a BDD testy
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/ - Úvod do problematiky fuzzingu a fuzz testování
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/ - Úvod do problematiky fuzzingu a fuzz testování – složení vlastního fuzzeru
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani-slozeni-vlastniho-fuzzeru/ - Knihovny a moduly usnadňující testování aplikací naprogramovaných v jazyce Clojure
https://www.root.cz/clanky/knihovny-a-moduly-usnadnujici-testovani-aplikaci-naprogramovanych-v-jazyce-clojure/ - Validace dat s využitím knihovny spec v Clojure 1.9.0
https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/ - Testování aplikací naprogramovaných v jazyce Go
https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/ - Knihovny určené pro tvorbu testů v programovacím jazyce Go
https://www.root.cz/clanky/knihovny-urcene-pro-tvorbu-testu-v-programovacim-jazyce-go/ - Testování aplikací psaných v Go s využitím knihoven Goblin a Frisby
https://www.root.cz/clanky/testovani-aplikaci-psanych-v-go-s-vyuzitim-knihoven-goblin-a-frisby/ - Testování Go aplikací s využitím knihovny GΩmega a frameworku Ginkgo
https://www.root.cz/clanky/testovani-go-aplikaci-s-vyuzitim-knihovny-gomega-mega-a-frameworku-ginkgo/ - Tvorba BDD testů s využitím jazyka Go a nástroje godog
https://www.root.cz/clanky/tvorba-bdd-testu-s-vyuzitim-jazyka-go-a-nastroje-godog/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem (dokončení)
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem-dokonceni/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
20. Odkazy na Internetu
- Dokumentace k CFFI
https://cffi.readthedocs.io/en/latest/overview.html - How can use CFFI to call an existing C function given the source code?
https://stackoverflow.com/questions/37936045/how-can-use-cffi-to-call-an-existing-c-function-given-the-source-code - Python Bindings: Calling C or C++ From Python
https://realpython.com/python-bindings-overview/ - Wrapping a C library in Python: C, Cython or ctypes?
https://stackoverflow.com/questions/1942298/wrapping-a-c-library-in-python-c-cython-or-ctypes - Calling Rust From Python
https://bheisler.github.io/post/calling-rust-in-python/ - Calling Rust in Python (komentáře k předchozímu článku)
https://www.reddit.com/r/rust/comments/63iy5a/calling_rust_in_python/ - CFFI Documentation
https://cffi.readthedocs.io/en/latest/ - Build Script Support
http://doc.crates.io/build-script.html - Creating a shared and static library with the gnu compiler [gcc]
http://www.adp-gmbh.ch/cpp/gcc/create_lib.html - ctypes — A foreign function library for Python
https://docs.python.org/2/library/ctypes.html - FFI: Foreign Function Interface
https://doc.rust-lang.org/book/ffi.html - Primitive Type pointer
https://doc.rust-lang.org/std/primitive.pointer.html - Requests: HTTP for Humans (dokumentace)
http://docs.python-requests.org/en/master/ - Requests: Introduction
http://docs.python-requests.org/en/latest/user/intro/ - Requests na GitHubu
https://github.com/requests/requests - Requests (software) na Wikipedii
https://en.wikipedia.org/wiki/Requests_%28software%29 - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - 20 Python libraries you can’t live without
https://pythontips.com/2013/07/30/20-python-libraries-you-cant-live-without/ - What are the top 10 most useful and influential Python libraries and frameworks?
https://www.quora.com/What-are-the-top-10-most-useful-and-influential-Python-libraries-and-frameworks - Python: useful modules
https://wiki.python.org/moin/UsefulModules - Top 15 most popular Python libraries
https://keyua.org/blog/most-popular-python-libraries/ - Hypertext Transfer Protocol
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol - List of HTTP header fields
https://en.wikipedia.org/wiki/List_of_HTTP_header_fields - List of HTTP status codes
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes - Python requests deep dive
https://medium.com/@anthonypjshaw/python-requests-deep-dive-a0a5c5c1e093 - The awesome requests module
https://www.pythonforbeginners.com/requests/the-awesome-requests-module - Send HTTP Requests in Python
https://code-maven.com/http-requests-in-python - Introducing JSON
http://json.org/ - Writing tests for RESTful APIs in Python using requests – part 1: basic tests
https://www.ontestautomation.com/writing-tests-for-restful-apis-in-python-using-requests-part-1-basic-tests/ - Step by Step Rest API Testing using Python + Pytest + Allure
https://www.udemy.com/course/api-testing-python/ - Prime formulas and polynomial functions
https://en.wikipedia.org/wiki/Formula_for_primes#Prime_formulas_and_polynomial_functions - Prime-Generating Polynomial
https://mathworld.wolfram.com/Prime-GeneratingPolynomial.html - Hoare logic
https://en.wikipedia.org/wiki/Hoare_logic - Goto Fail, Heartbleed, and Unit Testing Culture
https://martinfowler.com/articles/testing-culture.html - PEP-484
https://www.python.org/dev/peps/pep-0484/ - In-depth: Functional programming in C++
https://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php - mypy
http://www.mypy-lang.org/ - Welcome to Mypy documentation!
https://mypy.readthedocs.io/en/latest/index.html - mypy na GitHubu
https://github.com/python/mypy - mypy 0.770 na PyPi
https://pypi.org/project/mypy/ - Extensions for mypy (separated out from mypy/extensions)
https://github.com/python/mypy_extensions - The Mypy Blog
https://mypy-lang.blogspot.com/2020/03/mypy-0770-released.html - Our journey to type checking 4 million lines of Python
https://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python - Type-Checking Python Programs With Type Hints and mypy
https://www.youtube.com/watch?v=2×WhaALHTvU - Refactoring to Immutability – Kevlin Henney
https://www.youtube.com/watch?v=APUCMSPiNh4 - Bernat Gabor – Type hinting (and mypy) – PyCon 2019
https://www.youtube.com/watch?v=hTrjTAPnA_k - Stanford Seminar – Optional Static Typing for Python
https://www.youtube.com/watch?v=GiZKuyLKvAA - mypy Getting to Four Million Lines of Typed Python – Michael Sullivan
https://www.youtube.com/watch?v=FT_WHV4-QcU - Shebang
https://en.wikipedia.org/wiki/Shebang_(Unix) - pytest 5.4.2 na PyPi
https://pypi.org/project/pytest/ - Hillel Wayne – Beyond Unit Tests: Taking Your Testing to the Next Level – PyCon 2018
https://www.youtube.com/watch?v=MYucYon2-lk - Awesome Python – testing
https://github.com/vinta/awesome-python#testing - pytest Plugins Compatibility
http://plugincompat.herokuapp.com/ - Selenium (pro Python)
https://pypi.org/project/selenium/ - Getting Started With Testing in Python
https://realpython.com/python-testing/ - unittest.mock — mock object library
https://docs.python.org/3.5/library/unittest.mock.html - mock 2.0.0
https://pypi.python.org/pypi/mock - An Introduction to Mocking in Python
https://www.toptal.com/python/an-introduction-to-mocking-in-python - Mock – Mocking and Testing Library
http://mock.readthedocs.io/en/stable/ - Python Mocking 101: Fake It Before You Make It
https://blog.fugue.co/2016–02–11-python-mocking-101.html - Nauč se Python! – Testování
http://naucse.python.cz/lessons/intro/testing/ - Flexmock (dokumentace)
https://flexmock.readthedocs.io/en/latest/ - Test Fixture (Wikipedia)
https://en.wikipedia.org/wiki/Test_fixture - Mock object (Wikipedia)
https://en.wikipedia.org/wiki/Mock_object - Extrémní programování
https://cs.wikipedia.org/wiki/Extr%C3%A9mn%C3%AD_programov%C3%A1n%C3%AD - Programování řízené testy
https://cs.wikipedia.org/wiki/Programov%C3%A1n%C3%AD_%C5%99%C3%ADzen%C3%A9_testy - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - Tox
https://tox.readthedocs.io/en/latest/ - pytest: helps you write better programs
https://docs.pytest.org/en/latest/ - doctest — Test interactive Python examples
https://docs.python.org/dev/library/doctest.html#module-doctest - unittest — Unit testing framework
https://docs.python.org/dev/library/unittest.html - Python namespaces
https://bytebaker.com/2008/07/30/python-namespaces/ - Namespaces and Scopes
https://www.python-course.eu/namespaces.php - Stránka projektu Robot Framework
https://robotframework.org/ - GitHub repositář Robot Frameworku
https://github.com/robotframework/robotframework - Robot Framework (Wikipedia)
https://en.wikipedia.org/wiki/Robot_Framework - Tutoriál Robot Frameworku
http://www.robotframeworktutorial.com/ - Robot Framework Documentation
https://robotframework.org/robotframework/ - Robot Framework Introduction
https://blog.testproject.io/2016/11/22/robot-framework-introduction/ - robotframework 3.1.2 na PyPi
https://pypi.org/project/robotframework/ - Robot Framework demo (GitHub)
https://github.com/robotframework/RobotDemo - Robot Framework web testing demo using SeleniumLibrary
https://github.com/robotframework/WebDemo - Robot Framework for Mobile Test Automation Demo
https://www.youtube.com/watch?v=06LsU08slP8 - Gherkin
https://cucumber.io/docs/gherkin/ - Selenium
https://selenium.dev/ - SeleniumLibrary
https://robotframework.org/ - The Practical Test Pyramid
https://martinfowler.com/articles/practical-test-pyramid.html - Acceptance Tests and the Testing Pyramid
http://www.blog.acceptancetestdrivendevelopment.com/acceptance-tests-and-the-testing-pyramid/ - Tab-separated values
https://en.wikipedia.org/wiki/Tab-separated_values - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321 - radamsa
https://gitlab.com/akihe/radamsa - Fuzzing (Wikipedia)
https://en.wikipedia.org/wiki/Fuzzing - american fuzzy lop
http://lcamtuf.coredump.cx/afl/ - Fuzzing: the new unit testing
https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1 - Corpus for github.com/dvyukov/go-fuzz examples
https://github.com/dvyukov/go-fuzz-corpus - AFL – QuickStartGuide.txt
https://github.com/google/AFL/blob/master/docs/QuickStartGuide.txt - Introduction to Fuzzing in Python with AFL
https://alexgaynor.net/2015/apr/13/introduction-to-fuzzing-in-python-with-afl/ - Writing a Simple Fuzzer in Python
https://jmcph4.github.io/2018/01/19/writing-a-simple-fuzzer-in-python/ - How to Fuzz Go Code with go-fuzz (Continuously)
https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/ - Golang Fuzzing: A go-fuzz Tutorial and Example
http://networkbit.ch/golang-fuzzing/ - Fuzzing Python Modules
https://stackoverflow.com/questions/20749026/fuzzing-python-modules - 0×3 Python Tutorial: Fuzzer
http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/ - fuzzing na PyPi
https://pypi.org/project/fuzzing/ - Fuzzing 0.3.2 documentation
https://fuzzing.readthedocs.io/en/latest/ - Randomized testing for Go
https://github.com/dvyukov/go-fuzz - HTTP/2 fuzzer written in Golang
https://github.com/c0nrad/http2fuzz - Ffuf (Fuzz Faster U Fool) – An Open Source Fast Web Fuzzing Tool
https://hacknews.co/hacking-tools/20191208/ffuf-fuzz-faster-u-fool-an-open-source-fast-web-fuzzing-tool.html - Continuous Fuzzing Made Simple
https://fuzzit.dev/ - Halt and Catch Fire
https://en.wikipedia.org/wiki/Halt_and_Catch_Fire#Intel_x86 - Random testing
https://en.wikipedia.org/wiki/Random_testing - Monkey testing
https://en.wikipedia.org/wiki/Monkey_testing - Fuzzing for Software Security Testing and Quality Assurance, Second Edition
https://books.google.at/books?id=tKN5DwAAQBAJ&pg=PR15&lpg=PR15&q=%22I+settled+on+the+term+fuzz%22&redir_esc=y&hl=de#v=onepage&q=%22I%20settled%20on%20the%20term%20fuzz%22&f=false - libFuzzer – a library for coverage-guided fuzz testing
https://llvm.org/docs/LibFuzzer.html - fuzzy-swagger na PyPi
https://pypi.org/project/fuzzy-swagger/ - fuzzy-swagger na GitHubu
https://github.com/namuan/fuzzy-swagger - Fuzz testing tools for Python
https://wiki.python.org/moin/PythonTestingToolsTaxonomy#Fuzz_Testing_Tools - A curated list of awesome Go frameworks, libraries and software
https://github.com/avelino/awesome-go - gofuzz: a library for populating go objects with random values
https://github.com/google/gofuzz - tavor: A generic fuzzing and delta-debugging framework
https://github.com/zimmski/tavor - hypothesis na GitHubu
https://github.com/HypothesisWorks/hypothesis - Hypothesis: Test faster, fix more
https://hypothesis.works/ - Hypothesis
https://hypothesis.works/articles/intro/ - What is Hypothesis?
https://hypothesis.works/articles/what-is-hypothesis/ - What is Property Based Testing?
https://hypothesis.works/articles/what-is-property-based-testing/ - Databáze CVE
https://www.cvedetails.com/ - Fuzz test Python modules with libFuzzer
https://github.com/eerimoq/pyfuzzer - Taof – The art of fuzzing
https://sourceforge.net/projects/taof/ - JQF + Zest: Coverage-guided semantic fuzzing for Java
https://github.com/rohanpadhye/jqf - http2fuzz
https://github.com/c0nrad/http2fuzz - Demystifying hypothesis testing with simple Python examples
https://towardsdatascience.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294 - Testování
http://voho.eu/wiki/testovani/ - Unit testing (Wikipedia.en)
https://en.wikipedia.org/wiki/Unit_testing - Unit testing (Wikipedia.cz)
https://cs.wikipedia.org/wiki/Unit_testing - Unit Test vs Integration Test
https://www.youtube.com/watch?v=0GypdsJulKE - TestDouble
https://martinfowler.com/bliki/TestDouble.html - Test Double
http://xunitpatterns.com/Test%20Double.html - Test-driven development (Wikipedia)
https://en.wikipedia.org/wiki/Test-driven_development - Acceptance test–driven development
https://en.wikipedia.org/wiki/Acceptance_test%E2%80%93driven_development - Gauge
https://gauge.org/ - Gauge (software)
https://en.wikipedia.org/wiki/Gauge_(software) - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst
https://medium.com/@fistsOfReason/testing-is-good-pyramids-are-bad-ice-cream-cones-are-the-worst-ad94b9b2f05f - Články a zprávičky věnující se Pythonu
https://www.root.cz/n/python/ - PythonTestingToolsTaxonomy
https://wiki.python.org/moin/PythonTestingToolsTaxonomy - Top 6 BEST Python Testing Frameworks [Updated 2020 List]
https://www.softwaretestinghelp.com/python-testing-frameworks/ - pytest-print 0.1.3
https://pypi.org/project/pytest-print/ - pytest fixtures: explicit, modular, scalable
https://docs.pytest.org/en/latest/fixture.html - PyTest Tutorial: What is, Install, Fixture, Assertions
https://www.guru99.com/pytest-tutorial.html - Pytest – Fixtures
https://www.tutorialspoint.com/pytest/pytest_fixtures.htm - Marking test functions with attributes
https://docs.pytest.org/en/latest/mark.html - pytest-print
https://pytest-print.readthedocs.io/en/latest/ - Continuous integration
https://en.wikipedia.org/wiki/Continuous_integration - Travis CI
https://travis-ci.org/ - Mutation testing
https://en.wikipedia.org/wiki/Mutation_testing - Články o Hypothesis
https://news.ycombinator.com/from?site=hypothesis.works - Testovací případ
https://cs.wikipedia.org/wiki/Testovac%C3%AD_p%C5%99%C3%ADpad - Most testing is ineffective
https://hypothesis.works/