Hlavní navigace

Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI

Již mnohokrát jsme se na stránkách Roota setkali s tematikou testů a testování aplikací. Tato problematika je tak důležitá a užitečná, že si zaslouží vlastní seriál. Příklady budou sice v Pythonu, ovšem uvedené postupy jsou univerzální.
Pavel Tišnovský
Doba čtení: 34 minut

Sdílet

11. Acceptance test–driven development (ATDD)

12. Použití knihovny unittest.mock (nejenom) při testování

13. Zdrojový soubor s funkcí, kterou budeme nahrazovat mock objektem

14. Test s volanou i s mockovanou funkcí

15. Vytvoření handleru, který se zavolá namísto originální funkce

16. Kombinace handleru s předkonfigurovanou návratovou hodnotou?

17. Obsah následující části seriálu

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. Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI

Testování aplikací, což je téma, do kterého se počítá jak tvorba testů, tak i nástroje a postupy pro jejich spouštění a vyhodnocování, v současnosti tvoří nedílnou součást vývoje aplikací a popř. i součást jejich akceptace zákazníkem. Jedná se o velmi rozsáhlou oblast IT, která se postupně vyvíjela a rozdělovala do několika podoblastí společně s tím, jak se měnil charakter vyvíjených aplikací i jejich celková složitost (původně dávkové úlohy, dále čistě desktopové aplikace s grafickým uživatelským rozhraním, následovaly webové aplikace a webové služby, cloud atd.). S problematikou testů a testování jsme se na stránkách Roota již několikrát setkali (viz odkazy uvedené v devatenácté kapitole), ale možná bude užitečné se na testování podívat i z určitého odstupu – jaké typy testů se používají, proč se vlastně používají, jaké pomocné nástroje máme v dané oblasti k dispozici a jaké jsou případné dobré a naopak špatné praktiky.

Samotný – dnes již samostatný – obor testování v IT se postupně vyvíjel a měnily se i jeho metodiky. U mnoha starších (z dnešního pohledu prastarých) projektů bylo testování prováděno jednorázově, a to na konci vývoje celého systému. Toto mnohdy manuální či jen poloautomatické testování bychom dnes nazvali akceptačními testy. Tento přístup již pro většinu rozsáhlejších projektů není ani praktický ani nejlevnější; dnes je totiž testování úzce svázáno s celým procesem vývoje nového informačního systému a někdy celý vývoj začíná právě vytvořením prvotní sady testů. Důležitější je ovšem průběžné testování v čase vývoje a nasazování aplikace, což nám mj. dává přímou zpětnou vazbu o stavu vyvíjeného systému (pokud jsou pochopitelně testy napsány dobře). V dnes začínajícím (mini)seriálu si nejprve vysvětlíme základní termíny (ty ovšem nejsou plně ustáleny) a postupně budeme proházet „testovací pyramidou“ (viz druhou kapitolu) od její základny až po vrchol.

Poznámka: v tomto seriálu budeme primárně používat programovací jazyk Python, ovšem jednotlivé typy testů, jejich struktura atd. má svůj přímý protějšek v mnoha dalších programovacích jazycích. Mezi přednosti Pythonu v této oblasti patří snadná tvorba mocků v jednotkových testech, rozsáhlá sada nástrojů pro tvorbu prakticky jakýchkoli typů testů (včetně knihovny Robot Framework, Hypothesis) a rozhraní pro Selenium) a v neposlední řadě i relativně snadná dostupnost studijních materiálů.

2. Různé podoby testovací pyramidy

V souvislosti s testy (resp. přesněji řečeno s různými typy testů) se poměrně často můžeme setkat s termínem „testovací pyramida“. O co se vlastně jedná? Jde o rozdělení jednotlivých typů testů do vrstev, přičemž vrstvy na spodních úrovních mají blíže k vlastnímu zdrojovému kódu, zatímco vrstvy na úrovních vyšších již od kódu (a mnohdy i od architektury aplikace) do značné míry abstrahují. Má to pochopitelně svůj velmi dobrý význam, protože jedna úroveň abstrakce nemůže v této oblasti vyhovovat pro všechny oblasti použití. Zajímavé je, že se sice tento termín skutečně používá poměrně často, ale autoři si jednotlivé vrstvy (zejména ty prostřední) rozdělují různým způsobem. Ostatně se můžeme podívat na několik příkladů:

Obrázek 1: Různé podoby testovací pyramidy.

Obrázek 2: Různé podoby testovací pyramidy.

Obrázek 3: Různé podoby testovací pyramidy.

Obrázek 4: Různé podoby testovací pyramidy.

Většina autorů se shodne na tom, že testovací pyramida (ať již jsou její vrstvy blíže k vrcholku pojmenovány různě) tvoří velmi dobrý příklad toho, jak by testy reálné aplikace měly vypadat. Základem pyramidy jsou jednotkové testy a základna by pochopitelně měla být co nejširší. Předností jednotkových testů je jejich rychlé spouštění (může se testovat izolovaně pouze ona jednotka – třída, funkce atd.), stabilita a poměrně snadné a rychlé úpravy. Směrem k vrcholku pyramidy se vrstvy pochopitelně zužují, což v překladu znamená, že by se testům na těchto úrovních mohla (relativně!) věnovat menší pozornost. Testy blíže k vrcholu pyramidy jsou „dražší“, a to jak z hlediska investovaného pracovního času (extrémem jsou manuální testy), tak i času strojového (je nutné sestavit celou aplikaci i se všemi souvisejícími službami atd.).

Poznámka: představenou testovací pyramidu není nutné chápat jako neměnný předpis, jak by testy měly či naopak neměly vypadat; někdy se okolo ní dokonce tvoří zbytečný cargo cult. Následující blogpost je dobrým zamyšlením nad některými limity celé hierarchie Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst.

3. Zmrzlinový kornout

V souvislosti s prioritami, které se mají věnovat jednotlivým typům testů, se někdy setkáme i s termínem „zmrzlinový kornout“, což je struktura připomínající otočenou pyramidu. V této struktuře se málo času/energie/peněz věnuje jednotkovým testům a testům komponent, o to více času (alespoň relativně) se pak věnuje například testům grafického uživatelského rozhraní nebo dokonce manuálním testům. Takto nastavené priority při tvorbě testů jsou ovšem většinou praktikujících autorů považovány za antipattern, ovšem i s takovým postupem (popř. nějakou jeho variantou) se můžeme v praxi setkat.

