Hlavní navigace

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

 Autor: Depositphotos
Dnes se zaměříme na složitější dennodenně používané techniky – čtení binárních dat, práci s cookies, využití session atd. Uvidíme, že knihovna většinu operací přímo podporuje, a to tak, aby bylo její využití snadné.
Pavel Tišnovský 9. 7. 2020
Doba čtení: 41 minut

Sdílet

11. Poslání cookies v GET požadavku

12. Objekt typu Session a jeho využití pro uložení stavu mezi dotazy

13. Příklad na použití objektu typu Session

14. Úprava testovacího HTTP serveru takovým způsobem, aby vypisoval hlavičky posílané klientem

15. Nová podoba testovacího HTTP serveru

16. Skript pro volání testovacího HTTP serveru s hlavičkou Set-Cookie

17. Spuštění skriptu, ukázka celého tvaru požadavku a formát odpovědi serveru

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 (2)

V dnešním článku si ukážeme některé další možnosti nabízené knihovnou Requests, s jejímž základním použitím jsme se seznámili v článku předchozím. Nejprve si řekneme, jak lze přečíst binární data poslaná serverem v odpovědi (typicky se bude jednat o rastrové obrázky, audio data, archivy atd.), použijeme HTTP metody pojmenované PATCH, PUT a OPTIONS a dále se seznámíme s konceptem takzvaných cookies a ukážeme si, jakým způsobem je možné využít objekt typu Session, díky němuž je možné s cookies (a obecně se „sezeními“) velmi snadno a rychle pracovat – právě zde se ostatně ukáže snadnost použití knihovny Requests v porovnání s jinými knihovnami a nástroji, které mnohdy nenabízejí všechny operace, popř. je sice nabízejí, ale na odlišné „úrovni abstrakce“ (buď zbytečně nízkoúrovňově, nebo naopak některé možnosti zabalují do abstraktních operací). A session budou užitečné i při psaní testů pro REST API, což si prakticky ukážeme příště.

Poznámka: podobně jako minule se i dnes zaměříme jak na samotnou knihovnu Request, tak i (tam, kde to má význam) i na využití všestranného nástroje curl a v některých případech i starého dobrého telnetu. Příklady psané v Pythonu, které volají funkce knihovny Requests, používají HTTPS, ovšem stejným způsobem budou pracovat i při použití HTTP.

2. Přečtení nezpracovaného těla odpovědi při práci s binárními daty

Při vývoji některých aplikací využívajících REST API, popř. při přímém přístupu k prostředkům (obrázkům, animacím, …) nabízeným na HTTP serverech se někdy setkáme s nutností zpracování binárních dat vrácených serverem s využitím protokolu HTTP nebo HTTPS. V takovém případě je možné (a také vhodné) obejít většinu funkcí nabízených knihovnou Requests a přečíst binární data vlastními prostředky – bajt po bajtu nebo blok po bloku. Ve skutečnosti je to relativně jednoduché. Nejdříve musíme zjistit typ dat (pro jistotu) a taktéž jejich délku, kterou by měl HTTP server poslat v hlavičce (pozor – ne vždy se tak děje korektně!):

# přečtení hlaviček
headers = response.headers
 
# výpis typu internetoveho media
print("Typ dat:", headers.get("content-type"))
 
# výpis delky dat predanych v tele
print("Delka dat:", headers.get("content-length"))

Dále již jen využijeme zjištěnou délku a použijeme metodu nazvanou response.raw.read(délka), která je určena pro postupné přečtení binárních dat z těla odpovědi. Zde pro jednoduchost budeme číst data po bajtu, i když výhodnější by bylo čtení po delších blocích:

# délka dat předaných v těle odpovědi
length = int(headers.get("content-length"))
 
# přečtení těla odpovědi bajt po bajtu
for i in range(length):
    byte = response.raw.read(1)
    print(hex(byte[0]))
Poznámka: metodě read je možné předat požadavek na přečtení delšího bloku, ovšem stále si hlídejte celkovou délku předaných dat. Taktéž je dobré – minimálně v produkčním kódu – hlídat případné výjimky, které mohou nastat. Ostatně si ukážeme, jak lze naprogramovat HTTP server vracející chybné metainformace, popř. zavírající připojení – i tyto stavy je většinou zapotřebí pokrýt testy.

3. První demonstrační příklad – přečtení bloku binárních dat z odpovědi serveru

V dnešním prvním demonstračním příkladu, jehož úplný zdrojový kód je dostupný na GitHubu https://github.com/tisnik/testing-in-python/tree/master/reques­ts/11_get_binary_data.py, využijeme službu dostupnou na adrese https://httpbin.org/bytes/_po­čet_bajtů_, která v těle odpovědi vrátí požadovaný počet bajtů (ty budou mít náhodnou, resp. přesněji řečeno pseudonáhodnou hodnotu!). Konkrétně budeme vyžadovat sto náhodných bajtů, takže požadavek bude směřován na adresu https://httpbin.org/bytes/100:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/bytes/100"
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, stream=True)
 
# přečtení hlaviček
headers = response.headers
 
# výpis typu internetového media
print("Typ dat:", headers.get("content-type"))
 
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
 
length = int(headers.get("content-length"))
 
