Hlavní navigace

Testování webových aplikací s REST API z Pythonu (3)

 Autor: Depositphotos
Dnes konečně dojde ke spojení dvou technologií, které již známe: frameworku Pytest a knihovny Requests. Uvidíme, že psaní testů s využitím těchto dvou technologií je snadné a poměrně rychlé.
Pavel Tišnovský 16. 7. 2020
Doba čtení: 49 minut

Sdílet

11. Otestování REST API endpointu dostupného přes HTTP metodu POST

12. Zaslání dat na server s využitím metody POST

13. Předání dat serveru v těle HTTP požadavku typu POST

14. Předání cookies v požadavku a test dat vrácených v odpovědi serveru

15. Správa sezení s využitím cookies

16. Kontrola složitějších datových struktur vracených serverem

17. Zpracování XML a HTML v Pythonu s využitím knihovny lxml

18. Repositář s demonstračními příklady

19. Předchozí články s tématem testování (nejenom) v Pythonu

20. Odkazy na Internetu

1. Testování webových aplikací s REST API z Pythonu (3)

Na předchozí dva články [1] [2] dnes navážeme, protože si ukážeme, jakým způsobem je možné využít knihovnu Requests společně s frameworkem Pytest, jehož základní možnosti jsme si již taktéž popsali v [3] [4] a [5]. Sice by se mohlo zdát, že Pytest je určen pouze pro psaní jednotkových testů (což je skutečně jeho primárním účelem), ovšem ve skutečnosti nám nikdo nebrání v tom použít tento velmi užitečný nástroj i pro testy integrační, popř. konkrétně pro vytváření testů REST API.

Poznámka: na druhou stranu některé pluginy pro Pytest, například plugin pro zjištění pokrytí kódu jednotkovými testy, přestanou mít význam.

Teoreticky si v mnoha případech nasazení REST API testů skutečně vystačíme pouze s dvojicí Requests+Pytest, ovšem dále uvidíme, že může být velmi výhodné začít používat i další nástroje, které například umožňují automatické či poloautomatické vytváření testů na základě specifikace OpenAPI, resp. Swaggeru. Toto téma je velmi zajímavé především u těch služeb, které mají rozsáhlé REST API, tj. obsahují mnoho koncových bodů, každý s několika HTTP metodami, mnoha parametry atd.

Poznámka: samozřejmě je dobré si položit otázku, jestli je takové „barokní“ REST API vůbec korektní či alespoň vhodné používat ve světě mikroslužeb; na druhou stranu mnohdy nemá tester možnost zasahovat do architektury již vytvořené aplikace
.

V závěru článku se ve stručnosti zmíníme o tom, jak je možné testovat složitější datové struktury vracené v REST API odpovědích, popř. jak lze testovat XML, popř. přímo HTML stránky (či jejich fragmenty), které jsou serverem vraceny v HTTP odpovědích.

2. Poslání požadavku s využitím HTTP metody GET

Začněme velmi jednoduchým demonstračním příkladem, v němž pouze pošleme HTTP požadavek (request) na server s REST API rozhraním a zkontrolujeme a poté provedeme „dummy“ test na existenci objektu typu response (ten se ve skutečnosti vrátí za všech okolností, protože v opačném případě by nastala výjimka, která by byla vyhozena a následně zachycena samotným Pytestem ještě předtím, než by se došlo k příkazu assert). V tomto demonstračním příkladu se tedy neprovádí žádné reálné testy, pouze si ukazujeme, jak vypadá typická funkce (nebo i metoda) s implementací REST API testů:

  1. Její jméno začíná prefixem „test_“
  2. Uvnitř funkce/metody se typicky nachází sekvence příkazů (řídicích konstrukcí) assert.
  3. Alternativně je možné namísto příkazu assert využít pomocné metody z testovacího frameworku unittest, které jsou vypsány zde.

Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/reques­ts/tests/01_basic_usage_tes­t.py a vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    assert response is not None
 
 
def test_get_method_for_missing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/neexistuje"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    assert response is not None

Vzhledem k tomu, že se jedná o korektně napsané testovací funkce spustitelné nástrojem Pytest, můžeme si chování snadno otestovat:

$ pytest -v 01_basic_usage_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 2 items
 
01_basic_usage_test.py::test_get_method_for_existing_endpoint PASSED     [ 50%]
01_basic_usage_test.py::test_get_method_for_missing_endpoint PASSED      [100%]
 
============================== 2 passed in 0.71s ===============================
Poznámka: vzhledem k tomu, že je příkaz assert nejdůležitější částí testů (ostatní příkazy jsou z tohoto pohledu jen pomocné), je ve všech zdrojových kódech zvýrazněn podtrženým písmem.

3. Otestování, zda byla odpověď poslána s očekávaným HTTP kódem

Z předchozích článků již víme, že stavový kód odpovědi serveru je reprezentován celým číslem, přičemž z první číslice (stovky) lze odvodit základní vlastnost stavu (úspěch, chyba, přesměrování…):

Skupina stavových kódů Význam
1×x informační, potvrzení atd. (ovšem požadavek se prozatím nevykonal)
2×x úspěšné vyřízení požadavku, popř. jeho akceptace serverem (202)
3×x přesměrování požadavku, informace o tom, že se objekt nezměnil atd.
4×x různé typy chyb typicky zaviněných klientem (bohužel nejrozsáhlejší skupina)
5×x různé chyby na serveru

Pokud budeme dotaz posílat s využitím knihovny Requests, je možné ke stavovému kódu přistupovat přes objekt typu Response, představujícího odpověď serveru. Číselný kód stavu uložen v atributu pojmenovaném status_code. Kromě toho existuje ještě atribut nazvaný ok, který obsahuje pravdivostní hodnotu True v případě, že je číselný kód stavu menší než 400 a hodnotu False v opačném případě. Obě tyto hodnoty pochopitelně můžeme snadno otestovat:

assert response.ok
assert response.status_code == 200

Tento typ testů je ukázán ve druhém demonstračním příkladu, který zjistí, zda je možný přístup na existující URL i na neexistující koncový bod. Úplný zdrojový kód tohoto demonstračního příkladu lze nalézt na adrese https://github.com/tisnik/testing-in-python/blob/master/reques­ts/tests/02_check_status_tes­t.py a vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def test_get_method_for_missing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/neexistuje"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    assert response is not None
    assert response.ok
    assert response.status_code == 200

Po spuštění těchto testů by mělo dojít k detekci chyby, a to konkrétně u druhého testu, který se snaží přistoupit na neexistující koncový bod REST API serveru:

$ pytest -v 02_check_status_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 2 items
 
02_check_status_test.py::test_get_method_for_existing_endpoint PASSED    [ 50%]
02_check_status_test.py::test_get_method_for_missing_endpoint FAILED     [100%]
 
=================================== FAILURES ===================================
_____________________ test_get_method_for_missing_endpoint _____________________
 
    def test_get_method_for_missing_endpoint():
        # adresa s testovaci REST API sluzbou
        URL = "http://httpbin.org/neexistuje"
     
        # poslani HTTP dotazu typu GET
        response = requests.get(URL)
     
        assert response is not None
>       assert response.ok
E       assert False
E        +  where False = <Response [404]>.ok
 
02_check_status_test.py:27: AssertionError
=========================== short test summary info ============================
FAILED 02_check_status_test.py::test_get_method_for_missing_endpoint - assert...
========================= 1 failed, 1 passed in 0.88s ==========================