Obrázek 5: Podoba zmrzlinového kornoutu v porovnání s testovací pyramidou.

Poznámka: ovšem jak již bylo patrné při představování různých variant testovací pyramidy, je nesnadné až nemožné (a hlavně mnohdy nepraktické) pracovat na všech úrovních testů. Mnohdy si velmi dobře vystačíme například pouze se třemi úrovněmi (jednotkové testy, integrační testy, testy UI) – to ovšem záleží na tom, o jakou aplikaci se jedná. Jiné požadavky budou kladeny na utilitu spouštěnou z příkazové řádky v porovnání s desktopovou aplikací s plnohodnotným GUI, popř. dokonce s webovou aplikací založenou na využití velkého množství mikroslužeb.

4. Jednotkové testy – základ pyramidy, špička kornoutu

První fáze testování většinou probíhá na úrovni balíčků, tříd, objektů, metod a funkcí. Jedná se o takzvané jednotkové testy (unit tests), přičemž onou jednotkou bývá jedna funkce, metoda, celá třída nebo (méně často) celý balíček. Tyto testy se v Pythonu tvoří relativně snadno a typicky se vytváří souběžně s programovým kódem nebo (při TDD) dokonce ještě před zahájením prací na programovém kódu. Výhodou jednotkových testů je jejich specifičnost, možnost selektivního spouštění testů pouze pro zvolenou jednotku i snadné plánovaní prací (předpokládá se, že jednotkové testy jsou součástí zdrojových kódů). Vývoj současně s jednotkovými testy může pomoci s vytvářením kvalitnějšího kódu (typicky při omezování stavového prostoru aplikace, globálních proměnných atd.). Ovšem při tvorbě jednotkových testů je nutné počítat i s některými riziky, zejména pak:

  1. Pokud jednotkové testy vytváří samotný autor testovaného zdrojového kódu, je pravděpodobné, že v testech nemusí pokrýt všechny možné případy, které tak nebudou ani správně odhaleny ve zdrojovém kódu.
  2. Velké množství procházejících testů může vést k tomu, že se budou zbývající padající testy ignorovat.
  3. Testy na sobě mohou záviset, což je obecně špatná architektura (testy by mělo být možné spouštět v náhodném pořadí, popř. je filtrovat).
  4. Změna architektury aplikace může znamenat nutnost přepisu velkého množství jednotkových testů.

V jednotkových testech je nutné některé (zrovna netestované) bloky nahradit za jednodušší objekty nazývané test double. Ty se dále rozdělují do několika kategorií: fake, stub, mock, spy atd. (viz též navazující kapitoly, zejména dvanáctou kapitolu).

Příklad primitivního jednotkového testu:

import pytest
 
def test_the_answer():
      x = 6
      y = 7
      assert x*y == 42, "test failed"

Příklad testu, který zjišťuje, zda testovaná funkce vyhodila výjimku či nikoli:

import pytest
 
def raise_expection():
    raise Exception("Diamonds are forewer")
 
def test_exception(self):
    with pytest.raises(Exception):
        ...
        ...
        ...
 
    self.assertTrue('This is broken' in context.exception)

Pro programovací jazyk Python v současnosti existuje téměř nepřeberné množství knihoven, frameworků a rozšíření standardních jednotkových testů. Jmenujme například:

# Knihovna/framework
1 unittest
2 doctest
3 pytest
4 nose
5 testify
6 Trial
7 Twisted
8 subunit
9 testresources
10 reahl.tofu
11 unit testing
12 testtools
13 Sancho
14 zope.testing
15 pry
16 pythoscope
17 testlib
18 pytest
19 dutest
Poznámka: v dalších článcích se s některými výše uvedenými balíčky a nástroji setkáme.

5. Testy komponent a integrační testy

Jak jsme již mohli vidět ve druhé kapitole, tvoří jednotkové testy jen jedinou vrstvu (základnu) takzvané „testovací pyramidy“. Nad nimi se v dalších vrstvách nachází testy komponent, funkční testy, integrační testy, různé formy benchmarků, BDD testy, testy grafického uživatelského rozhraní, manuálně prováděné testy atd. Všechny tyto testy jsou potenciálně důležité, protože samotné jednotkové testy zdaleka nedokáží odhalit všechny možné chyby:

Obrázek 6: Jednotkové testy nedokáží odhalit problémy na vyšších úrovních abstrakce, například problematické sestavení jednotlivých modulů do vyšších celků.

A právě z tohoto důvodu jsou nad samotnými jednotkovými testy postaveny testy komponent a integrační testy. Ty se snaží zjistit, zda je možné menší (a ideálně velmi dobře izolovaně otestované) jednotky (units) sestavit do většího a přitom stále funkčního celku. V některých případech se rozlišuje mezi vnitřní a vnější integrací (testy komponent patří do první kategorie). Testuje se například komunikace mezi jednotlivými moduly, ze kterých se výsledná aplikace skládá. Podobně jako se u jednotkových testů používají mock objekty (resp. obecněji test double), lze u integračních testů používat takzvané fake moduly a mock moduly. Poměrně názorným příkladem takového modulu může být zjednodušená forma databáze uložená v operační paměti, která pro účely testů nahrazuje reálnou databázi (v paměti lze velmi snadno provozovat například SQLite). Relativně snadno se nahrazují i například moduly pro logování apod. Taktéž se zde setkáme s takzvanými špiony (spies), což jsou moduly zaznamenávající všechny požadavky přicházející z jiných modulů.

6. Systémové testy, akceptační testy

U úroveň výše v testovací pyramidě nacházíme celou skupinu testů, zejména pak systémové testy a testy akceptační. Systémové testy se většinou rozdělují do dalších podkategorií. Zejména se často používají takzvané smoke testy, které pouze velmi rychle zjišťují, zda je zajištěna alespoň minimální míra funkčnosti aplikace předtím, než se spustí složitější a časově mnohem náročnější testy. Pokud smoke testy zhavarují, vrací se aplikace zpět k vývojářům a popř. k devops týmu :-)

Poznámka: název smoke test pochází z jiných oborů, v nichž se například kontroluje těsnost potrubí ještě před jeho reálným použitím tím, že se použije nějaký plyn, který v běžném vzduchu vytvoří kouř (nebo se přímo použije kouř). Poté je již snadné pomocí více smyslů zjistit případné netěsnosti, a to ještě předtím, než se potrubí použije v reálném provozu (například s nějakou mnohem nebezpečnější látkou).