# přečtení těla odpovědi bajt po bajtu
for i in range(length):
    byte = response.raw.read(1)
    print(hex(byte[0]))

Po spuštění příkladu by měl začátek zpráv vypisovaných na terminál začínat takto:

$ ./11_get_binary_data.py
 
Typ dat: application/octet-stream
Delka dat: 100

Následuje výpis hexadecimálních hodnot stovky bajtů, které byly poslány serverem v odpovědi:

0x20
0xc1
0x34
0xe6
0x6b
0x0
0x35
0x12
0xf4
0xae
0xed
0xc7
0xe9
0xc5
0x86
0xba
0x94
0x1f
0x4e
...
...
...

Podobně můžeme postupovat i z příkazové řádky za použití nástroje curl:

$ curl https://httpbin.org/bytes/100 2>/dev/null | od -t x1
 
0000000 72 9d 36 d3 67 50 f3 6b 49 e9 13 f0 37 94 81 11
0000020 4f 02 dc 28 dd 00 ce 35 07 29 e8 94 42 b7 c6 72
0000040 f7 3b ca 72 68 57 19 01 6f 1a a6 d8 aa 3d ed 03
0000060 7d 23 c8 c7 f3 b7 37 7b 21 a9 8f 60 e2 4e e8 9c
0000100 e0 63 2b b0 4f 0f 4f d9 f4 0b 38 c6 66 43 fa 86
0000120 c9 e4 79 97 3c ce e4 35 b0 1b 3c 9f 0d 87 f8 0b
0000140 e8 25 ce 39
0000144
Poznámka: pro prohlížení binárních dat ve formě hexadecimálních hodnot je použit standardní nástroj od, který již byl na stránkách Rootu popsán.

4. Získání a uložení rastrového obrázku typu image/png a image/jpeg

Ve druhém demonstračním příkladu (https://github.com/tisnik/testing-in-python/tree/master/reques­ts/12_binary_data_png_ima­ge.py) si ukážeme způsob základního zpracování rastrového obrázku typu PNG předaného v binární podobě po poslání HTTP metody GET na adresu https://httpbin.org/image/png. Obrázek je možné uložit do (binárního) souboru po blocích, jejichž délku jsme nastavili na 128 bajtů (lze samozřejmě použít i kratší či naopak delší bloky). Namísto metody response.raw.read() použijeme alternativní přístup přes metodu response.iter_content(), která navíc správně vyřeší i délku posledního bloku (délka tohoto bloku samozřejmě může být menší než 128 bajtů):

with open("test1.png", 'wb') as fout:
    for block in response.iter_content(chunk_size=128):
        fout.write(block)

Úplný zdrojový kód tohoto příkladu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/image/png"
 
# poslání HTTP dotazu typu GET
response = requests.get(URL)
 
# přečtení hlaviček
headers = response.headers
 
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
 
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
 
print(response.raw)
 
with open("test1.png", 'wb') as fout:
    for block in response.iter_content(chunk_size=128):
        fout.write(block)

Dtto, ale za použití nástroje curl:

$ curl -o test1.png https://httpbin.org/image/png
 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  8090  100  8090    0     0   8090      0  0:00:01 --:--:--  0:00:01 10932
 
$ file test1.png
 
test1.png: PNG image data, 100 x 100, 8-bit/color RGB, non-interlaced

Zcela stejným způsobem můžeme přečíst a uložit rastrový obrázek typu JPEG, který je testovací REST API službou vrácen při poslání HTTP metody GET na adresu https://httpbin.org/image/jpeg:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/image/jpeg"
 
# poslání HTTP dotazu typu GET
response = requests.get(URL)
 
# přečtení hlaviček
headers = response.headers
 
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
 
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
 
print(response.raw)
 
with open("test1.jpg", 'wb') as fout:
    for block in response.iter_content(chunk_size=128):
        fout.write(block)

Dtto, ale za použití nástroje curl:

$ curl -o test1.jpeg https://httpbin.org/image/jpeg
 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 35588  100 35588    0     0  35588      0  0:00:01  0:00:01 --:--:-- 31946
 
$ file test1.jpeg
 
test1.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPCM), density 28x28, segment length 16, comment: "Edited by Paul Sherman for WPClipart, Public Domain", baseline, precision 8, 239x178, frames 3

5. Využití hlavičky accept posílané v požadavku serveru pro určení formátu dat

Testovací REST API služba, kterou používáme ve většině dnes popisovaných demonstračních příkladů, nabízí i možnost výběru formátu rastrových dat. Výběr se přitom provádí nastavením hlavičky se jménem accept v požadavku (request) poslaném na server. Na základě obsahu (tedy hodnoty) této hlavičky server připraví data v požadovaném formátu a pošle je (v binární podobě) zpět klientovi. Příslušná adresa, na kterou je nutné požadavek s hlavičkou accept poslat, je https://httpbin.org/image.

Jedním z podporovaných formátů je i „image/png“, takže další demonstrační příklad přečte obrázek ve formátu PNG a uloží ho do souboru „test.png“:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/image"
 
# hlavička posílaná v dotazu
headers = {'accept': 'image/png'}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers)
 
# přečtení hlaviček
headers = response.headers
 
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
 
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
 
print(response.raw)
 
with open("test2.png", 'wb') as fout:
    for block in response.iter_content(chunk_size=128):
        fout.write(block)