4. Příprava funkcí pro knihovnu určenou pro testování REST API

V předchozích testech už bylo patrné, že se některé operace – zejména podmínky – opakují. Z tohoto důvodu nemusí být špatným nápadem provést malý refaktoring a připravit si tak malou knihovnu určenou pro zjednodušení psaní REST API testů. Inspirací přitom je knihovna Frisby určená pro programovací jazyk Go, s níž jsme se seznámili v článku Testování aplikací psaných v Go s využitím knihoven Goblin a Frisby. Tato knihovna používá zřetězení funkcí, ovšem prozatím si vystačíme pouze s pomocnými funkcemi; například s funkcí, která pouze otestuje, zda server vrátil HTTP kód 200 OK či nikoli:

def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200

Zařazení takové funkce do testů je snadné, což je ostatně ukázáno i na dnešním třetím demonstračním příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
 
 
def test_get_method_for_missing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/neexistuje"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)

Nyní by měl nástroj Pytest vypsat informaci o tom, že první test byl dokončen úspěšně, ale druhý skončil s chybou:

$ pytest -v 03_refactor_check_status_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 2 items
 
03_refactor_check_status_test.py::test_get_method_for_existing_endpoint PASSED [ 50%]
03_refactor_check_status_test.py::test_get_method_for_missing_endpoint FAILED [100%]
 
=================================== FAILURES ===================================
_____________________ test_get_method_for_missing_endpoint _____________________
 
    def test_get_method_for_missing_endpoint():
        # adresa s testovaci REST API sluzbou
        URL = "http://httpbin.org/neexistuje"
 
        # poslani HTTP dotazu typu GET
        response = requests.get(URL)
 
        # zakladni test odpovedi
>       expect_ok_response(response)

03_refactor_check_status_test.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
response = <Response [404]>
 
    def expect_ok_response(response):
        assert response is not None
>       assert response.ok
E       assert False
E        +  where False = <Response [404]>.ok
 
03_refactor_check_status_test.py:9: AssertionError
=========================== short test summary info ============================
FAILED 03_refactor_check_status_test.py::test_get_method_for_missing_endpoint
========================= 1 failed, 1 passed in 0.88s ==========================

5. Kontrola hlavičky nebo hlaviček vrácených serverem v odpovědi

Samotné odpovědi serveru neobsahují pouze požadovaná data (payload), ale i metainformace o těchto datech. Tyto metainformace se ukládají do hlaviček odpovědi, ke kterým samozřejmě máme při použití knihovny Requests přístup a můžeme je tedy zkontrolovat i v testech REST API, například následujícím způsobem:

# test existence hlavicky
assert "content-type" in headers
 
# kontrola obsahu hlavicky
assert headers["content-type"] == "application/json"
Poznámka: z tohoto úryvku kódu je patrné, že se jedná o běžný slovník.

V dnešním čtvrtém demonstračním příkladu je výše uvedený úryvek programového kódu použit pro ověření, že server vrací odpověď se správným typem, tedy konkrétně data odpovídající MIME typu „application/json“:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # precteni hlavicek
    headers = response.headers
 
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == "application/json"

Výsledek získaný po spuštění testů:

$ pytest -v 04_check_content_type.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
04_check_content_type.py::test_get_method_for_existing_endpoint PASSED   [100%]
 
============================== 1 passed in 0.53s ===============================

Samozřejmě nám nic nebrání si vyzkoušet chování v případě, že se vrací neočekávaný typ dat:

$ pytest -v 04_check_content_type_B.py
 
================================================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collected 1 item
 
04_check_content_type_B.py::test_get_method_for_existing_endpoint FAILED                                        [100%]
 
====================================================== FAILURES =======================================================
________________________________________ test_get_method_for_existing_endpoint ________________________________________
 
    def test_get_method_for_existing_endpoint():
        # adresa s testovaci REST API sluzbou
        URL = "http://httpbin.org/get"
 
        # poslani HTTP dotazu typu GET
        response = requests.get(URL)
 
        # precteni hlavicek
        headers = response.headers
 
        assert response is not None
        assert response.ok
        assert response.status_code == 200
 
        # test existence hlavicky
        assert "content-type" in headers
 
        # kontrola obsahu hlavicky
>       assert headers["content-type"] == "text/plain"
E       AssertionError: assert 'application/json' == 'text/plain'
E         - text/plain
E         + application/json
 
04_check_content_type_B.py:25: AssertionError
========================================================= short test summary info ==================================================
FAILED 04_check_content_type_B.py::test_get_method_for_existing_endpoint - AssertionError: assert 'application/json' == 'text/plain'
============================================================ 1 failed in 0.66s =====================================================

6. Samostatná funkce pro otestování typu odpovědi

Kontrola typu odpovědi (resp. přesněji MIME typu) je v praxi tak častá, že si pro ni můžeme vytvořit novou pomocnou funkci volanou z jednotlivých testů:

def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type

Upravený kód tohoto demonstračního příkladu s dvojicí testů vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")

Chování takto upraveného příkladu po spuštění:

$ pytest -v 05_refactor_check_content_type.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
05_refactor_check_content_type.py::test_get_method_for_existing_endpoint PASSED [100%]
 
============================== 1 passed in 0.47s ===============================

Opět je samozřejmě možné otestovat i negativní příklad (s chybami):

$ pytest -v 05_refactor_check_content_type_B.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
05_refactor_check_content_type_B.py::test_get_method_for_existing_endpoint FAILED [100%]
 
=================================== FAILURES ===================================
____________________ test_get_method_for_existing_endpoint _____________________
 
    def test_get_method_for_existing_endpoint():
        # adresa s testovaci REST API sluzbou
        URL = "http://httpbin.org/get"
     
        # poslani HTTP dotazu typu GET
        response = requests.get(URL)
     
        # zakladni test odpovedi
        expect_ok_response(response)
>       expect_content_type(response, "text/plain")
 
05_refactor_check_content_type_B.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
response = <Response [200]>, content_type = 'text/plain'
 
    def expect_content_type(response, content_type):
        # precteni hlavicek
        headers = response.headers
 
        # test existence hlavicky
        assert "content-type" in headers
 
        # kontrola obsahu hlavicky
>       assert headers["content-type"] == content_type
E       AssertionError: assert 'application/json' == 'text/plain'
E         - text/plain
E         + application/json
 
05_refactor_check_content_type_B.py:21: AssertionError
=========================== short test summary info ============================
FAILED 05_refactor_check_content_type_B.py::test_get_method_for_existing_endpoint
============================== 1 failed in 0.57s ===============================

7. Test, zda server poslal rastrové obrázky či podobná data

Mnoho serverů nevrací požadované informace pouze ve formátu JSON. Příkladem mohou být mapové servery vracející buď vektorové kresby ve formátu SVG nebo rastrové obrázky ve formátu PNG, GIF, popř. JPEG. I tyto typy informací mají přiřazen příslušný MIME typ, takže si náš test můžeme rozšířit o další typy dat, zde konkrétně o otestování, zda server vrátil rastrové obrázky ve formátech PNG a JPEG (pochopitelně při přístupu na příslušné koncové body):

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
 
def test_get_method_for_png_image():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/image/png"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "image/png")
 
 
def test_get_method_for_jpeg_image():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/image/jpeg"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "image/jpeg")

Spuštění testů vůči existujícímu REST API serveru:

$ pytest -v 06_more_content_types.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 3 items
 