Po úspěšném provedení smoke testů se mohou spouštět systémové testy, jejichž primárním účelem je ověření, jestli aplikace (služba) sestavená do jednoho celku pracuje korektně. Tyto testy se zaměřují jak na zjištění, zda pro určité vstupy aplikace produkuje určitý výstup (například objedná letenku), ale i to, jak reaguje na nestandardní situace, špatně zadané vstupy, pokusy o průnik do aplikace apod. Tvorbou těchto testů již může být pověřen samostatný tým (na rozdíl od testů jednotkových a popř. testů komponent, což je většinou starostí vývojového týmu).

Testy akceptační jsou ještě zajímavější, protože se na jejich vytváření může podílet i zákazník. Jak již název těchto testů napovídá, je jejich úspěšné provedení (většinou) vyžadováno proto, aby byla akceptována funkčnost aplikace. Pro akceptační testy lze v Pythonu použít různé knihovny a frameworky, například dále zmíněnou knihovnu Behave nebo – a to pravděpodobně častěji – nástroj nazvaný Robot Framework. V tomto nástroji lze testy zapisovat formou tabulek, například následujícím způsobem (nejedná se o jediný možný způsob):

| *** Settings ***   |                  |   |
| Library            | Test16.py        |   |
|                    |                  |   |
| *** Test Cases *** |                  |   |
| Adder #1           |                  |   |
|                    | Add              | 1 | 2
|                    | Result should be | 3 |
|                    |                  |   |
| Adder #2           |                  |   |
|                    | Add              | 0 | 0
|                    | Result should be | 0 |
|                    |                  |   |
| Adder #3           |                  |   |
|                    | Add              | 1 | -1
|                    | Result should be | 0 |

Ve webovém prohlížeči si můžeme prohlédnout vygenerované soubory s výsledky:

*

Obrázek 7: Přehled s výsledky testovacího scénáře. Zelená barva naznačuje, že všechny testy skončily korektně.

Alternativně:

| *** Settings ***   |                  |   |
| Library            | Test16.py        |   |
 
| *** Test Cases *** |                  |   |
| Adder #1           |                  |   |
|                    | Add              | 1 | 2
|                    | Result should be | 3 |
|                    | Add              | 2 | 3
|                    | Result should be | 5 |
|                    | Add              | 4 | 5
|                    | Result should be | 9 |
 
| Adder #2           |                  |   |
|                    | Add              | 0 | 0
|                    | Result should be | 0 |
 
| Adder #3           |                  |   |
|                    | Add              | 1 | -1
|                    | Result should be | 0 |
*

Obrázek 8: Podrobnější pohled na jednotlivé kroky testu.

Jiný způsob zápisu testu, opět v tabulkové podobě:

| *** Settings ***   |                             |             |             |
| Library            | Accumulator6.py             |             |             |
| Test template      | Accumulate                  |             |             |
| Test setup         | Setup method                | 0           |             |
| Test teardown      | Teardown method             |             |             |
|                    |                             |             |             |
| *** Test Cases *** | Value                       | Expected    |             |
| Test1              | 0                           | 0           |             |
|                    | 1                           | 1           |             |
|                    | 10                          | 11          |             |
|                    | -10                         | 1           |             |
|                    | 1                           | 2           |             |
|                    | 1                           | 3           |             |
|                    |                             |             |             |
| *** Keywords ***   |                             |             |             |
| Accumulate         |                             |             |             |
|                    | [Arguments]                 | ${value}    | ${expected} |
|                    | Add value                   | ${value}    |             |
|                    | Accumulator value should be | ${expected} |             |
*

Obrázek 9: Podrobnější pohled na jednotlivé kroky testu, informace z logovacího souboru.

Poznámka: pokud se s využitím Robot Frameworku tvoří akceptační testy, tak může být právě tabulková forma vhodná, protože se vlastně jedná o úřední dokument mnohdy zpracovávaný lidmi, kteří mají tabulky v oblibě :-)
*

Obrázek 10: Přehled s výsledky testovacího scénáře. Červená barva naznačuje pád alespoň jednoho testu.

7. Testy aplikačního (programového) rozhraní

Dalším typem testů jsou testy aplikačního programového rozhraní, dnes typicky (ale nejenom) REST API. Tyto testy mohou být vyvinuty a spouštěny samostatně (například vůči jedné izolované komponentě), nebo mohou být součástí end-to-end testů zmíněných dále. Součástí API testů bývají i kontroly autentizace a autorizace, stejně jako kontroly, jak API reaguje na pokusy o průnik, popř. „pouze“ na vadná data. Jak jsme si již řekli na začátku tohoto odstavce, dnes se velmi často používá REST API, takže testy pro ně lze vytvořit různými způsoby (například automaticky ze specifikace OpenAPI), ovšem používají se i další protokoly (MQTT, SOAP, RMI, SMTP atd.). Pro některé typy webových aplikací (resp. přesněji řečeno jejich REST API) mi vyhovovala kombinace knihovny Behave se známou knihovnou requests, ovšem existují i mnohé další více či méně komplexní nástroje.

Dnes můžeme sledovat poměrně rychlou adaptaci fuzzy systémů (fuzzerů) při testování webových služeb či celých webových aplikací. Je to ostatně logické, zejména když si uvědomíme, že právě webové služby a aplikace poskytují svá rozhraní mnohdy všem uživatelům Internetu a tedy i mnoha potenciálním útočníkům. Snaha o co nejlepší zabezpečení je tedy v této oblasti IT zcela pochopitelná. Testovat je možné například REST API. V tomto případě mohou fuzzery použít popis API (OpenAPI/Swagger atd.) a na základě něho začít generovat různé potenciálně problematické vstupy se snahou o obejití vnitřních kontrolních mechanismů aplikace či služby. Některé nástroje, například https://github.com/dubzzz/fuzz-rest-api/, se navíc snaží o různé specifické typy útoků, například do dat přidávají řetězce s příklady SQL Injection apod. Dále lze pochopitelně posílat pseudonáhodná data v tělech požadavků, měnit parametry URL i hlavičky požadavků.

Příklady nástrojů určených pro Python:

# Nástroj
1 Hypothesis
2 Pester
3 Peach Fuzzer Framework
4 antiparser
5 Fusil (Fusil the fuzzer)
6 PyFuzzer
Poznámka: tyto nástroje lze použít i pro jiné účely, než pro „pouhé“ testování aplikačního rozhraní.

8. Testy grafického uživatelského rozhraní a end-to-end testy

Následují relativně vysokoúrovňové testy, zejména testy grafického uživatelského rozhraní, kterým bude později věnováno hned několik článků v tomto seriálu. Mnohé dnešní aplikace jsou založeny na webovém prohlížeči (ať již klasickém, či „schovaném“ ve frameworcích typu Electron), takže existuje hned několik nástrojů pro Python, které umožňují testování GUI takových aplikací. Většinou se setkáme se dvěma způsoby testování:

  1. Simulace chování webového prohlížeče, typicky na úrovni protokolu HTTP a parsování HTML, které posílá aplikace prohlížeči. Mnohdy jsou tyto testy zredukovány na testy REST API. Sem spadají nástroje jako webunit, zope.testbrowser, webtest nebo FunkLoad.
  2. Automatizace přímo v rámci webového prohlížeče, kdy testy (resp. framework pod nimi) přímo používá možnosti webového prohlížeče. V této kategorii je pravděpodobně nejznámějším nástrojem Selenium, které obsahuje podporu i pro Python. Existují však i další nástroje, například Windmill či Splinter.

Na ještě vyšší úrovni, prakticky na vrcholku pyramidy, jsou end-to-end testy. Ty slouží k otestování aplikace jako celku, od začátku do konce. Může se například provádět následující testování:

  1. Přihlášení do aplikace
  2. Zahájení konkrétní operace
  3. Vytvoření reportu
  4. Poslání tohoto reportu na tiskárnu
  5. Odhlášení z aplikace

9. Testy chování (BDD)

Velmi důležité jsou testy chování (behaviour-driven), protože ty nám mohou odhalit nelogičnosti ve zdánlivě funkčním systému (například se očekává neintuitivní ovládání). Tyto typy testů do určité míry kombinují přístupy TDD a ATDD (viz následující dvě kapitoly); používají se zde nástroje Behave či Robot Framework. Zajímavé je, že se v této oblasti můžeme setkat s určitou standardizací (procházející dokonce přes více programovacích jazyků), kterou představuje doménově specifický jazyk (DSL) nazvaný Gherkin.

Ve dvou článcích [1] [2] o programovacím jazyku Clojure jsme se věnovali popisu integrace výše zmíněného doménově specifického jazyka Gherkin určeného pro popis testovacích scénářů přímo v programovacím jazyku Clojure. Ve skutečnosti ovšem není Gherkin v žádném případě určen pouze pro použití společně s Clojure, ale jedná se o DSL integrovatelný i do mnoha dalších programovacích jazyků. Dnes si ve stručnosti představíme knihovnu Behave, s jejíž pomocí se Gherkin integruje do jazyka Python. Ve skutečnosti se bude jednat o téměř ideální spojení, protože Gherkin i Python používají podobný způsob zápisu, v němž i odsazení jednotlivých programových řádků je součástí syntaxe (naproti tomu se Gherkin a Clojure ze syntaktického hlediska zcela odlišují).

Jazyk Gherkin je navržen takovým způsobem, aby ho uživatelé (nemusí se totiž nutně jednat pouze o programátory) mohli začít používat prakticky okamžitě, tj. bez nutnosti studia sáhodlouhých manuálů.

Tento doménově specifický jazyk odstiňuje autora testů od vlastní implementace systému i od programovacího jazyka (či jazyků), v nichž je systém vytvořen. Ostatně v Gherkinu lze popsat očekávané chování prakticky jakéhokoli systému, který dokonce nemusí mít nic společného s IT.

Obrázek 11: Ukázka scénářů napsaných v doménově specifickém jazyce Gherkin.

Na předchozím screenshotu jsou zvýrazněna klíčová slova uvozující jednotlivé kroky testu. Ostatní slova a číslice ve větách jsou buď pevně daná (svázaná s konkrétním krokem), nebo se jedná o proměnné. Ve scénáři je zapsána i tabulka, jejíž obsah se řádek po řádku postupně stává obsahem jednotlivých kroků testu (obsahem tabulky se nahrazují slova umístěná do ostrých závorek).

Poznámka: doménově specifický jazyk Gherkin existuje v různých jazykových mutacích, my se však budeme držet jeho originální anglické varianty. Ostatně mnoho knihoven, které Gherkin podporují, pracuje pouze s anglickou variantou.

Druhým důležitým souborem je vlastní testovací scénář nazvaný adder.feature, který je (př použití knihovny Behave) typicky uložen v podadresáři features. Tento testovací scénář je naprogramován v jazyku Gherkin, konkrétně v jeho výchozí anglické „mutaci“. V následujícím výpisu jsou klíčová slova rozeznávaná interpretrem označena tučně:

Feature: Adder test
 
  Scenario: Check the function add()
    Given The function add is callable
    When I call function add with arguments 1 and 2
    Then I should get 3 as a result
Poznámka: povšimněte si, že jazyk Gherkin je z hlediska sémantiky velmi jednoduchý jazyk. Obsahuje deklarace jednotlivých testovacích scénářů, počáteční podmínku a potom sérii kroků ve stylu „když udělám X, stane se (očekávám) Y“. Syntax je také jednoduchá (alespoň v základních testovacích scénářích), protože první slovo je klíčové a jednotlivé bloky (zde scénáře) jsou odsazeny, podobně jako v samotném Pythonu či ve formátu YAML.

Tento přístup je možné napodobit i při použití Robot Frameworku, v němž může zápis testu vypadat například následovně:

*** Settings ***
Library             Accumulator6.py
Test setup          Setup method  0
Test teardown       Teardown method
Test template       Accumulator operation
 
*** Keywords ***
Accumulator operation
                    [Arguments]  ${value}  ${expected}
                    Given accumulator has been zeroed
                    When I add ${value} to accumulator
                    Then the accumulated value Should Be ${expected}
 
*** Test Cases ***     Value  Expected
Accumulator operation  1      1
Accumulator operation  10     10
 
 
 
*** Keywords ***
Accumulator has been zeroed
    log  accumulator init
 
I add ${value} to accumulator
    Add value  ${value}
 
Then the accumulated value should be ${expected}
    Accumulator value should be  ${expected}