Dalším podporovaným formátem dat testovací služby dostupné na adrese https://httpbin.org/image je image/jpeg, který pochopitelně vrací rastrový obrázek uložený ve formátu JPEG:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/image"
 
# hlavička posílaná v dotazu
headers = {'accept': 'image/jpeg'}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers)
 
# přečtení hlaviček
headers = response.headers
 
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
 
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
 
print(response.raw)
 
with open("test2.jpg", 'wb') as fout:
    for block in response.iter_content(chunk_size=128):
        fout.write(block)

Alternativní využití příkazového řádku a nástroje curl vyžaduje použití přepínače -H:

$ curl https://httpbin.org/image
 
{"message": "Client did not request a supported media type.", "accept": ["image/webp", "image/svg+xml", "image/jpeg", "image/png", "image/*"]}
$ curl -H accept:image/jpeg https://httpbin.org/image -o test2.jpeg
 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 35588  100 35588    0     0  35588      0  0:00:01 --:--:--  0:00:01 36613
 
$ file test2.jpeg
 
test2.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPCM), density 28x28, segment length 16, comment: "Edited by Paul Sherman for WPClipart, Public Domain", baseline, precision 8, 239x178, frames 3
$ curl -H accept:image/png https://httpbin.org/image -o test2.png
 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  8090  100  8090    0     0   8090      0  0:00:01 --:--:--  0:00:01  8968
 
$ file test2.png
 
test2.png: PNG image data, 100 x 100, 8-bit/color RGB, non-interlaced

6. CRUD operace a jejich obdoba v HTTP metodách

Již minule jsme si popsali dvě základní HTTP metody nazvané GET a POST. Kromě těchto metod však existují i metody další. Některé z nich (například metoda OPTIONS) hrají spíše pomocnou roli související se samotným síťovým protokolem (popřípadě s CORSem), ovšem metody pojmenované PUT a DELETE a částečně i metoda PATCH hrají důležitou roli v takzvaných CRUD operacích. V tabulce vypsané pod tímto odstavcem jsou vypsány všechny čtyři základní operace typu CRUD neboli Create, Read, Update a Delete používanými při práci s daty uloženými v nějakém perzistentním úložišti, například v databázi umístěné na serveru (je vcelku jedno, o jakou databázi se jedná, zda o relační, dokumentovou atd.). Tyto operace mají své sémantické protějšky ve čtyřech HTTP metodách, které nalezneme ve druhém sloupci:

Operace HTTP metoda
Create POST
Read (Retrieve) GET
Update (Modify) PUT
Delete (Destroy) DELETE
Poznámka: částečný Update zaměřený pouze na modifikaci některých atributů je představován HTTP metodou PATCH, o níž se ve stručnosti zmíníme v sedmé kapitole.

7. Požadavek využívající metodu PATCH

V dalším demonstračním skriptu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/testing-in-python/tree/master/reques­ts/16_patch_method.py, je ukázán základní způsob použití HTTP metody nazvané PATCH. Tato metoda se prozatím nepoužívá příliš často, a to ani v některých REST API službách, ovšem její sémantika předurčuje tuto metodu použít ve chvíli, kdy je nutné modifikovat nějaký již existující zdroj (resource) uložený na serveru (popř. v relační databázi apod.). Pokud by se například jednalo o úpravu jediného atributu, bude požadavek používající metodu PATCH kratší a provedený rychleji, než úplný požadavek založený na metodě PUT (POST má naproti tomu dosti odlišnou sémantiku – slouží k vytvoření nového zdroje):

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/patch"
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
 
# poslání HTTP dotazu typu PATCH
response = requests.patch(URL, headers=headers)
 
# přečtení hlaviček odpovědi
headers = response.headers
 
# výpis všech hlaviček odpovědi
print("Headers:")
 
for header_name, header_value in headers.items():
    print("{:40s} {}".format(header_name, header_value))
 
print("-" * 60)
 
print("Content:")
 
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
 
print(json.dumps(data, indent=4, sort_keys=True))

Ve skriptu je metoda PATCH použita pro přístup na adresu https://httpbin.org/patch sloužící k jejímu základnímu otestování. Odpověď vrácená serverem by měla vypadat přibližně následovně (vypsány jsou napřed hlavičky a poté i tělo odpovědi ve formátu JSON):

Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:28 GMT
Content-Type                             application/json
Content-Length                           373
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "args": {},
    "data": "",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "close",
        "Content-Length": "0",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.13.0"
    },
    "json": null,
    "origin": "213.175.37.10",
    "url": "https://httpbin.org/patch"
}

Využití nástroje curl, napřed bez specifikace metody (použije se GET):

$ curl -v http://httpbin.org/patch
 
*   Trying 3.220.112.94...
* TCP_NODELAY set
* Connected to httpbin.org (3.220.112.94) port 80 (#0)
> GET /patch HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 405 METHOD NOT ALLOWED
< Date: Tue, 07 Jul 2020 17:22:04 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Allow: PATCH, OPTIONS
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
* Connection #0 to host httpbin.org left intact

Explicitní specifikace HTTP metody PATCH:

$ curl -v -X PATCH http://httpbin.org/patch
 
*   Trying 54.236.246.173...
* TCP_NODELAY set
* Connected to httpbin.org (54.236.246.173) port 80 (#0)
> PATCH /patch HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 07 Jul 2020 17:23:15 GMT
< Content-Type: application/json
< Content-Length: 317
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Amzn-Trace-Id": "Root=1-5f04af83-057eaaea85113b26e15ab29a"
  },
  "json": null,
  "origin": "37.48.9.246",
  "url": "http://httpbin.org/patch"
}
* Connection #0 to host httpbin.org left intact