06_more_content_types.py::test_get_method_for_existing_endpoint PASSED   [ 33%]
06_more_content_types.py::test_get_method_for_png_image PASSED           [ 66%]
06_more_content_types.py::test_get_method_for_jpeg_image PASSED          [100%]
 
============================== 3 passed in 1.49s ===============================

8. Otestování těla odpovědi

Testy REST API pochopitelně musí zjišťovat i to, jestli server skutečně vrátil požadovanou informaci či informace. Musíme tedy nejenom testovat obsah HTTP hlaviček odpovědi (což jsou metainformace), ale i obsah těla odpovědi. Pokud je odpověď čistě textová (nebo čistě binární), tj. bez složitější struktury, můžeme s tělem odpovědi pracovat přímo, a to přes tento atribut:

response.text

Nejjednodušší test může zjišťovat, jestli se v těle odpovědi nachází očekávaný řetězec. Samozřejmě můžeme použít regulární výrazy atd., ovšem test na přítomnost očekávaného (pod)řetězce lze v Pythonu napsat ještě stručněji:

def expect_text_in_payload(response, expected):
    assert expected in response.text

V dalším demonstračním příkladu se zjišťuje, jestli se v těle odpovědi nachází text „42“:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def expect_text_in_payload(response, expected):
    assert expected in response.text
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get?x=6&y=7&answer=42"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    expect_text_in_payload(response, "42")

Spuštění testů:

$ pytest -v 07_check_payload.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
07_check_payload.py::test_get_method_for_existing_endpoint PASSED        [100%]
 
============================== 1 passed in 0.47s ===============================

Předchozí test skutečně prošel, a to z toho důvodu, že odpověď serveru obsahuje následující data:

{
  "args": {
    "answer": "42",
    "x": "6",
    "y": "7"
  },
  "headers": {
    "Host": "a0207c42-pmhttpbin-pmhttpb-c018-592832243.us-east-1.elb.amazonaws.com",
    "X-Amzn-Trace-Id": "Root=1-5f022b53-28ef633486278da51c263ebf"
  },
  "origin": "37.48.51.80",
  "url": "http://a0207c42-pmhttpbin-pmhttpb-c018-592832243.us-east-1.elb.amazonaws.com/get?x=6&y=7&answer=42"
}

9. Zpracování a otestování odpovědi serveru ve formátu JSON

Velmi často se setkáme s tím, že je nutné zpracovat nebo alespoň zkontrolovat odpověď serveru ve formátu JSON. Příkladem může být odpověď ve formátu, který byl ukázán na konci předchozí kapitoly. Jak tedy například zjistíme, že se správně vrátila hodnota atributu answer (tedy řetězec „42“), který je uložen v atributu args:

{
  "args": {
    "answer": "42", 
    ...
    ...
    ...

Jedno z možných řešení spočívá v otrockém přístupu k hodnotě atributu:

# pruchod datovou strukturou odeslanou serverem
encoded = response.json()
 
assert "args" in encoded
args = encoded["args"]
 
assert "answer" in args
answer = args["answer"]
 
assert answer == "42"

Popř. použít jednořádkovou variantu:

# pruchod datovou strukturou odeslanou serverem
encoded = response.json()
 
assert encoded["args"]["answer"] == "42"

Toto jednoduché otestování jednoho atributu odpovědi je ukázáno v další verzi demonstračního příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get?x=6&y=7&answer=42"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # pruchod datovou strukturou odeslanou serverem
    encoded = response.json()
 
    assert "args" in encoded
    args = encoded["args"]
 
    assert "answer" in args
    answer = args["answer"]
 
    assert answer == "42"

S výsledky:

$ pytest -v 08_check_json_content.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
08_check_json_content.py::test_get_method_for_existing_endpoint PASSED   [100%]
 
============================== 1 passed in 0.46s ===============================

10. Výběr atributů s využitím DSL

Při práci s formátem JSON můžeme narazit na úkol získat pouze jeden konkrétní element (nebo jeho hodnotu), a to i v případě, že je tento element zanořený ve složitější datové struktuře. Pro zjednodušení tohoto úkolu jsem pro jeden starší projekt vytvořil pomocnou funkci, která umožňuje zápis cesty k elementu, popř. v určení indexu (pokud se v JSONu používá pole), což je přístup, jenž je (pochopitelně v mnohem sofistikovanější podobě) použit i v rodině jazyků XML v nástrojích podporujících xpath. Podívejme se nyní na použití této funkce v pořadí již devátém demonstračním příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def get_value_using_path(obj, path):
    """Get the attribute value using the XMLpath-like path specification.
    Return any attribute stored in the nested object and list hierarchy using
    the 'path' where path consists of:
        keys (selectors)
        indexes (in case of arrays)
    separated by slash, ie. "key1/0/key_x".
    Usage:
    get_value_using_path({"x" : {"y" : "z"}}, "x"))   -> {"y" : "z"}
    get_value_using_path({"x" : {"y" : "z"}}, "x/y")) -> "z"
    get_value_using_path(["x", "y", "z"], "0"))       -> "x"
    get_value_using_path(["x", "y", "z"], "1"))       -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key1/1")) -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key2/1")) -> "b"
    """
    keys = path.split("/")
    for key in keys:
        if key.isdigit():
            obj = obj[int(key)]
        else:
            obj = obj[key]
    return obj
 
 
def test_get_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/get?x=6&y=7&answer=42"
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # ziskani konkretniho prvku z vracene datove struktury
    encoded = response.json()
 
    answer = get_value_using_path(encoded, "args/answer")
    assert answer == "42"
Poznámka: povšimněte si, že funkce get_value_using_path skutečně dokáže pracovat jak s klasickými seznamy, tak i se slovníky a pochopitelně i s rekurzivně zanořenými datovými strukturami.

Chování si můžeme snadno ověřit spuštěním příslušného příkladu jako testu:

$ pytest -v 09_xpath_like_query.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
09_xpath_like_query.py::test_get_method_for_existing_endpoint PASSED     [100%]
 
============================== 1 passed in 0.44s ===============================

11. Otestování REST API endpointu dostupného přes HTTP metodu POST

Vzhledem k tomu, že knihovna Requests podporuje všechny HTTP metody:

Metoda Příklad použití
GET Základní metoda sloužící k získání dat ze serveru. Může se například jednat o HTML stránku, statický obrázek, ale i výsledek volání REST API služby.
POST Metoda používaná pro odesílání dat na server. Teoreticky je sice možné použít i metodu GET, ovšem sémanticky je vhodnější použít tuto metodu (a REST API služby sémantiku operace většinou dodržují).
   
PUT Tato metoda slouží k nahrání dat na server. S touto metodou se setkáme u některých REST API služeb.
DELETE Slouží ke smazání dat ze serveru. Opět platí – s touto metodou se setkáme méně často u některých REST API služeb.
   
HEAD Tato metoda se částečně podobá metodě GET, ovšem server nevrátí požadovaná data, ale pouze metadata (čas změny, velikost dat, typ/formát dat apod.). Obecně je možné říci, že se tento dotaz zpracuje rychleji než GET.
CONNECT Používá se při použití TCP/IP tunelování.
OPTIONS Poslání dotazu na server, které metody podporuje. Využití najde například při použití CORS apod.
TRACE Server by měl klientovi odeslat požadavek zpět, čehož se používá pro zjištění, které údaje se mění na přenosové cestě.
PATCH Umožňuje změnu dat na serveru, má tady jinou sémantiku než DELETE+PUT.

…můžeme si zkusit napsat velmi jednoduchý test založený na druhé nejpoužívanější metodě POST (zcela nejpoužívanější je díky existenci WWW metoda GET). Další demonstrační příklad vznikl jen nepatrnou úpravou příkladů předchozích:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_post_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/post"
 
    # poslani HTTP dotazu typu POST
    response = requests.post(URL)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
Poznámka: povšimněte si, že i když se používá metoda POST, nepřenáší se na server žádná data, což je sémanticky (většinou) špatné řešení.

Opět si nezapomeneme celý demonstrační příklad spustit ve formě testu:

$ pytest -v 10_post_method_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
10_post_method_test.py::test_post_method_for_existing_endpoint PASSED    [100%]
 
============================== 1 passed in 0.44s ===============================

12. Zaslání dat na server s využitím metody POST

První metoda poslání parametrů od klienta na server používá takzvané „formulářové položky“. Tento poněkud nepřesný název je odvozen od toho, že se podobným způsobem posílají data z HTML formuláře (bez použití JavaScriptu, pouze čistě HTML prostředky). Pokud budeme chtít simulovat posílání dat tímto způsobem, můžeme použít nepovinný parametr nazvaný data předaný funkci requests.post():

payload = {
    "klic": "hodnota",
    "answer": 42,
    "question": None,
    "correct": True}
 
# poslani HTTP dotazu typu POST se specifikaci hodnot formulare
response = requests.post(URL, data=payload)

Implementace tohoto postupu do demonstračního příkladu je dosti přímočará:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_post_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/post"
 
    # data posilana serveru
    payload = {
        "klic": "hodnota",
        "answer": 42,
        "question": None,
        "correct": True}
 
    # poslani HTTP dotazu typu POST s telem
    response = requests.post(URL, data=payload)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")

S výsledky:

$ pytest -v 11_post_method_with_payload_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
11_post_method_with_payload_test.py::test_post_method_for_existing_endpoint PASSED [100%]
 
============================== 1 passed in 0.55s ===============================

13. Předání dat serveru v těle HTTP požadavku typu POST

Pokud budeme chtít serveru předat větší množství strukturovaných dat, a to potenciálně včetně speciálních hodnot, je lepší takové údaje předat přímo v těle požadavku. Pro tento účel se ve funkci requests.post() použije nepovinný parametr nazvaný json a nikoli parametr pojmenovaný data (jako tomu bylo v příkladu předchozím). Tento postup jsme si již ukázali v úvodním článku, takže jeho použití v testech bude snadné a přímočaré:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def test_post_method_for_existing_endpoint():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/post"
 
    # data posilana serveru
    payload = {
        "klic": "hodnota",
        "answer": 42,
        "question": None,
        "correct": True}
 
    # poslani HTTP dotazu typu POST s telem
    response = requests.post(URL, json=payload)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")

S výsledky (očekávanými):

$ pytest -v 12_post_method_with_payload_test.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
12_post_method_with_payload_test.py::test_post_method_for_existing_endpoint PASSED [100%]
 
============================== 1 passed in 0.46s ===============================

14. Předání cookies v požadavku a test dat vrácených v odpovědi serveru

Funkce requests.get() umožňuje kromě typu požadavku (HTTP metoda), dat i metadat (typ, například „application/json“) specifikovat i cookies, což je téma, kterému jsme se podrobněji věnovali minule. Parametr, v němž se cookies předávají do požadavku posílaného na server, se jmenuje přímo cookies a předává se v něm buď slovník obsahující dvojice jméno_cookie+hodnota_cookie nebo objekt typu CookieJar. V dalším demonstračním příkladu je ukázáno, jak se reprezentují cookies formou slovníku, což je samozřejmě (alespoň v Pythonu) nejjednodušší řešení. Příklad na server pošle požadavek s cookies a server tyto cookies vrátí v těle odpovědi:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def get_value_using_path(obj, path):
    """Get the attribute value using the XMLpath-like path specification.
    Return any attribute stored in the nested object and list hierarchy using
    the 'path' where path consists of:
        keys (selectors)
        indexes (in case of arrays)
    separated by slash, ie. "key1/0/key_x".
    Usage:
    get_value_using_path({"x" : {"y" : "z"}}, "x"))   -> {"y" : "z"}
    get_value_using_path({"x" : {"y" : "z"}}, "x/y")) -> "z"
    get_value_using_path(["x", "y", "z"], "0"))       -> "x"
    get_value_using_path(["x", "y", "z"], "1"))       -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key1/1")) -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key2/1")) -> "b"
    """
    keys = path.split("/")
    for key in keys:
        if key.isdigit():
            obj = obj[int(key)]
        else:
            obj = obj[key]
    return obj
 
 
def test_cookies():
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/cookies"
 
    # hlavicka posilana v dotazu
    headers = {'accept': 'application/json'}
 
    # priprava cookies
    cookies = {'key1': 'value1',
               'key2': 'value2',
               'key3': 'value3'}
 
    # poslani HTTP dotazu typu GET
    response = requests.get(URL, headers=headers, cookies=cookies)
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # precteni hlavicek
    headers = response.headers
 
    # pruchod datovou strukturou odeslanou serverem
    encoded = response.json()
    cookies = get_value_using_path(encoded, "cookies")
 
    # test existence klicu
    assert "key1" in cookies
    assert "key2" in cookies
    assert "key3" in cookies
 
    # test hodnot, ktere server detekoval v poslanych cookies
    assert get_value_using_path(encoded, "cookies/key1") == "value1"
    assert get_value_using_path(encoded, "cookies/key2") == "value2"
    assert get_value_using_path(encoded, "cookies/key3") == "value3"
 
    # nyni ziskame cookies z odpovedi a zkontrolujeme jejich obsah
    cookies = response.cookies.get_dict()
 
    # melo by se jednat o prazdny slovnik
    assert not cookies

Tento příklad si samozřejmě opět můžeme spustit nástrojem Pytest:

$ pytest -v 13_cookies_tests.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 1 item
 
13_cookies_tests.py::test_cookies PASSED                                 [100%]
 
============================== 1 passed in 0.49s ===============================

15. Správa sezení s využitím cookies

Cookies se používají kromě dalších věcí i ve chvíli, kdy je zapotřebí si nějakým způsobem zapamatovat stav nějaké sekvence operací, resp. stavu (opět viz předchozí článek). Typickým příkladem je webový obchod, u něhož si samozřejmě musíme pamatovat přihlášeného uživatele, obsah jeho košíku, zda již bylo za zboží zaplaceno atd. (ostatně podobnou technologii používá i Root). Ve chvíli, kdy je klientská část naprogramována s využitím knihovny Requests, je možné celý stav (možná lépe řečeno „sezení“) reprezentovat objektem typu Session. Požadavky na server se pak posílají odlišně – provádějí se nikoli zavoláním request._http_metoda_, ale session._http_metoda_. Jak ovšem takové sezení vytvořit v testech? V tomto případě se nabízí použití speciální funkce setup_module.

Funkce nazvaná setup_module je spuštěná – pokud ovšem existuje – na začátku inicializace modulu s jednotkovým testem. Podobně funkce pojmenovaná teardown_module je spuštěna po dokončení všech jednotkových testů v tomto modulu. Oběma zmíněným funkcím je předán objekt s informacemi o modulu, který je tak možné modifikovat. První varianta příkladu je založena na použití globální proměnné session, což je sice korektní, ovšem nepříliš idiomatické:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
session = None
 
 
def expect_ok_response(response):
    assert response is not None
    assert response.ok
    assert response.status_code == 200
 
 
def expect_content_type(response, content_type):
    # precteni hlavicek
    headers = response.headers
 
    # test existence hlavicky
    assert "content-type" in headers
 
    # kontrola obsahu hlavicky
    assert headers["content-type"] == content_type
 
 
def expect_cookie(response, name, value):
    cookies = session.cookies
    assert name in cookies
    assert cookies[name] == value
 
 
def expect_cookies(response, how_many):
    cookies = session.cookies
    assert len(cookies) == how_many
 
 
def get_value_using_path(obj, path):
    """Get the attribute value using the XMLpath-like path specification.
    Return any attribute stored in the nested object and list hierarchy using
    the 'path' where path consists of:
        keys (selectors)
        indexes (in case of arrays)
    separated by slash, ie. "key1/0/key_x".
    Usage:
    get_value_using_path({"x" : {"y" : "z"}}, "x"))   -> {"y" : "z"}
    get_value_using_path({"x" : {"y" : "z"}}, "x/y")) -> "z"
    get_value_using_path(["x", "y", "z"], "0"))       -> "x"
    get_value_using_path(["x", "y", "z"], "1"))       -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key1/1")) -> "y"
    get_value_using_path({"key1" : ["x", "y", "z"],
                          "key2" : ["a", "b", "c", "d"]}, "key2/1")) -> "b"
    """
    keys = path.split("/")
    for key in keys:
        if key.isdigit():
            obj = obj[int(key)]
        else:
            obj = obj[key]
    return obj
 
 
def set_cookie(session, name, value):
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/cookies/set/{name}/{value}".format(name=name, value=value)
 
    # hlavicka posilana v dotazu
    headers = {'accept': 'application/json'}
 
    # poslani HTTP dotazu typu GET
    return session.get(URL, headers=headers)
 
 
def delete_cookie(session, name):
    # adresa s testovaci REST API sluzbou
    URL = "http://httpbin.org/cookies/delete?{name}=".format(name=name)
 
    # hlavicka posilana v dotazu
    headers = {'accept': 'application/json'}
 
    # poslani HTTP dotazu typu GET
    return session.get(URL, headers=headers)
 
 
def setup_module(module):
    global session
    session = requests.Session()
 
 
def test_set_cookie_1():
    response = set_cookie(session, "foo", "6")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 1)
    expect_cookie(response, "foo", "6")
 
 
def test_set_cookie_2():
    response = set_cookie(session, "bar", "7")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 2)
    expect_cookie(response, "foo", "6")
    expect_cookie(response, "bar", "7")
 
 