10. Test-driven development (TDD)

Zkratkou TDD se označuje poměrně populární metodika vývoje softwaru, v němž jsou jednotlivé kroky (typicky malé operace) definovány s využitím testů, a to většinou jednotkových testů, ovšem může se jednat i o testy komponent či dokonce o testy UI (to je ovšem méně flexibilní). Nejprve jsou napsány tyto testy, které pochopitelně musí zhavarovat, protože vyžadovaná funkcionalita neexistuje. Posléze vývojáři postupně doplní programový kód, který se neustále testuje – tento proces je nedílnou součástí celého vývoje a provádí se prakticky neustále, a to pochopitelně i před a po refaktoringu. Touto metodikou se opět budeme podrobněji zabývat v samostatném článku.

11. Acceptance test–driven development (ATDD)

Podobně znějící zkratka ATDD znamená Acceptance test–driven development. Opět se jedná o metodiku vývoje softwaru, tentokrát ovšem postavenou na poměrně úzké spolupráci mezi vývojáři, zákazníky a testery (což bývá velmi problematické :-). Využívá se zde například BDD a cílem je získat přesnější informace o požadavcích zákazníka ještě předtím, než se začne ztrácet drahý čas vývojem – mnoho problémů by se mělo objevit již při psaní akceptačních testů, popř. BDD. Teoreticky by se mělo jednat o ideální postup, který ovšem nemusí být některou z participujících stran dobře pochopen.

12. Použití knihovny unittest.mock (nejenom) při testování

Při testování aplikací, zejména při psaní jednotkových testů (což je, jak již ostatně víme, základ testovací pyramidy), se poměrně často dostaneme do situace, kdy potřebujeme nahradit nějakou funkci, metodu nebo dokonce celou třídu používanou v reálné aplikaci za „falešnou“ funkci/metodu/třídu vytvořenou pouze pro účely jednotkových testů. V programovacím jazyku Python je možné pro tvorbu a použití takových „falešných“ bloků kódu použít hned několik různých knihoven, které se od sebe odlišují jak svými možnostmi, tak i způsobem zápisu či deklarace očekávaného chování testované aplikace. Určitým standardem v této oblasti je v současnosti knihovna unittest.mock. Dnes si ukážeme některé základní techniky, které nám tato knihovna poskytuje.

S následující situací se již setkal pravděpodobně každý vývojář vytvářející jednotkové testy – je nutné otestovat funkcionalitu části aplikace, v této části se však volá nějaká funkce nebo metoda provádějící potenciálně destruktivní činnost (změna filesystému, vzdálené volání procedur, programování zařízení připojeného přes USB atd.). Popř. se volá funkce/metoda, která v závislosti na různých okolnostech vrací (minimálně z pohledu testů) pseudonáhodná data. Takovou funkci/metodu by bylo vhodné pro účely testování nahradit jednodušším kódem, jenž bude provádět předem známou činnost, například bude za každých okolností pouze vracet určitou hodnotu. Taková náhrada skutečných funkcí či metod za funkce/metody „falešné“ se (poněkud nepřesně) nazývá „mockování“ (mocking), a příslušný náhradní kód pak test double, popř. podle funkce fake, stub, spy nebo mock. V druhé části dnešního článku si ukážeme, jakým způsobem se může tato technika použít v Pythonu, konkrétně v Pythonu řady 3.x.

V současnosti existuje relativně velké množství různých knihoven, které mockování v Pythonu umožňují. Z nich jmenujme například velmi zajímavý projekt Flexmock, který naleznete na adrese https://pypi.python.org/py­pi/flexmock. Ovšem v Pythonu 3.x se standardem v této oblasti stala knihovna nazvaná unittest.mock. V případě, že ještě z nějakého důvodu musíte používat Python 2.x, použijte namísto knihovny unittest.mock knihovnu nazvanou jednoduše mock. Tato knihovna nabízí prakticky stejné možnosti jako unittest.mock (je ostatně založena na stejném kódu, který pouze byl pro potřeby Pythonu 2.x upraven), ovšem lze ji použít jak v Pythonu 2.x, tak i v Pythonu 3.x, a to bez toho, abyste museli upravovat zdrojové kódy vašich testů (samozřejmě za předpokladu, že se v nich nevyskytují konstrukce, které nejsou v Pythonu 2.x podporovány). Další knihovny, které stojí za zmínku a které jsou potenciálně užitečné, jsou zmíněny v další tabulce:

# Nástroj
1 Ludibrio
2 Python Mock
3 PyMock
4 mock
5 pMock
6 minimock
7 svnmock
8 Mocker
9 Stubble
10 Mox
11 MockTest
12 Fudge
13 Mockito for Python
14 CaptureMock
15 flexmock
16 doublex
17 aspectlib

Vraťme se však k základům jednotkových testů a mockování. Zmíněný falešný blok kódu je možné podle jeho vlastností rozdělit do několika kategorií:

  1. fake – vrací jedinou programátorem zvolenou hodnotu. Příkladem může být funkce nahrazující čtení z databáze, která vždy vrátí jediný záznam.
  2. stub – již obsahuje jednoduchou logiku, například dokáže reagovat na špatný vstup podobně, jako nahrazovaný blok.
  3. spy – dokáže zaznamenat předávané parametry či dokonce celý stav (nebo podstav) aplikace.
  4. mock – mnohdy se jedná o blok s vlastnostmi, které se přibližují reálnému (nahrazovanému) kódu. Vylepšená verze stub.
Poznámka: samotná knihovna unittest.mock je obecná, takže mezi výše zmíněnými kategoriemi nijak nerozlišuje (a mnozí programátoři také ne). I my budeme při popisech možností této knihovny mluvit o „mock objektech“, ale až na základě jejich konkrétní implementace se rozliší, zda se jedná o blok vracející jednu hodnotu, zda bude obsahovat nějakou logiku, logování parametrů/stavů atd.

13. Zdrojový soubor s funkcí, kterou budeme nahrazovat mock objektem

Popis možností knihovny unittest.mock začneme na tom nejjednodušším možném příkladu. Bude se jednat o aplikaci (či spíše minimalistickou „aplikaci“) tvořenou pouhými dvěma zdrojovými soubory application.py a main.py. První zmíněný soubor obsahuje jedinou funkci pojmenovanou function1, která po svém zavolání nejprve vypíše na standardní výstup text „function1 called“ a následně vrátí do volajícího kódu řetězec s obsahem „tested function“, jenž může být v případě potřeby dále zpracován. Celý soubor se zdrojovým kódem má tedy pouze několik řádků (funkce je volána ze skriptu main.py):