8. Požadavek využívající metodu PUT

Požadavek používající HTTP metodu PUT vypadá velmi podobně jako požadavek POST popsaný minule. Liší se však sémantika (přibližně řečeno logický význam) těchto metod, protože zatímco POST typicky slouží k založení nového zdroje (resource) na serveru, je PUT použit k přepisu již existujících údajů. Rozdíl je v některých případech patrný i při pohledu na URL (adresu), protože u metody PUT by se mělo odkazovat přímo na konkrétní resource, kdežto u metody POST se odkazuje spíše na obecnější funkci (můžeme dokonce říci, že na konstruktor), která nový resource vytvoří a vrátí jeho identifikátor v odpovědi serveru. Podívejme se nyní v rychlosti na způsob použití metody PUT, kde opět použijeme testovací službu https://httpbin.org/, konkrétně URL/adresu https://httpbin.org/put:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/put"
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
 
# posláni HTTP dotazu typu PUT
response = requests.put(URL, headers=headers)
 
# přečtení hlaviček odpovědi
headers = response.headers
 
# výpis všech hlaviček odpovědi
print("Headers:")
 
for header_name, header_value in headers.items():
    print("{:40s} {}".format(header_name, header_value))
 
print("-" * 60)
 
print("Content:")
 
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
 
print(json.dumps(data, indent=4, sort_keys=True))

Po spuštění tohoto skriptu dostaneme následující odpověď (vypsány jsou opět jak hlavičky poslané serverem, tak i tělo odpovědi):

Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:38 GMT
Content-Type                             application/json
Content-Length                           371
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "args": {},
    "data": "",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "close",
        "Content-Length": "0",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.13.0"
    },
    "json": null,
    "origin": "213.175.37.10",
    "url": "https://httpbin.org/put"
}

Přístup ke stejnému zdroji, ovšem nepodporovanou metodou GET:

$ curl -v http://httpbin.org/put
 
*   Trying 3.220.112.94...
* TCP_NODELAY set
* Connected to httpbin.org (3.220.112.94) port 80 (#0)
> GET /put HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 405 METHOD NOT ALLOWED
< Date: Wed, 08 Jul 2020 07:16:41 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Allow: OPTIONS, PUT
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
* Connection #0 to host httpbin.org left intact

Korektní specifikace metody PUT:

$ curl -v -X PUT http://httpbin.org/put
 
*   Trying 3.220.112.94...
* TCP_NODELAY set
* Connected to httpbin.org (3.220.112.94) port 80 (#0)
> PUT /put HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Jul 2020 07:17:47 GMT
< Content-Type: application/json
< Content-Length: 315
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Amzn-Trace-Id": "Root=1-5f05731b-e182f683e9db0d8fc7bd7d15"
  },
  "json": null,
  "origin": "37.48.9.246",
  "url": "http://httpbin.org/put"
}
* Connection #0 to host httpbin.org left intact

9. Požadavek využívající metodu OPTIONS

Poslední HTTP metoda, kterou si v dnešním článku alespoň ve stručnosti popíšeme, se jmenuje OPTIONS. Tato metoda se používá pro zjištění všech operací, které server na dané adrese podporuje (na druhou stranu však nemusí být na straně dotazované služby podporována). Operacemi jsou přitom v tomto kontextu myšleny HTTP metody. V případě, že pošleme serveru HTTP požadavek typu OPTIONS, bude odpověď typicky prázdná (tj. nebude mít žádné tělo), ovšem v hlavičkách odpovědi nalezneme i hlavičku Allow se seznamem podporovaných HTTP metod platných pro danou adresu. Hodnota této hlavičky může vypadat následovně:

Allow   OPTIONS, GET, HEAD

Opět se podívejme na demonstrační příklad, který metodu OPTIONS použije, tentokrát na nám již známou adresu https://httpbin.org/get. Namísto volání requests.get() použijeme funkci requests.options():

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/get"
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
 
# poslání HTTP dotazu typu OPTIONS
response = requests.options(URL, headers=headers)
 
# přečtení hlaviček odpovědi
headers = response.headers
 
# výpis všech hlaviček odpovědi
print("Headers:")
 
for header_name, header_value in headers.items():
    print("{:40s} {}".format(header_name, header_value))
 
print("-" * 60)
 
print("Content:")
 
# výpis těla odpovědi
print("Plain text:")
print("-" * 60)
print(response.text)
print("-" * 60)

Povšimněte si, že tělo odpovědi je skutečně prázdné a všechny (meta)informace tedy musíme získat z hlaviček:

Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 19:31:01 GMT
Content-Type                             text/html; charset=utf-8
Allow                                    OPTIONS, GET, HEAD
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Access-Control-Allow-Methods             GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Max-Age                   3600
Content-Length                           0
Via                                      1.1 vegur
------------------------------------------------------------
Content:
Plain text:
------------------------------------------------------------
 