def test_set_cookie_3():
    response = set_cookie(session, "foo", "42")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 2)
    expect_cookie(response, "foo", "42")
    expect_cookie(response, "bar", "7")
 
 
def test_delete_cookie_1():
    response = delete_cookie(session, "foo")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 1)
    expect_cookie(response, "bar", "7")
 
 
def test_delete_cookie_2():
    response = delete_cookie(session, "baz")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 1)
    expect_cookie(response, "bar", "7")
 
 
def test_delete_cookie_3():
    response = delete_cookie(session, "bar")
 
    # zakladni test odpovedi
    expect_ok_response(response)
    expect_content_type(response, "application/json")
 
    # test cookies
    expect_cookies(response, 0)

Výsledky:

$ pytest -v 14_session_cookies_tests.py
 
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 6 items
 
14_session_cookies_tests.py::test_set_cookie_1 PASSED                    [ 16%]
14_session_cookies_tests.py::test_set_cookie_2 PASSED                    [ 33%]
14_session_cookies_tests.py::test_set_cookie_3 PASSED                    [ 50%]
14_session_cookies_tests.py::test_delete_cookie_1 PASSED                 [ 66%]
14_session_cookies_tests.py::test_delete_cookie_2 PASSED                 [ 83%]
14_session_cookies_tests.py::test_delete_cookie_3 PASSED                 [100%]
 
============================== 6 passed in 3.03s ===============================
Poznámka: ve skutečnosti je mnohem lepším řešením použít:
def setup_module(module):
    module.session = requests.Session()

16. Kontrola složitějších datových struktur vracených serverem

Mnoho testů REST API navíc vyžaduje provedení minimálně tří, popř. čtyř typů operací:

  1. Kontrola, popř. použití JWT tokenů či podobné technologie. Tímto důležitým tématem se budeme zabývat později.
  2. Validace prakticky libovolně komplikovaných datových struktur vrácených serverem, a to na základě programátorem definovaného schématu.
  3. Načítání (parsování) obsahu reprezentovaného pomocí XML, přístup k jednotlivým prvkům výsledného stromu.
  4. Dtto, ovšem pro obsah (typicky dokument) reprezentovaný v HTML.

Nejdříve se alespoň ve stručnosti zmiňme o validaci datových struktur, tj. většinou struktur reprezentovaných v JSONu, popř. (méně často) v jiném serializačním formátu. Pro programovací jazyk Python existuje několik knihoven, které většinou zajišťují dvě úlohy:

  1. provedení samotné validace (musíme vědět, jak mají data vypadat)
  2. současně strukturu dat dokumentuje, a to rigidním způsobem

Validaci dat je možné využít v mnoha oblastech. Představme si například dokumentovou databázi, složitý konfigurační soubor nebo asi nejlépe klasickou webovou službu, která přijme data ve formátu JSON, převede je knihovní funkcí do nativní datové struktury (typicky do slovníku seznamů či hierarchicky uspořádaných slovníků) a následně provede validaci této struktury, ovšem nikoli programově (testováním jednotlivých atributů), ale na základě deklarativního popisu této struktury. Například můžeme specifikovat, že v atributu nazvaném „price“ by mělo být uloženo nezáporné číslo menší než 100000, v atributu pojmenovaném „valid_from“ musí být uložen řetězec odpovídající skutečnému datu (to už nelze otestovat primitivním regulárním výrazem, ale složitějším predikátem) a v atributu „login“ bude buď nick uživatele nebo bude tento atribut obsahovat null/None (popř. alternativně nebude existovat vůbec).

Pro Python jsou poskytovány mj. i tyto knihovny:

  1. Schemagic
  2. Schema
  3. Voluptuous

Některé příklady použití těchto knihoven si ukážeme příště společně s jednoduchým REST API serverem, který bude poskytovat data v příslušném formátu.

Poznámka: tyto knihovny jsou dostupné i ve formě pluginu pro nástroj Pytest, což je ostatně patrné i na mé konfiguraci:
============================= 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/requests/tests
plugins: print-0.1.3, voluptuous-1.0.2
collecting ... collected 2 items
 
01_basic_usage_test.py::test_get_method_for_existing_endpoint PASSED     [ 50%]
01_basic_usage_test.py::test_get_method_for_missing_endpoint PASSED      [100%]
 
============================== 2 passed in 0.71s ===============================

17. Zpracování XML a HTML v Pythonu s využitím knihovny lxml