"""Implementace logiky aplikace, kterou budeme testovat."""
 
 
def function1():
    """Funkce, kterou v testech nahradíme mockem."""
    print("function1 called")
    return "tested function"

Zavolání této aplikace je snadné a provede se, jak již víme, přes skript main.py:

#!/usr/bin/env python3
 
"""Vstupní bod do testované aplikace."""
 
from application import *
 
 
if __name__ == '__main__':
    # pouze zavoláme funkci, která se bude v testech mockovat
    print(function1())

Následovně:

$ python3 main.py
Poznámka: jedná se o zcela umělý příklad, který má jen jedinou přednost – je skutečně minimalistický, takže ukázka možností knihovny unit.mock pravděpodobně bude poměrně názorná. S reálnějšími příklady se seznámíme příště.

14. Test s volanou i s mockovanou funkcí

Nyní se podívejme na to, jakým způsobem se může funkce nazvaná function1 volat a testovat v jednotkových testech. Pro jednoduchost prozatím nepoužijeme žádný framework určený pro psaní jednotkových testů (to bude téma pro samostatný článek), ale vytvoříme si jednoduchý pomocný soubor nazvaný test.py, v němž se pokusíme zavolat jak původní funkci, tak i její tzv. mock („falešnou“ variantu původní funkce). Na začátku je nutné provést import modulu unittest.mock a samozřejmě taktéž import testovaného modulu application:

from unittest.mock import *
 
import application

První pseudotest bude jednoduchý – pouze v něm zavoláme původní funkci a vypíšeme hodnotu, kterou tato funkce vrátí volajícímu kódu (zde se žádné mockování neprování):

def test1():
    print(application.function1())
Poznámka: právě na tomto místě by měly následovat podmínky vyhodnocující návratovou hodnotu funkce, to, zda došlo k vyhození výjimky atd.

Druhý pseudotest je již mnohem zajímavější, protože v něm namísto původní funkce function1 z modulu application použijeme mock. Deklarace testovací funkce je doplněna o anotaci @patch, v níž specifikujeme jméno mockované funkce (ve formě řetězce, jehož obsah je kontrolován) a současně i návratovou hodnotu. To je nutné, protože se původní funkce ve skutečnosti vůbec nezavolá, ale návratovou hodnotu použijeme ve funkci print:

@patch('application.function1', return_value=42)
def test2(mocked_function_object):
    print(application.function1())

Povšimněte si, že jméno mockované funkce je zapsáno i s uvedením jmenného prostoru („application.function1“), který ovšem musí odpovídat kontextu, v němž se funkce volá! Právě uvedení správného kontextu je pravděpodobně nejdůležitější část, kterou je nutné při mockování pochopit (více viz navazující část tohoto miniseriálu). Navíc stojí za povšimnutí, že se testovací funkci test2 předává parametr mocked_function_object, který představuje objekt udržující informace o mocku. Tento objekt využijeme v dalších demonstračních příkladech, nyní je však nutné si uvědomit, že se tento parametr plní automaticky (při volání test2 ho explicitně nebudeme uvádět – ostatně v reálných jednotkových testech se bude tato funkce volat automaticky).

Nyní již můžeme skript doplnit o kód, který všechny testy spustí. Povšimněte si, že první test naschvál spouštíme dvakrát (na začátku a na konci), aby bylo patrné, že mockovaná funkce se volá pouze v testu test2 (mockování je v tomto případě přísně lokální):

if __name__ == '__main__':
    test1()
    print()
 
    test2()
    print()
 
    test1()
    print()

Výsledek vypsaný po provedení skriptu potvrzuje, že při každém spuštění testu test1 se zavolá původní funkce, kdežto při spuštění testu test2 funkce mockovaná:

$ python3 test.py
 
function1 called
tested function
 
42
 
function1 called
tested function
Poznámka: náš mock pouze vrací hodnotu a nesnaží se žádným způsobem napodobit logiku funkce (tedy výpis zprávy na standardní výstup). Jedná se tedy o fake double.

15. Vytvoření handleru, který se zavolá namísto originální funkce

Prozatím jsme se dozvěděli, jakým způsobem je možné nahradit volání skutečné funkce vrácením nějaké předem nastavené hodnoty. Tato hodnota sice může být prakticky jakákoli (číslo, pravdivostní hodnota, řetězec, pole, n-tice, slovník, objekt, klidně i None), ovšem někdy si s tímto chováním nevystačíme a budeme potřebovat, aby se namísto původní funkce zavolala funkce odlišná; typicky mnohem jednodušší, s předvídatelnějšími výsledky atd. Příkladem může být „falešná“ funkce nahrazující čtení záznamů z databáze za výběr hodnoty z předem známé datové struktury. S využitím knihovny unittest.mock je nahrazení původní funkce za její (ne)plnohodnotný mock snadné. Nejprve tuto funkci deklarujeme (měla by akceptovat stejné parametry, jako funkce původní) a následně použijeme v anotaci @patch nepovinný parametr side_effect, kterému předáme referenci na mock:

def side_effect_handler():
    print("side_effect function called")
    return -1
 
 
@patch('application.function1', side_effect=side_effect_handler)
def test3(mocked_function_object):
    print(application.function1())

Pokud spustíme třetí test představovaný výše vypsanou funkcí test3 (a to opět BEZ parametrů):

test3()

zavolá se z něj ve skutečnosti funkce side_effect_handler a nikoli application.function1:

side_effect function called
-1
Poznámka: takto vytvořený mock je opět použit jen lokálně v rámci testu představovaného funkcí test3.

16. Kombinace handleru s předkonfigurovanou návratovou hodnotou?

Podívejme se ještě, co se stane ve chvíli, kdy v anotaci @patch současně použijeme parametr return_valueside_effect. Zápis bude vypadat následovně:

def side_effect_handler():
    print("side_effect function called")
    return -1
 
@patch('application.function1', return_value=42, side_effect=side_effect_handler)
def test4(mocked_function_object):
    print(application.function1())

V případě, že zavoláme výše vypsanou testovací funkci test4, vypíšou se na standardní výstup následující dva řádky, z nichž je patrné, že se hodnota specifikovaná parametrem return_value ignorovala a namísto ní se použila návratová hodnota „falešné“ funkce side_effect_handler:

side_effect function called
-1

Toto chování se ale změní ve chvíli, kdy mock vrátí speciální hodnotu unittest.mock.DEFAULT (přesněji řečeno sentinel.DEFAULT). V takovém případě se skutečně využije hodnota zapsaná v parametru return_value v anotaci @patch, o čemž se lze ostatně velmi snadno přesvědčit:

def side_effect_handler_2():
    print("side_effect function called")
    return DEFAULT
 
 
@patch('application.function1', return_value=42, side_effect=side_effect_handler_2)
def test5(mocked_function_object):
    print(application.function1())

Výsledek zavolání výše vypsané testovací funkce test5:

side_effect function called
42

Díky tomuto chování je možné použít „falešnou“ funkci ve větším množství testů, což je téma, kterým se budeme podrobněji zabývat příště.

Pro přehlednost je v této kapitole vypsán úplný zdrojový kód dnešního druhého demonstračního příkladu rozděleného do dvou modulů.

Soubor application.py s testovanou funkcí

"""Implementace logiky aplikace, kterou budeme testovat."""
 
 
def function1():
    """Funkce, kterou v testech nahradíme mockem."""
    print("function1 called")
    return "tested function"

Soubor test.py s testy

"""Implementace (umělých) jednotkových testů."""
 
from unittest.mock import *
 
import application
 
 
def test1():
    """První test neprovádí prakticky žádné reálné kontroly, jen zavolá testovanou funkci."""
    print(application.function1())
 
 
@patch('application.function1', return_value=42)
def test2(mocked_function):
    """Druhý test používá fake test double - náhradu volané funkce."""
    print(application.function1())
 
 
def side_effect_handler():
    """Implementace handleru - stub funkce nahrazované mockem."""
    print("side_effect function called")
    return -1
 
 
@patch('application.function1', side_effect=side_effect_handler)
def test3(mocked_function):
    """Druhý test používá stub test double - náhradu volané funkce."""
    print(application.function1())
 
 
@patch('application.function1', return_value=42, side_effect=side_effect_handler)
def test4(mocked_function):
    """Čtvrtý test se snaží zkombinovat fake a stub."""
    print(application.function1())
 
 
def side_effect_handler_2():
    """Implementace handleru - stub funkce nahrazované mockem, který ovšem ovlivňuje chování testu."""
    print("side_effect function called")
    return DEFAULT
 
 
@patch('application.function1', return_value=42, side_effect=side_effect_handler_2)
def test5(mocked_function):
    """Čtvrtý test se opět snaží zkombinovat fake a stub."""
    print(application.function1())
 
 
if __name__ == '__main__':
    print("*** test1 ***")
    test1()
    print()
 
    print("*** test2 ***")
    test2()
    print()
 
    print("*** test3 ***")
    test3()
    print()
 
    print("*** test4 ***")
    test4()
    print()
 
    print("*** test5 ***")
    test5()
    print()
 
    print("*** test1 ***")
    test1()
    print()

17. Obsah následující části seriálu

V navazujícím článku si ukážeme některé další možnosti, které nám Python a jeho knihovny a frameworky nabízí v oblasti jednotkových testů (včetně možnosti selektivního spouštění benchmarků). Podíváme se i na způsob spouštění jednotkových testů v k tomu určených prostředích (Jenkins, Travis CI) apod.

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ě deseti 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:

tip_Ansible