------------------------------------------------------------

Stejná operace, ovšem používající nástroj curl:

$ curl -X OPTIONS -v http://httpbin.org/get
 
*   Trying 3.220.112.94...
* TCP_NODELAY set
* Connected to httpbin.org (3.220.112.94) port 80 (#0)
> OPTIONS /get HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Jul 2020 08:41:51 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 0
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Allow: HEAD, GET, OPTIONS
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
< Access-Control-Max-Age: 3600
<
* Connection #0 to host httpbin.org left intact
Poznámka: s touto metodou se setkáme příště při psaní testů pro REST API.

10. Základy práce s cookies

Ve druhé části dnešního článku si ukážeme základy práce s takzvanými cookies. Jedná se o technologii používanou webovými servery i klienty založenými na protokolu HTTP. Samotné cookie je (většinou) malé množství dat, které HTTP server pošle klientovi. Klient si může taková data uložit u sebe, a to buď na delší dobu (cookie jsou ukládány do souboru) nebo po dobu trvání jednoho sezení (session). Cookie mohou sloužit skutečně pouze pro identifikaci sezení (session) a potom je jejich obsah krátký – jen jednoznačný identifikátor session (například JSESSIONID u serverových aplikací naprogramovaných v Javě atd.) nebo je možné do cookie uložit i větší množství dat; například obsah nákupního košíku apod. U cookies je možné specifikovat jak dobu jejich platnosti, tak i adresu/adresy, pro něž je cookie platná (cookie je totiž nutné poslat zpět serveru a potřebujeme zamezit takzvanému kradení cookies). Dnes popisovaná knihovna Requests samozřejmě práci s cookies umožňuje a obsahuje i třídu Session, která je využitelná pro udržení kontextu mezi jednotlivými dotazy, které jsou směřovány na HTTP server.

11. Poslání cookies v GET požadavku

Metodu Requests.get() jsme si již vyzkoušeli v předchozím článku, takže víme, že kromě adresy, která je povinná (první vyžadovaný parametr metody), je možné této metodě předat i další nepovinné údaje, z nichž se následně sestaví požadavek. Jedním z těchto nepovinných údajů je i informace o hlavičkách (známe) a taktéž informace o cookies, které klient předává serveru. Tento parametr 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í:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
# adresa s testovací REST API službou
URL = "https://httpbin.org/cookies"
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
 
# příprava cookies
cookies = {'key1': 'value1',
           'key2': 'value2',
           'key3': 'value3'}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
 
# přečtení hlaviček
headers = response.headers
 
print("-" * 60)
 
# výpis všech hlaviček
print("Headers:")
 
for header_name, header_value in headers.items():
    print("{:40s} {}".format(header_name, header_value))
 
print("-" * 60)
 
print("Content:")
 
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
 
print(json.dumps(data, indent=4, sort_keys=True))
 
print("-" * 60)
 
print("Cookies:")
print(response.cookies.get_dict())

Server odpoví následujícím způsobem – cookies, které získal, nám vrátí v těle odpovědi, což je praktické zejména s ohledem na ladění aplikací:

------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:42 GMT
Content-Type                             application/json
Content-Length                           90
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    }
}
------------------------------------------------------------
Cookies:
{}

Předání cookie je pochopitelně možné realizovat i nástrojem curl, jen nesmíme zapomenout na to, že se cookie je ve formě klíč-hodnota:

$ curl -v http://httpbin.org/cookies -b "foo=bar"
 
*   Trying 54.236.246.173...
* TCP_NODELAY set
* Connected to httpbin.org (54.236.246.173) port 80 (#0)
> GET /cookies HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
> Cookie: foo=bar
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Jul 2020 07:33:27 GMT
< Content-Type: application/json
< Content-Length: 40
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
  "cookies": {
    "foo": "bar"
  }
}
* Connection #0 to host httpbin.org left intact

Větší množství cookies:

$ curl -v http://httpbin.org/cookies -b "foo=bar;baz=xyzzy"
 