Další často prováděnou operací při testování REST API je zpracování (či možná lépe řečeno kontrola) dat reprezentovaných v jazyku XML či HTML. K tomuto účelu může velmi dobře posloužit knihovna lxml, o níž se dnes pro úplnost jen krátce zmíníme. Tato knihovna slouží k načítání (parsování) XML souborů, přístupu k jednotlivým prvkům výsledného stromu, tvorbě a zapisování nových XML a v případě potřeby lze tuto knihovnu použít i pro zpracování HTML stránek. Zajímavé je, že se tato knihovna poměrně dobře hodí i pro práci s nevalidními XML, XML bez schématu atd. – tj. se soubory, které může být obtížné zpracovat v jiných nástrojích – a právě proto se velmi dobře hodí do testů. Vývojářům jsou v případě potřeby k dispozici i další zajímavé technologie, zejména XPath (zjednodušeně: přístup k elementům a jejich atributům přes doménově specifický jazyk) a SAX, tj. možnost zpracovávat XML jako sekvenci elementů, což je přístup mnohem méně náročný na paměť. Navíc se většinou jedná o rychlejší způsob práce s XML.

Knihovnu lxml je možné v případě potřeby použít i pro zpracování HTML stránek. Při zpracovávání HTML stránek se nevyžaduje (a popravdě řečeno ani neočekává) validita stránky, takže se specializovaný parser nazvaný HTMLParser snaží z dodaného zdrojového kódu stránky získat korektní stromovou strukturu, a to i v případě, že autor například neuzavírá značky, nevkládá uzavírací značky ve správném pořadí atd. Podívejme se nyní na jednoduchý příklad zpracovatelné HTML stránky, konkrétně na stránku dostupnou na adrese http://www.zyvra.org/html/simple.htm. Zdrojový kód této stránky vypadá následovně (stylem zápisu trošku připomíná minulé tisíciletí :-):

<html><head><title>
Very simple HTML page.
</title></head>
<body>
 
<p>You can look at the source of this page by: Right clicking anywhere
out in space on this page then selecting "View" in the menu.</p>
<p>This works on any page, but sometimes
what you see may be very complex
and seem confusing.</p>
 
<p>
<b>Please,</b> look at the source and what you see with the browser.
You should understand and see the effect of every tag. Use the little
Icons up in the right of your browser screen to change the size of the
window and see the effect, and how the browser displays this page.</p>
<p align="right">
Yes, this is a <b>Very Plain</b> page. <i>But it works!</i></p>
<p><i><b>Remember. We are just getting started,</b> and I haven't used
anything more than I have talked about in a couple pages!</i>
Yes. You will want to be more fancy.
Just be patient, we'll get there.
 
<p align="center">Now create a page like
this of your own. <b>Have fun!</b></p
 
</body></html>

Povšimněte si například toho, že se ve stránce objevují neuzavřené značky <p>, chybí informace o verzi HTML, o kódování atd. I přesto je možné takovou stránku zpracovat.

Realizace příkladu pro zpracování HTML stránky je poměrně přímočará. Nejprve (přesněji řečeno po importu lxml.etree) vytvoříme instanci parseru HTML stránek:

import lxml.etree as ET
 
parser = ET.HTMLParser()

Dále se pokusíme stránku načíst a ihned poté zparsovat. Povšimněte si, že funkce lxml.etree.parse rozpozná URL a stránku v případě potřeby stáhne (pokud ovšem není dostupná na serveru s HTTPS!):

url = "http://www.zyvra.org/html/simple.htm"
 
tree = ET.parse(url, parser)
 
root = tree.getroot()

Nyní, když máme k dispozici celý strom i kořenový prvek, se můžeme pokusit zpětně zrekonstruovat řetězec se zdrojovým kódem stránky:

print("\n\n\nContent:")
result = ET.tostring(tree.getroot(), pretty_print=True, method="html")
print(result)

Podrobnosti, včetně testů určených pro reálný server, si ukážeme příště.

MIF tip3

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:

# Příklad Stručný popis Cesta
1 01_basic_usage_test.py poslání (HTTP) požadavku s využitím HTTP metody GET (umělý test) https://github.com/tisnik/testing-in-python/tree/master/tests/01_ba­sic_usage_test.py
2 02_check_status_test.py otestování, zda byla odpověď poslána s očekávaným HTTP kódem https://github.com/tisnik/testing-in-python/tree/master/tests/02_chec­k_status_test.py
3 03_refactor_check_status_test.py příprava funkcí pro knihovnu určenou pro testování REST API https://github.com/tisnik/testing-in-python/tree/master/tests/03_re­factor_check_status_test.py
4 04_check_content_type.py kontrola hlavičky nebo hlaviček vrácených serverem v odpovědi https://github.com/tisnik/testing-in-python/tree/master/tests/04_chec­k_content_type.py
5 05_refactor_check_content_type.py samostatná funkce pro otestování typu odpovědi https://github.com/tisnik/testing-in-python/tree/master/tests/05_re­factor_check_content_type­.py
6 06_more_content_types.py test, zda server poslal rastrové obrázky či podobná data https://github.com/tisnik/testing-in-python/tree/master/tests/06_mo­re_content_types.py
7 07_check_payload.py otestování těla odpovědi https://github.com/tisnik/testing-in-python/tree/master/tests/07_chec­k_payload.py
8 08_check_json_content.py zpracování a otestování odpovědi serveru ve formátu JSON https://github.com/tisnik/testing-in-python/tree/master/tests/08_chec­k_json_content.py
9 09_xpath_like_query.py výběr atributů z JSONu s využitím jednoduchého DSL https://github.com/tisnik/testing-in-python/tree/master/tests/09_xpat­h_like_query.py
10 10_post_method_test.py otestování REST API endpointu dostupného přes HTTP metodu POST https://github.com/tisnik/testing-in-python/tree/master/tests/10_pos­t_method_test.py
11 11_post_method_with_payload_test.py zaslání dat na server s využitím metody POST https://github.com/tisnik/testing-in-python/tree/master/tests/11_pos­t_method_with_payload_tes­t.py
12 12_post_method_with_payload_test.py předání dat serveru v těle HTTP požadavku typu POST https://github.com/tisnik/testing-in-python/tree/master/tests/12_pos­t_method_with_payload_tes­t.py
13 13_cookies_tests.py předání cookies v požadavku a test dat vrácených v odpovědi serveru https://github.com/tisnik/testing-in-python/tree/master/tests/13_co­okies_tests.py
14 14_session_cookies_tests.py správa sezení s využitím cookies https://github.com/tisnik/testing-in-python/tree/master/tests/14_ses­sion_cookies_tests.py

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:

  1. 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/
  2. 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/
  3. 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/
  4. 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/
  5. 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/
  6. Struktura projektů s jednotkovými testy, využití Travis CI
    https://www.root.cz/clanky/struktura-projektu-s-jednotkovymi-testy-vyuziti-travis-ci/
  7. Omezení stavového prostoru testovaných funkcí a metod
    https://www.root.cz/clanky/omezeni-stavoveho-prostoru-testovanych-funkci-a-metod/
  8. Testování aplikací s využitím nástroje Hypothesis
    https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis/
  9. Testování aplikací s využitím nástroje Hypothesis (dokončení)
    https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis-dokonceni/
  10. Testování webových aplikací s REST API z Pythonu
    https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu/
  11. 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/
  12. 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/
  13. 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/
  14. 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/
  15. 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/
  16. Validace datových struktur v Pythonu (2. část)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/
  17. Validace datových struktur v Pythonu (dokončení)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/
  18. Univerzální testovací nástroj Robot Framework
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/
  19. Univerzální testovací nástroj Robot Framework a BDD testy
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/
  20. Úvod do problematiky fuzzingu a fuzz testování
    https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/
  21. Ú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/
  22. 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/
  23. 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/
  24. Testování aplikací naprogramovaných v jazyce Go
    https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/
  25. 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/
  26. 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/
  27. 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/
  28. 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/
  29. 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/
  30. 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/
  31. 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/
  32. 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

  1. Requests: HTTP for Humans (dokumentace)
    http://docs.python-requests.org/en/master/
  2. Requests: Introduction
    http://docs.python-requests.org/en/latest/user/intro/
  3. Requests na GitHubu
    https://github.com/requests/requests
  4. Requests (software) na Wikipedii
    https://en.wikipedia.org/wi­ki/Requests_%28software%29
  5. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  6. 20 Python libraries you can’t live without
    https://pythontips.com/2013/07/30/20-python-libraries-you-cant-live-without/
  7. 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
  8. Python: useful modules
    https://wiki.python.org/mo­in/UsefulModules
  9. Top 15 most popular Python libraries
    https://keyua.org/blog/most-popular-python-libraries/
  10. Hypertext Transfer Protocol
    https://en.wikipedia.org/wi­ki/Hypertext_Transfer_Pro­tocol
  11. List of HTTP header fields
    https://en.wikipedia.org/wi­ki/List_of_HTTP_header_fi­elds
  12. List of HTTP status codes
    https://en.wikipedia.org/wi­ki/List_of_HTTP_status_co­des
  13. Python requests deep dive
    https://medium.com/@antho­nypjshaw/python-requests-deep-dive-a0a5c5c1e093
  14. The awesome requests module
    https://www.pythonforbegin­ners.com/requests/the-awesome-requests-module
  15. Send HTTP Requests in Python
    https://code-maven.com/http-requests-in-python
  16. Introducing JSON
    http://json.org/
  17. Writing tests for RESTful APIs in Python using requests – part 1: basic tests
    https://www.ontestautomati­on.com/writing-tests-for-restful-apis-in-python-using-requests-part-1-basic-tests/
  18. Step by Step Rest API Testing using Python + Pytest + Allure
    https://www.udemy.com/course/api-testing-python/
  19. Prime formulas and polynomial functions
    https://en.wikipedia.org/wi­ki/Formula_for_primes#Pri­me_formulas_and_polynomial_fun­ctions
  20. Prime-Generating Polynomial
    https://mathworld.wolfram.com/Prime-GeneratingPolynomial.html
  21. Hoare logic
    https://en.wikipedia.org/wi­ki/Hoare_logic
  22. Goto Fail, Heartbleed, and Unit Testing Culture
    https://martinfowler.com/ar­ticles/testing-culture.html
  23. PEP-484
    https://www.python.org/dev/peps/pep-0484/
  24. In-depth: Functional programming in C++
    https://www.gamasutra.com/vi­ew/news/169296/Indepth_Fun­ctional_programming_in_C.php
  25. mypy
    http://www.mypy-lang.org/
  26. Welcome to Mypy documentation!
    https://mypy.readthedocs.i­o/en/latest/index.html
  27. mypy na GitHubu
    https://github.com/python/mypy
  28. mypy 0.770 na PyPi
    https://pypi.org/project/mypy/
  29. Extensions for mypy (separated out from mypy/extensions)
    https://github.com/python/my­py_extensions
  30. The Mypy Blog
    https://mypy-lang.blogspot.com/2020/03/mypy-0770-released.html
  31. Our journey to type checking 4 million lines of Python
    https://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python
  32. Type-Checking Python Programs With Type Hints and mypy
    https://www.youtube.com/wat­ch?v=2×WhaALHTvU
  33. Refactoring to Immutability – Kevlin Henney
    https://www.youtube.com/wat­ch?v=APUCMSPiNh4
  34. Bernat Gabor – Type hinting (and mypy) – PyCon 2019
    https://www.youtube.com/wat­ch?v=hTrjTAPnA_k
  35. Stanford Seminar – Optional Static Typing for Python
    https://www.youtube.com/wat­ch?v=GiZKuyLKvAA
  36. mypy Getting to Four Million Lines of Typed Python – Michael Sullivan
    https://www.youtube.com/wat­ch?v=FT_WHV4-QcU
  37. Shebang
    https://en.wikipedia.org/wi­ki/Shebang_(Unix)
  38. pytest 5.4.2 na PyPi
    https://pypi.org/project/pytest/
  39. Hillel Wayne – Beyond Unit Tests: Taking Your Testing to the Next Level – PyCon 2018
    https://www.youtube.com/wat­ch?v=MYucYon2-lk
  40. Awesome Python – testing
    https://github.com/vinta/awesome-python#testing
  41. pytest Plugins Compatibility
    http://plugincompat.herokuapp.com/
  42. Selenium (pro Python)
    https://pypi.org/project/selenium/
  43. Getting Started With Testing in Python
    https://realpython.com/python-testing/
  44. unittest.mock — mock object library
    https://docs.python.org/3­.5/library/unittest.mock.html
  45. mock 2.0.0
    https://pypi.python.org/pypi/mock
  46. An Introduction to Mocking in Python
    https://www.toptal.com/python/an-introduction-to-mocking-in-python
  47. Mock – Mocking and Testing Library
    http://mock.readthedocs.io/en/stable/
  48. Python Mocking 101: Fake It Before You Make It
    https://blog.fugue.co/2016–02–11-python-mocking-101.html
  49. Nauč se Python! – Testování
    http://naucse.python.cz/les­sons/intro/testing/
  50. Flexmock (dokumentace)
    https://flexmock.readthedoc­s.io/en/latest/
  51. Test Fixture (Wikipedia)
    https://en.wikipedia.org/wi­ki/Test_fixture
  52. Mock object (Wikipedia)
    https://en.wikipedia.org/wi­ki/Mock_object
  53. Extrémní programování
    https://cs.wikipedia.org/wi­ki/Extr%C3%A9mn%C3%AD_pro­gramov%C3%A1n%C3%AD
  54. Programování řízené testy
    https://cs.wikipedia.org/wi­ki/Programov%C3%A1n%C3%AD_%C5%99%­C3%ADzen%C3%A9_testy
  55. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  56. Tox
    https://tox.readthedocs.io/en/latest/
  57. pytest: helps you write better programs
    https://docs.pytest.org/en/latest/
  58. doctest — Test interactive Python examples
    https://docs.python.org/dev/li­brary/doctest.html#module-doctest
  59. unittest — Unit testing framework
    https://docs.python.org/dev/li­brary/unittest.html
  60. Python namespaces
    https://bytebaker.com/2008/07/30/pyt­hon-namespaces/
  61. Namespaces and Scopes
    https://www.python-course.eu/namespaces.php
  62. Stránka projektu Robot Framework
    https://robotframework.org/
  63. GitHub repositář Robot Frameworku
    https://github.com/robotfra­mework/robotframework
  64. Robot Framework (Wikipedia)
    https://en.wikipedia.org/wi­ki/Robot_Framework
  65. Tutoriál Robot Frameworku
    http://www.robotframeworktu­torial.com/
  66. Robot Framework Documentation
    https://robotframework.or­g/robotframework/
  67. Robot Framework Introduction
    https://blog.testproject.i­o/2016/11/22/robot-framework-introduction/
  68. robotframework 3.1.2 na PyPi
    https://pypi.org/project/ro­botframework/
  69. Robot Framework demo (GitHub)
    https://github.com/robotfra­mework/RobotDemo
  70. Robot Framework web testing demo using SeleniumLibrary
    https://github.com/robotfra­mework/WebDemo
  71. Robot Framework for Mobile Test Automation Demo
    https://www.youtube.com/wat­ch?v=06LsU08slP8
  72. Gherkin
    https://cucumber.io/docs/gherkin/
  73. Selenium
    https://selenium.dev/
  74. SeleniumLibrary
    https://robotframework.org/
  75. The Practical Test Pyramid
    https://martinfowler.com/ar­ticles/practical-test-pyramid.html
  76. Acceptance Tests and the Testing Pyramid
    http://www.blog.acceptance­testdrivendevelopment.com/ac­ceptance-tests-and-the-testing-pyramid/
  77. Tab-separated values
    https://en.wikipedia.org/wiki/Tab-separated_values
  78. A quick guide about Python implementations
    https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321
  79. radamsa
    https://gitlab.com/akihe/radamsa
  80. Fuzzing (Wikipedia)
    https://en.wikipedia.org/wiki/Fuzzing
  81. american fuzzy lop
    http://lcamtuf.coredump.cx/afl/
  82. Fuzzing: the new unit testing
    https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1
  83. Corpus for github.com/dvyukov/go-fuzz examples
    https://github.com/dvyukov/go-fuzz-corpus
  84. AFL – QuickStartGuide.txt
    https://github.com/google/AF­L/blob/master/docs/QuickStar­tGuide.txt
  85. Introduction to Fuzzing in Python with AFL
    https://alexgaynor.net/2015/a­pr/13/introduction-to-fuzzing-in-python-with-afl/
  86. Writing a Simple Fuzzer in Python
    https://jmcph4.github.io/2018/01/19/wri­ting-a-simple-fuzzer-in-python/
  87. How to Fuzz Go Code with go-fuzz (Continuously)
    https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/
  88. Golang Fuzzing: A go-fuzz Tutorial and Example
    http://networkbit.ch/golang-fuzzing/
  89. Fuzzing Python Modules
    https://stackoverflow.com/qu­estions/20749026/fuzzing-python-modules
  90. 0×3 Python Tutorial: Fuzzer
    http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/
  91. fuzzing na PyPi
    https://pypi.org/project/fuzzing/
  92. Fuzzing 0.3.2 documentation
    https://fuzzing.readthedoc­s.io/en/latest/
  93. Randomized testing for Go
    https://github.com/dvyukov/go-fuzz
  94. HTTP/2 fuzzer written in Golang
    https://github.com/c0nrad/http2fuzz
  95. 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
  96. Continuous Fuzzing Made Simple
    https://fuzzit.dev/
  97. Halt and Catch Fire
    https://en.wikipedia.org/wi­ki/Halt_and_Catch_Fire#In­tel_x86
  98. Random testing
    https://en.wikipedia.org/wi­ki/Random_testing
  99. Monkey testing
    https://en.wikipedia.org/wi­ki/Monkey_testing
  100. Fuzzing for Software Security Testing and Quality Assurance, Second Edition
    https://books.google.at/bo­oks?id=tKN5DwAAQBAJ&pg=PR15&lpg=PR15&q=%­22I+settled+on+the+term+fuz­z%22&redir_esc=y&hl=de#v=o­nepage&q=%22I%20settled%20on%20the%20ter­m%20fuzz%22&f=false
  101. libFuzzer – a library for coverage-guided fuzz testing
    https://llvm.org/docs/LibFuzzer.html
  102. fuzzy-swagger na PyPi
    https://pypi.org/project/fuzzy-swagger/
  103. fuzzy-swagger na GitHubu
    https://github.com/namuan/fuzzy-swagger
  104. Fuzz testing tools for Python
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy#Fuzz_Testing_Tools
  105. A curated list of awesome Go frameworks, libraries and software
    https://github.com/avelino/awesome-go
  106. gofuzz: a library for populating go objects with random values
    https://github.com/google/gofuzz
  107. tavor: A generic fuzzing and delta-debugging framework
    https://github.com/zimmski/tavor
  108. hypothesis na GitHubu
    https://github.com/Hypothe­sisWorks/hypothesis
  109. Hypothesis: Test faster, fix more
    https://hypothesis.works/
  110. Hypothesis
    https://hypothesis.works/ar­ticles/intro/
  111. What is Hypothesis?
    https://hypothesis.works/articles/what-is-hypothesis/
  112. What is Property Based Testing?
    https://hypothesis.works/articles/what-is-property-based-testing/
  113. Databáze CVE
    https://www.cvedetails.com/
  114. Fuzz test Python modules with libFuzzer
    https://github.com/eerimoq/pyfuzzer
  115. Taof – The art of fuzzing
    https://sourceforge.net/pro­jects/taof/
  116. JQF + Zest: Coverage-guided semantic fuzzing for Java
    https://github.com/rohanpadhye/jqf
  117. http2fuzz
    https://github.com/c0nrad/http2fuzz
  118. Demystifying hypothesis testing with simple Python examples
    https://towardsdatascience­.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294
  119. Testování
    http://voho.eu/wiki/testovani/
  120. Unit testing (Wikipedia.en)
    https://en.wikipedia.org/wi­ki/Unit_testing
  121. Unit testing (Wikipedia.cz)
    https://cs.wikipedia.org/wi­ki/Unit_testing
  122. Unit Test vs Integration Test
    https://www.youtube.com/wat­ch?v=0GypdsJulKE
  123. TestDouble
    https://martinfowler.com/bli­ki/TestDouble.html
  124. Test Double
    http://xunitpatterns.com/Tes­t%20Double.html
  125. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  126. Acceptance test–driven development
    https://en.wikipedia.org/wi­ki/Acceptance_test%E2%80%93dri­ven_development
  127. Gauge
    https://gauge.org/
  128. Gauge (software)
    https://en.wikipedia.org/wi­ki/Gauge_(software)
  129. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  130. Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst
    https://medium.com/@fistsOf­Reason/testing-is-good-pyramids-are-bad-ice-cream-cones-are-the-worst-ad94b9b2f05f
  131. Články a zprávičky věnující se Pythonu
    https://www.root.cz/n/python/
  132. PythonTestingToolsTaxonomy
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy
  133. Top 6 BEST Python Testing Frameworks [Updated 2020 List]
    https://www.softwaretestin­ghelp.com/python-testing-frameworks/
  134. pytest-print 0.1.3
    https://pypi.org/project/pytest-print/
  135. pytest fixtures: explicit, modular, scalable
    https://docs.pytest.org/en/la­test/fixture.html
  136. PyTest Tutorial: What is, Install, Fixture, Assertions
    https://www.guru99.com/pytest-tutorial.html
  137. Pytest – Fixtures
    https://www.tutorialspoin­t.com/pytest/pytest_fixtu­res.htm
  138. Marking test functions with attributes
    https://docs.pytest.org/en/la­test/mark.html
  139. pytest-print
    https://pytest-print.readthedocs.io/en/latest/
  140. Continuous integration
    https://en.wikipedia.org/wi­ki/Continuous_integration
  141. Travis CI
    https://travis-ci.org/
  142. Mutation testing
    https://en.wikipedia.org/wi­ki/Mutation_testing
  143. Články o Hypothesis
    https://news.ycombinator.com/from?si­te=hypothesis.works
  144. Testovací případ
    https://cs.wikipedia.org/wi­ki/Testovac%C3%AD_p%C5%99%C3%AD­pad
  145. Most testing is ineffective
    https://hypothesis.works/