https://github.com/tisnik/testing-in-python/blob/master/

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. 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/
  2. 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/
  3. 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/
  4. 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/
  5. Validace datových struktur v Pythonu (2. část)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/
  6. Validace datových struktur v Pythonu (dokončení)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/
  7. Univerzální testovací nástroj Robot Framework
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/
  8. Univerzální testovací nástroj Robot Framework a BDD testy
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/
  9. Úvod do problematiky fuzzingu a fuzz testování
    https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/
  10. Ú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/
  11. 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/
  12. 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/
  13. Testování aplikací naprogramovaných v jazyce Go
    https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/
  14. 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/
  15. 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/
  16. 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/
  17. 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/
  18. 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/
  19. 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/
  20. 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/
  21. 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. Awesome Python – testing
    https://github.com/vinta/awesome-python#testing
  2. Selenium (pro Python)
    https://pypi.org/project/selenium/
  3. Getting Started With Testing in Python
    https://realpython.com/python-testing/
  4. unittest.mock — mock object library
    https://docs.python.org/3­.5/library/unittest.mock.html
  5. mock 2.0.0
    https://pypi.python.org/pypi/mock
  6. An Introduction to Mocking in Python
    https://www.toptal.com/python/an-introduction-to-mocking-in-python
  7. Mock – Mocking and Testing Library
    http://mock.readthedocs.io/en/stable/
  8. Python Mocking 101: Fake It Before You Make It
    https://blog.fugue.co/2016–02–11-python-mocking-101.html
  9. Nauč se Python! – Testování
    http://naucse.python.cz/les­sons/intro/testing/
  10. Flexmock (dokumentace)
    https://flexmock.readthedoc­s.io/en/latest/
  11. Test Fixture (Wikipedia)
    https://en.wikipedia.org/wi­ki/Test_fixture
  12. Mock object (Wikipedia)
    https://en.wikipedia.org/wi­ki/Mock_object
  13. Extrémní programování
    https://cs.wikipedia.org/wi­ki/Extr%C3%A9mn%C3%AD_pro­gramov%C3%A1n%C3%AD
  14. Programování řízené testy
    https://cs.wikipedia.org/wi­ki/Programov%C3%A1n%C3%AD_%C5%99%­C3%ADzen%C3%A9_testy
  15. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  16. Tox
    https://tox.readthedocs.io/en/latest/
  17. pytest: helps you write better programs
    https://docs.pytest.org/en/latest/
  18. doctest — Test interactive Python examples
    https://docs.python.org/dev/li­brary/doctest.html#module-doctest
  19. unittest — Unit testing framework
    https://docs.python.org/dev/li­brary/unittest.html
  20. Python namespaces
    https://bytebaker.com/2008/07/30/pyt­hon-namespaces/
  21. Namespaces and Scopes
    https://www.python-course.eu/namespaces.php
  22. Stránka projektu Robot Framework
    https://robotframework.org/
  23. GitHub repositář Robot Frameworku
    https://github.com/robotfra­mework/robotframework
  24. Robot Framework (Wikipedia)
    https://en.wikipedia.org/wi­ki/Robot_Framework
  25. Tutoriál Robot Frameworku
    http://www.robotframeworktu­torial.com/
  26. Robot Framework Documentation
    https://robotframework.or­g/robotframework/
  27. Robot Framework Introduction
    https://blog.testproject.i­o/2016/11/22/robot-framework-introduction/
  28. robotframework 3.1.2 na PyPi
    https://pypi.org/project/ro­botframework/
  29. Robot Framework demo (GitHub)
    https://github.com/robotfra­mework/RobotDemo
  30. Robot Framework web testing demo using SeleniumLibrary
    https://github.com/robotfra­mework/WebDemo
  31. Robot Framework for Mobile Test Automation Demo
    https://www.youtube.com/wat­ch?v=06LsU08slP8
  32. Gherkin
    https://cucumber.io/docs/gherkin/
  33. Selenium
    https://selenium.dev/
  34. SeleniumLibrary
    https://robotframework.org/
  35. The Practical Test Pyramid
    https://martinfowler.com/ar­ticles/practical-test-pyramid.html
  36. Acceptance Tests and the Testing Pyramid
    http://www.blog.acceptance­testdrivendevelopment.com/ac­ceptance-tests-and-the-testing-pyramid/
  37. Tab-separated values
    https://en.wikipedia.org/wiki/Tab-separated_values
  38. A quick guide about Python implementations
    https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321
  39. radamsa
    https://gitlab.com/akihe/radamsa
  40. Fuzzing (Wikipedia)
    https://en.wikipedia.org/wiki/Fuzzing
  41. american fuzzy lop
    http://lcamtuf.coredump.cx/afl/
  42. Fuzzing: the new unit testing
    https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1
  43. Corpus for github.com/dvyukov/go-fuzz examples
    https://github.com/dvyukov/go-fuzz-corpus
  44. AFL – QuickStartGuide.txt
    https://github.com/google/AF­L/blob/master/docs/QuickStar­tGuide.txt
  45. Introduction to Fuzzing in Python with AFL
    https://alexgaynor.net/2015/a­pr/13/introduction-to-fuzzing-in-python-with-afl/
  46. Writing a Simple Fuzzer in Python
    https://jmcph4.github.io/2018/01/19/wri­ting-a-simple-fuzzer-in-python/
  47. How to Fuzz Go Code with go-fuzz (Continuously)
    https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/
  48. Golang Fuzzing: A go-fuzz Tutorial and Example
    http://networkbit.ch/golang-fuzzing/
  49. Fuzzing Python Modules
    https://stackoverflow.com/qu­estions/20749026/fuzzing-python-modules
  50. 0×3 Python Tutorial: Fuzzer
    http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/
  51. fuzzing na PyPi
    https://pypi.org/project/fuzzing/
  52. Fuzzing 0.3.2 documentation
    https://fuzzing.readthedoc­s.io/en/latest/
  53. Randomized testing for Go
    https://github.com/dvyukov/go-fuzz
  54. HTTP/2 fuzzer written in Golang
    https://github.com/c0nrad/http2fuzz
  55. 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
  56. Continuous Fuzzing Made Simple
    https://fuzzit.dev/
  57. Halt and Catch Fire
    https://en.wikipedia.org/wi­ki/Halt_and_Catch_Fire#In­tel_x86
  58. Random testing
    https://en.wikipedia.org/wi­ki/Random_testing
  59. Monkey testing
    https://en.wikipedia.org/wi­ki/Monkey_testing
  60. 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
  61. libFuzzer – a library for coverage-guided fuzz testing
    https://llvm.org/docs/LibFuzzer.html
  62. fuzzy-swagger na PyPi
    https://pypi.org/project/fuzzy-swagger/
  63. fuzzy-swagger na GitHubu
    https://github.com/namuan/fuzzy-swagger
  64. Fuzz testing tools for Python
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy#Fuzz_Testing_Tools
  65. A curated list of awesome Go frameworks, libraries and software
    https://github.com/avelino/awesome-go
  66. gofuzz: a library for populating go objects with random values
    https://github.com/google/gofuzz
  67. tavor: A generic fuzzing and delta-debugging framework
    https://github.com/zimmski/tavor
  68. hypothesis na GitHubu
    https://github.com/Hypothe­sisWorks/hypothesis
  69. Hypothesis: Test faster, fix more
    https://hypothesis.works/
  70. Hypothesis
    https://hypothesis.works/ar­ticles/intro/
  71. What is Hypothesis?
    https://hypothesis.works/articles/what-is-hypothesis/
  72. Databáze CVE
    https://www.cvedetails.com/
  73. Fuzz test Python modules with libFuzzer
    https://github.com/eerimoq/pyfuzzer
  74. Taof – The art of fuzzing
    https://sourceforge.net/pro­jects/taof/
  75. JQF + Zest: Coverage-guided semantic fuzzing for Java
    https://github.com/rohanpadhye/jqf
  76. http2fuzz
    https://github.com/c0nrad/http2fuzz
  77. Demystifying hypothesis testing with simple Python examples
    https://towardsdatascience­.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294
  78. Testování
    http://voho.eu/wiki/testovani/
  79. Unit testing (Wikipedia.en)
    https://en.wikipedia.org/wi­ki/Unit_testing
  80. Unit testing (Wikipedia.cz)
    https://cs.wikipedia.org/wi­ki/Unit_testing
  81. Unit Test vs Integration Test
    https://www.youtube.com/wat­ch?v=0GypdsJulKE
  82. TestDouble
    https://martinfowler.com/bli­ki/TestDouble.html
  83. Test Double
    http://xunitpatterns.com/Tes­t%20Double.html
  84. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  85. Acceptance test–driven development
    https://en.wikipedia.org/wi­ki/Acceptance_test%E2%80%93dri­ven_development
  86. Gauge
    https://gauge.org/
  87. Gauge (software)
    https://en.wikipedia.org/wi­ki/Gauge_(software)
  88. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  89. 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
  90. Články a zprávičky věnující se Pythonu
    https://www.root.cz/n/python/
  91. PythonTestingToolsTaxonomy
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy
  92. Top 6 BEST Python Testing Frameworks [Updated 2020 List]
    https://www.softwaretestin­ghelp.com/python-testing-frameworks/