*   Trying 3.220.112.94...
* TCP_NODELAY set
* Connected to httpbin.org (3.220.112.94) port 80 (#0)
> GET /cookies HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.55.1
> Accept: */*
> Cookie: foo=bar;baz=xyzzy
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Jul 2020 07:33:39 GMT
< Content-Type: application/json
< Content-Length: 61
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
  "cookies": {
    "baz": "xyzzy",
    "foo": "bar"
  }
}
* Connection #0 to host httpbin.org left intact

Nástroj curl dokáže cookies vrácené serverem uložit do souboru:

$ curl curl http://httpbin.org/cookies/set/foo/bar -c session.txt

Obsah souboru session.txt vypadá následovně:

$ cat session.txt 
 
# Netscape HTTP Cookie File
# https://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
 
httpbin.org     FALSE   /       FALSE   0       foo     bar
Poznámka: tento soubor lze použít pro simulaci práce se session.

12. Objekt typu Session a jeho využití pro uložení stavu mezi dotazy

Jak jsme si již řekli v předchozích dvou kapitolách [10] [11], používají se cookies 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. 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_:

session = requests.Session()
URL = "..."
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
 
# poslání HTTP dotazu typu GET
return session.get(URL, headers=headers)
 
# poslání HTTP dotazu typu POST
return session.post(URL, headers=headers)

atd. atd.

Příklad, který objekt typu Session skutečně používá, si ukážeme v navazující kapitole.

13. Příklad na použití objektu typu Session

Další demonstrační příklad je již poněkud komplikovanější, protože v něm využíváme objekt typu Session, který si pamatuje stav požadavků (nebo možná lépe řečeno kontext, v jehož rámci se požadavky posílají a zpracovávají). V tomto příkladu voláme dvojici endpointů testovací HTTP služby https://httpbin.org/ pro nastavení nových cookies, popř. pro jejich odstranění ze sezení (session):

  1. https://httpbin.org/cooki­es/set/{name}/{value} pro nastavení cookie se zadaným jménem a hodnotou
  2. https://httpbin.org/cooki­es/delete?{name} pro vymazání cookie specifikovaného jména

Zdrojový kód tohoto příkladu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
import json
 
 
def set_cookie(session, name, value):
    # adresa s testovací REST API službou
    URL = "https://httpbin.org/cookies/set/{name}/{value}".format(name=name, value=value)
 
    # hlavička posílaná v dotazu
    headers = {'accept': 'application/json'}
 
    # poslani HTTP dotazu typu GET
    return session.get(URL, headers=headers)
 
 
def delete_cookie(session, name):
    # adresa s testovací REST API službou
    URL = "https://httpbin.org/cookies/delete?{name}=".format(name=name)
 
    # hlavička posílaná v dotazu
    headers = {'accept': 'application/json'}
 
    # poslani HTTP dotazu typu GET
    return session.get(URL, headers=headers)
 
 
def print_response(response):
    # přečtení hlaviček
    headers = response.headers
 
    print("-" * 60)
 
    # výpis hlavicek
    print("Headers:")
 
    for header_name, header_value in headers.items():
        print("{:40s} {}".format(header_name, header_value))
 
    print("-" * 60)
 
    print("Content:")
 
    # zpracovani odpovedi, ktera prisla ve formatu JSON
    data = response.text
 
    # zpracovani odpovedi, ktera prisla ve formatu JSON
    data = response.json()
 
    print(json.dumps(data, indent=4, sort_keys=True))
 
    print("-" * 60)
 
 
def print_session_cookies(session):
    cookies = session.cookies
    print("Session cookies:")
 
    for cookie_name, cookie_value in cookies.items():
        print("{:40s} {}".format(cookie_name, cookie_value))
 
    print("-" * 60)
 
 
session = requests.Session()
 
print("*** set cookie 'foo'=6 ***")
response = set_cookie(session, "foo", "6")
print_response(response)
print_session_cookies(session)
print()
 
print("*** set cookie 'bar'=7 ***")
response = set_cookie(session, "bar", "7")
print_response(response)
print_session_cookies(session)
print()
 
print("*** set cookie 'foo'=42 ***")
response = set_cookie(session, "foo", "42")
print_response(response)
print_session_cookies(session)
print()
 
print("*** delete cookie 'foo' ***")
response = delete_cookie(session, "foo")
print_response(response)
print_session_cookies(session)
print()
 
print("*** delete cookie 'baz' ***")
response = delete_cookie(session, "baz")
print_response(response)
print_session_cookies(session)
print()

Podívejme se nyní na výsledky. Jsou poněkud delší, protože ukazují, jak se dá zcela jednoduše měnit stav sezení:

*** set cookie 'foo'=6 ***
------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:57 GMT
Content-Type                             application/json
Content-Length                           38
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "foo": "6"
    }
}
------------------------------------------------------------
Session cookies:
foo                                      6
------------------------------------------------------------
 
*** set cookie 'bar'=7 ***
------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:57 GMT
Content-Type                             application/json
Content-Length                           55
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "bar": "7",
        "foo": "6"
    }
}
------------------------------------------------------------
Session cookies:
bar                                      7
foo                                      6
------------------------------------------------------------
 
*** set cookie 'foo'=42 ***
------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:57 GMT
Content-Type                             application/json
Content-Length                           56
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "bar": "7",
        "foo": "42"
    }
}
------------------------------------------------------------
Session cookies:
bar                                      7
foo                                      42
------------------------------------------------------------
 
*** delete cookie 'foo' ***
------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:57 GMT
Content-Type                             application/json
Content-Length                           38
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "bar": "7"
    }
}
------------------------------------------------------------
Session cookies:
bar                                      7
------------------------------------------------------------
 
*** delete cookie 'baz' ***
------------------------------------------------------------
Headers:
Connection                               keep-alive
Server                                   gunicorn/19.9.0
Date                                     Wed, 08 Aug 2018 15:25:58 GMT
Content-Type                             application/json
Content-Length                           38
Access-Control-Allow-Origin              *
Access-Control-Allow-Credentials         true
Via                                      1.1 vegur
------------------------------------------------------------
Content:
{
    "cookies": {
        "bar": "7"
    }
}
------------------------------------------------------------
Session cookies:
bar                                      7
------------------------------------------------------------

14. Úprava testovacího HTTP serveru takovým způsobem, aby vypisoval hlavičky posílané klientem

Na závěr si ukážeme, v jakém formátu se vlastně informace o cookies přenáší na server a jak je server dokáže zpracovat. Již dopředu si řekněme, že je vše řízeno dvěma hlavičkami se jmény Cookie a Set-Cookie. Abychom si ukázali způsob předávání těchto hlaviček, upravíme náš původní lokální (testovací) HTTP server, jehož první variantu naleznete na adrese https://github.com/tisnik/testing-in-python/tree/master/reques­ts/simple_server.py a upravenou variantu na adrese https://github.com/tisnik/testing-in-python/tree/master/reques­ts/simple_server2.py. Úprava bude spočívat v tom, že server bude vypisovat všechny hlavičky požadavků, a to nezávisle na tom, jaká HTTP metoda byla pro poslání požadavku použita.

15. Nová podoba testovacího HTTP serveru

Podoba zdrojového kódu nové varianty testovacího HTTP serveru je vypsána pod tímto odstavcem. Oproti původní variantě byly přidány pomocné metody send_headers() a print_request_content(). Server bude otestován skriptem popsaným v navazující kapitole:

#!/usr/bin/python3
# vim: set fileencoding=utf-8
 
# Original (slightly buggy) code:
# see https://gist.github.com/bradmontgomery/2219997
 
 
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
 
hostName = ""
hostPort = 8000
 
 
class SimpleServer(BaseHTTPRequestHandler):
 
    def print_uri(self):
        print("URI: {uri}".format(uri=self.path))
 
    def send_headers(self):
        # příprava hlavicky odpovedi
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
 
    def print_request_content(self):
        # přečtení těla HTTP požadavku
        print(self.headers)
        if "Content-Length" in self.headers:
            content_length = int(self.headers['Content-Length'])
            print("content length: {len}".format(len=content_length))
 
            content = self.rfile.read(content_length)
            print("content value:  {content}".format(content=content))
 
    def do_GET(self):
        self.print_uri()
        self.print_request_content()
 
        # odpověď serveru klientovi
        self.send_headers()
        self.wfile.write("*** get ***".encode("utf-8"))
 
    def do_POST(self):
        self.print_uri()
        self.print_request_content()
 
        # odpověď serveru klientovi
        self.send_headers()
        self.wfile.write("*** post ***".encode("utf-8"))
 
 
simpleServer = HTTPServer((hostName, hostPort), SimpleServer)
 
try:
    simpleServer.serve_forever()
except KeyboardInterrupt:
    pass
 
simpleServer.server_close()
Poznámka: tato implementace HTTP serveru je zcela bezstavová, tj. server si nepamatuje ani cookies ani informace o sezení.

16. Skript pro volání testovacího HTTP serveru s hlavičkou Set-Cookie

Podívejme se nyní na jednoduchý skript, který zavolá náš testovací HTTP server. Použije přitom metodu GET (koncovka adresy je libovolná, protože je serverem zcela ignorována) a předá serveru tři cookies:

# příprava cookies
cookies = {'key1': 'value1',
           'key2': 'value2',
           'key3': 'value3'}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)

Navíc se ještě předá přímo v hlavičkách požadavku hlavička pojmenovaná Set-Cookie se jménem a hodnotou cookie, která by se měla zapamatovat (a většinou poslat zpět klientovi):

# hlavička posílaná v dotazu
headers = {'accept': 'application/json',
           'Set-Cookie': "x=y"}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)

Úplný zdrojový kód tohoto skriptu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import requests
 
# adresa s testovací REST API službou
URL = "http://localhost:8000"
 
# hlavička posílaná v dotazu
headers = {'accept': 'application/json',
           'Set-Cookie': "x=y"}
 
# příprava cookies
cookies = {'key1': 'value1',
           'key2': 'value2',
           'key3': 'value3'}
 
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
 
# přečtení hlaviček
headers = response.headers
 
print("-" * 60)
 
# výpis všech hlaviček
print("Headers:")
 
for header_name, header_value in headers.items():
    print("{:40s} {}".format(header_name, header_value))
 
print("-" * 60)
 
print("Content:")
 
# zpracování odpovědi, která přišla ve formátu JSON
data = response.text
 
print(data)
 
print("-" * 60)
 
print("Cookies:")
print(response.cookies.get_dict())

17. Spuštění skriptu, ukázka celého tvaru požadavku a formát odpovědi serveru

Před otestováním skriptu popsaného v předchozí kapitole je nutné spustit náš testovací HTTP server v samostatném terminálu (aby bylo vidět jeho výstup). To se provede jednoduše příkazem (skript se serverem je spustitelný):

$ ./simple_server_2.py

Nyní již můžeme (opět v jiném terminálu) spustit zmíněný skript:

$ ./21_cookies_test_against_local_server.py 

Tento skript by měl poslat požadavek na testovací HTTP server, získat od něj odpověď a následně vypsat přibližně následující údaje (může se samozřejmě lišit čas, konkrétní verze interpretru Pythonu atd.). Povšimněte si, že server podle všech předpokladů nevrátil žádné cookies:

------------------------------------------------------------
Headers:
Server                                   BaseHTTP/0.6 Python/3.6.3
Date                                     Thu, 09 Aug 2018 12:11:39 GMT
Content-type                             text/plain
------------------------------------------------------------
Content:
*** get ***
------------------------------------------------------------
Cookies:
{}

Zajímavější bude pohled na zprávu vypsanou serverem ve chvíli, kdy přijme dotaz od demonstračního skriptu. Povšimněte si především dvou hlaviček nazvaných Cookie a Set-Cookie. V hlavičce Cookie jsou v textové podobě jména a hodnoty všech předaných cookies, v hlavičce Set-Cookie pak přesný opis textu, který jsme předali v požadavku (což je logické, ovšem je patrné, že zde máme velkou volnost zápisu, tj. serveru lze například předat data, která nebude schopen zpracovat):

Cookie: key1=value1; key2=value2; key3=value3
Set-Cookie: x=y
URI: /
Host: localhost:8000
User-Agent: python-requests/2.13.0
Accept-Encoding: gzip, deflate
accept: application/json
Connection: keep-alive
Set-Cookie: x=y
Cookie: key1=value1; key2=value2; key3=value3
 
 
127.0.0.1 - - [09/Aug/2018 14:11:39] "GET / HTTP/1.1" 200 -

Nic nám pochopitelně nebrání komunikovat s lokálně běžícím HTTP serverem z nástroje curl, z webového browseru (což však nemá praktický význam) či z telnetu.

Použití nástroje curl vůči lokálně běžícímu HTTP serveru:

$ curl -v localhost:8000 -b "foo=bar;baz=xyzzy"
 
* Rebuilt URL to: localhost:8000/
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.55.1
> Accept: */*
> Cookie: foo=bar;baz=xyzzy
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.6 Python/3.6.6
< Date: Wed, 08 Jul 2020 09:50:19 GMT
< Content-type: text/plain
<
* Closing connection 0

HTTP server tento požadavek zpracuje následujícím způsobem:

URI: /
Host: localhost:8000
User-Agent: curl/7.55.1
Accept: */*
 
 
127.0.0.1 - - [08/Jul/2020 11:49:57] "GET / HTTP/1.1" 200 -
URI: /
Host: localhost:8000
User-Agent: curl/7.55.1
Accept: */*
Cookie: foo=bar;baz=xyzzy
 
 
127.0.0.1 - - [08/Jul/2020 11:50:19] "GET / HTTP/1.1" 200 -

Použití nástroje telnet poměrně dobře ukazuje, jakým způsobem je strukturován HTTP požadavek (request):

MIF obecny

$ telnet localhost 8000
 
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /
Cookie: foo=bar
 
*** get ***Connection closed by foreign host.
Poznámka: za řádek se specifikací cookies je nutné přidat prázdný řádek (tedy stlačit klávesu Enter dvakrát za sebou).

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 11_get_binary_data.py použití HTTP metody GET pro získání binárních dat https://github.com/tisnik/testing-in-python/tree/master/reques­ts/11_get_binary_data.py
2 12_binary_data_png_image.py přečtení a uložení obrázku typu PNG https://github.com/tisnik/testing-in-python/tree/master/reques­ts/12_binary_data_png_ima­ge.py
3 13_binary_data_jpeg_image.py přečtení a uložení obrázku typu JPEG https://github.com/tisnik/testing-in-python/tree/master/reques­ts/13_binary_data_jpeg_ima­ge.py
4 14_binary_data_by_header_png.py specifikace požadovaného typu/formátu dat v hlavičce https://github.com/tisnik/testing-in-python/tree/master/reques­ts/14_binary_data_by_header_png­.py
5 15_binary_data_by_header_jpeg.py specifikace požadovaného typu/formátu dat v hlavičce https://github.com/tisnik/testing-in-python/tree/master/reques­ts/15_binary_data_by_header_jpeg­.py
6 16_patch_method.py použití HTTP metody PATCH https://github.com/tisnik/testing-in-python/tree/master/reques­ts/16_patch_method.py
7 17_put_method.py použití HTTP metody PUT https://github.com/tisnik/testing-in-python/tree/master/reques­ts/17_put_method.py
8 18_options_method.py použití HTTP metody OPTIONS https://github.com/tisnik/testing-in-python/tree/master/reques­ts/18_options_method.py
9 19_cookies.py poslání cookies společně s požadavkem https://github.com/tisnik/testing-in-python/tree/master/reques­ts/19_cookies.py
10 20_session_cookie.py základní způsoby využití objektu typu Session https://github.com/tisnik/testing-in-python/tree/master/reques­ts/20_session_cookie.py
11 21_cookies_test_against_local_server.py skript, který se má spustit oproti testovacímu HTTP serveru https://github.com/tisnik/testing-in-python/tree/master/reques­ts/21_cookies_test_against_lo­cal_server.py
       
12 simple_server2.py druhá varianta HTTP serveru https://github.com/tisnik/testing-in-python/tree/master/reques­ts/simple_server2.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. 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/
  12. 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/
  13. 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/
  14. 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/
  15. Validace datových struktur v Pythonu (2. část)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/
  16. Validace datových struktur v Pythonu (dokončení)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/
  17. Univerzální testovací nástroj Robot Framework
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/
  18. Univerzální testovací nástroj Robot Framework a BDD testy
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/
  19. Úvod do problematiky fuzzingu a fuzz testování
    https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/
  20. Ú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/
  21. 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/
  22. 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/
  23. Testování aplikací naprogramovaných v jazyce Go
    https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/
  24. 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/
  25. 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/
  26. 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/
  27. 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/
  28. 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/
  29. 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/
  30. 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/
  31. 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/