Testování

Programátorská práce nespočívá jen v tom, program napsat. Důležité je si i ověřit, že opravdu funguje (a případně ho pak opravit). Ověřování, že program funguje, se říká testování.

Zatím jsi asi svoje programy testovala tak, že jsi je zkusila spustit, něco zadala a podívala se, jestli jsou výsledky v pořádku. U větších programů, které budou mít více a více možností, ale bude těžší a těžší takhle zkontrolovat, jestli všechny ty možnosti fungují, jak mají.

Proto si programátoři, místo aby program zkoušeli ručně, píšou jiné programy, které testují jejich výtvory za ně.

Automatické testy jsou funkce, které zkontrolují, že náš program funguje správně. Spuštěním testů můžeš kdykoli ověřit, že kód funguje. Hlavní výhoda je, že když v otestovaném kódu v budoucnu uděláš nějakou změnu, testy ověří, že jsi nerozbila nic, co dříve fungovalo.

Instalace knihovny pytest

Zatím jsme v kurzu pracovaly s tím, co se instaluje se samotným Pythonem – s moduly jako math a turtle. Kromě takových modulů ale existuje ale velká spousta dalších knihoven, které nejsou přímo v Pythonu, ale dají se doinstalovat a používat.

Na testy je v samotném Pythonu zabudovaná knihovna unittest. Ta je ale celkem složitá na použití, proto ji my používat nebudeme. Nainstalujeme si knihovnu pytest, která se používá mnohem jednodušeji a je velice populární.

Knihovny se instalují do aktivního virtuálního prostředí. Jak se dělá a spouští virtuální prostředí ses naučila při instalaci Pythonu, ale teprve teď to začíná být opravdu důležité. Ujisti se, že máš virtuální prostředí aktivované.

Potom zadej následující příkaz. (Je to příkaz příkazové řádky, podobně jako cd nebo mkdir; nezadávej ho do Pythonu.)

(venv)$ python -m pip install pytest

Co to znamená?

python -m pip zavolá Python s tím, že má pustit modul pip. Tento modul umí instalovat nebo odinstalovávat knihovny. (Jestli si pamatuješ vytváření virtuálního prostředí, použila jsi tam příkaz python -m venv – modul venv umí vytvářet virtuální prostředí.) No a slova install pytest říkají Pipu, že má nainstalovat pytest.

Nápověda k použití Pipu se dá vypsat pomocí příkazu python -m pip --help.

Pro Windows

Jsi-li na Windows, od této lekce začne být důležité spouštět pythonní programy pomocí python program.py, ne jen program.py. Ačkoli se v těchto materiálech všude používá python na začátku, zatím mohlo všechno fungovat i bez toho. Program se ale bez příkazu python může spustit v jiném Pythonu, než v tom z virtuálního prostředí – a tam pytest nebude k dispozici.

Psaní testů

Nejdříve si testování ukážeme na jednoduchém příkladu. Tady je funkce secti, která umí sečíst dvě čísla, a další funkce, která testuje, jestli se secti pro určité hodnoty chová správně.

Kód si opiš do souboru test_secteni.py, v novém prázdném adresáři. Pro pytest je (ve výchozím nastavení) důležité, aby jména jak souborů s testy, tak samotných testovacích funkcí, začínala na test_.

def secti(a, b):
    return a + b

def test_secti():
    assert secti(1, 2) == 3

Co se v té testovací funkci děje?

Příkaz assert vyhodnotí výraz za ním a pokud výsledek není pravdivý, vyvolá výjimku, která způsobí, že test selže. Můžeš si představit, že assert a == b dělá následující:

if not (a == b):
    raise AssertionError('Test selhal!')

Zatím assert nepoužívej jinde než v testovacích funkcích. V „normálním” kódu má assert vlastnosti, do kterých teď nebudeme zabředávat.

Spouštění testů

Testy se spouští zadáním příkazu python -m pytest -v následovaným názvem souboru s testy. Tedy v překladu: Pythone, pusť modul pytest, v „ukecaném” režimu (angl. verbose) nad zadaným souborem.

$ python -m pytest -v test_secteni.py
============================= test session starts ==============================
platform linux -- Python 3.7.1, pytest-3.6.4, py-1.5.4, pluggy-0.6.0 -- venv/bin/python
cachedir: .pytest_cache
rootdir: naucse, inifile:
collecting ... collected 1 item

test_secteni.py::test_secti PASSED                                       [100%]

=========================== 1 passed in 0.00 seconds ===========================

Tento příkaz projde zadaný soubor, zavolá v něm všechny funkce, jejichž jméno začíná na test_, a ověří, že nevyvolají žádnou výjimku – typicky výjimku z příkazu assert. Pokud výjimka nastane, dá to pytest velice červeně najevo a přidá několik informací, které můžou usnadnit nalezení a opravu chyby.

Argument s názvem souboru můžeme vynechat: python -m pytest -v V takovém případě pytest projde aktuální adresář a spustí testy ze všech souborů, jejichž jméno začíná na test_. Místo souboru lze též uvést adresář a pytest vyhledá testy v něm.

Zkus si změnit funkci secti (nebo její test) a podívat se, jak to vypadá když test „neprojde“.

Testovací moduly

Testy se většinou nepíšou přímo ke kódu, ale do souboru vedle. Je to tak přehlednější a taky to pak zjednodušuje distribuci – předávání kódu někomu, kdo ho chce jen spustit a testy nepotřebuje.

Rozděl soubor s testem sečítání: funkci secti přesuň do modulu secteni.py, a v test_secteni.py nech jenom test. Do test_secteni.py pak na začátek přidej from secteni import secti, aby byla funkce testu k dispozici.

Test by měl opět projít.

Spouštěcí moduly

Automatické testy musí projít „bez dozoru“. V praxi se často automaticky spouští, případné chyby se automaticky oznamují (např. e-mailem) a fungující kód se automaticky začne používat dál (nebo se rovnou vydá zákazníkům).

Co to znamená pro nás? Funkce input v testech nefunguje. Nemá koho by se zeptala; „za klávesnicí“ nemusí nikdo sedět.

To může někdy „ztěžovat práci“. Ukážeme si to na složitějším projektu: na 1D piškvorkách.

Nemáš-li hotové 1D piškvorky, následující sekce budou jen teorietické. Učíš-li se z domu, dodělej si Piškvorky než budeš pokračovat dál! Zadání najdeš (prozatím) v projektech pro PyLadies na straně 2.

Kód pro 1D Piškvorky může rámcově vypadat zhruba takto:

import random  # (příp. import jiných věci, které budou potřeba)

def tah(pole, cislo_policka, symbol):
    """Vrátí pole s daným symbolem umístěným na danou pozici"""
    ...

def tah_hrace(pole):
    """Zeptá se hráče kam chce hrát a vrátí pole se zaznamenaným tahem"""
    ...
    input('Kam chceš hrát? ')
    ...

def piskvorky1d():
    """Spustí hru

    Vytvoří hrací pole a střídavě volá tah_hrace a tah_pocitace
    dokud někdo nevyhraje"""
    while ...:
        ...
        tah_hrace(...)
        ...

# Puštění hry!
piskvorky1d()

Když tenhle modul naimportuješ, Python v něm postupně, odshora dolů, provede všechny příkazy.

První příkaz, import, jen zpřístupní nějaké proměnné a funkce; je-li importovaný modul správně napsaný, nemá vedlejší účinek. Definice funkcí (příkazy def a všechno v nich) podobně jen definují funkce. Ale zavoláním funkce piskvorky1d se spustí hra: funkce piskvorky1d zavolá funkci tah_hrace() a ta zavolá input().

Importuješ-li tenhle modul z testů, input selže a import se nepovede.

A kdybys modul importovala odjinud – například bys chtěla funkci tah použít v nějaké jiné hře – uživatel si bude muset v rámci importu zahrát Piškvorky!

Volání funkce piskvorky1d je vedlejší efekt, a je potřeba ho odstranit. No jo, ale po takovém odstranění už nejde jednoduše spustit hra! Co s tím?

Můžeš na to vytvořit nový modul. Pojmenuj ho hra.py a dej do něj jenom to odstraněné volání:

import piskvorky

piskvorky.piskvorky1d()

Tenhle modul nebudeš moci testovat (protože nepřímo volá funkci input), ale můžeš ho spustit, když si budeš chtít zahrát. Protože k němu nemáš napsané testy, nepoznáš z nich, když se takový spouštěcí modul rozbije. Měl by být proto nejjednodušší – jeden import a jedno volání.

Původní modul teď můžeš importovat bez obav – ať už z testů nebo z jiných modulů. Test může vypadat třeba takhle:

import piskvorky

def test_tah_na_prazdne_pole():
    pole = piskvorky.tah_pocitace('--------------------')
    assert len(pole) == 20
    assert pole.count('x') == 1
    assert pole.count('-') == 19

Pozitivní a negativní testy

Testům, které kontrolují že se program za správných podmínek chová správně, se říká pozitivní testy. Můžeš ale testovat i reakci programu na špatné nebo neočekávané podmínky.

Testy, které kontrolují reakci na „špatný“ vstup, se jmenují negativní testy. Můžou kontrolovat nějaký negativní výsledek (např. že volání jako cislo_je_sude(7) vrátí False), a nebo to, že nastane „rozumná“ výjimka.

Například funkce tah_pocitace by měla způsobit chybu (třeba ValueError), když je herní pole už plné.

Vyvolat výjimku je mnohem lepší než alternativy, např. kdyby takové volání „tiše“ – bez oznámení – zablokovalo celý program. Když kód pak použiješ ve větším programu, můžeš si být jistá, že při špatném volání dostaneš srozumitelnou chybu – tedy takovou, která se co nejsnadněji opravuje.

Na otestování výjimky použij příkaz with a funkci raises naimportovanou z modulu pytest. Jak příkaz with přesně funguje, se dozvíme později; teď stačí říct, že ověří, že odsazený blok kódu pod ním vyvolá danou výjimku:

import pytest

import piskvorky

def test_tah_chyba():
    with pytest.raises(ValueError):
        piskvorky.tah_pocitace('oxoxoxoxoxoxoxoxoxox')
{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2019/plzen-podzim-2019:tests:2",
      "title": "Testování",
      "html": "\n          \n    \n\n    <h1>Testov&#xE1;n&#xED;</h1>\n<p>Program&#xE1;torsk&#xE1; pr&#xE1;ce nespo&#x10D;&#xED;v&#xE1; jen v tom, program napsat.\nD&#x16F;le&#x17E;it&#xE9; je si i ov&#x11B;&#x159;it, &#x17E;e opravdu funguje (a p&#x159;&#xED;padn&#x11B; ho pak opravit).\nOv&#x11B;&#x159;ov&#xE1;n&#xED;, &#x17E;e program funguje, se &#x159;&#xED;k&#xE1; <em>testov&#xE1;n&#xED;</em>.</p>\n<p>Zat&#xED;m jsi asi svoje programy testovala tak, &#x17E;e jsi\nje zkusila spustit, n&#x11B;co zadala a pod&#xED;vala se,\njestli jsou v&#xFD;sledky v po&#x159;&#xE1;dku.\nU v&#x11B;t&#x161;&#xED;ch program&#x16F;, kter&#xE9; budou m&#xED;t v&#xED;ce a v&#xED;ce\nmo&#x17E;nost&#xED;, ale bude t&#x11B;&#x17E;&#x161;&#xED; a t&#x11B;&#x17E;&#x161;&#xED; takhle zkontrolovat,\njestli v&#x161;echny ty mo&#x17E;nosti funguj&#xED;, jak maj&#xED;.</p>\n<p>Proto si program&#xE1;to&#x159;i, m&#xED;sto aby program zkou&#x161;eli ru&#x10D;n&#x11B;, p&#xED;&#x161;ou jin&#xE9; programy,\nkter&#xE9; testuj&#xED; jejich v&#xFD;tvory za n&#x11B;.</p>\n<p><em>Automatick&#xE9; testy</em> jsou funkce, kter&#xE9;\nzkontroluj&#xED;, &#x17E;e n&#xE1;&#x161; program funguje spr&#xE1;vn&#x11B;.\nSpu&#x161;t&#x11B;n&#xED;m test&#x16F; m&#x16F;&#x17E;e&#x161; kdykoli ov&#x11B;&#x159;it, &#x17E;e k&#xF3;d funguje.\nHlavn&#xED; v&#xFD;hoda je, &#x17E;e kdy&#x17E; v otestovan&#xE9;m k&#xF3;du\nv budoucnu ud&#x11B;l&#xE1;&#x161; n&#x11B;jakou zm&#x11B;nu,\ntesty ov&#x11B;&#x159;&#xED;, &#x17E;e jsi nerozbila nic, co d&#x159;&#xED;ve\nfungovalo.</p>\n<h2>Instalace knihovny pytest</h2>\n<p>Zat&#xED;m jsme v&#xA0;kurzu pracovaly s&#xA0;t&#xED;m, co se instaluje\nse samotn&#xFD;m Pythonem &#x2013; s&#xA0;moduly jako <code>math</code> a <code>turtle</code>.\nKrom&#x11B; takov&#xFD;ch modul&#x16F; ale existuje ale velk&#xE1; spousta\ndal&#x161;&#xED;ch <em>knihoven</em>, kter&#xE9; nejsou p&#x159;&#xED;mo v&#xA0;Pythonu, ale daj&#xED; se doinstalovat\na pou&#x17E;&#xED;vat.</p>\n<p>Na testy je v&#xA0;samotn&#xE9;m Pythonu zabudovan&#xE1; knihovna <code>unittest</code>.\nTa je ale celkem slo&#x17E;it&#xE1; na pou&#x17E;it&#xED;, proto ji my pou&#x17E;&#xED;vat nebudeme.\nNainstalujeme si knihovnu <code>pytest</code>, kter&#xE1; se pou&#x17E;&#xED;v&#xE1;\nmnohem jednodu&#x161;eji a je velice popul&#xE1;rn&#xED;.</p>\n<p>Knihovny se instaluj&#xED; do aktivn&#xED;ho virtu&#xE1;ln&#xED;ho prost&#x159;ed&#xED;.\nJak se d&#x11B;l&#xE1; a spou&#x161;t&#xED; virtu&#xE1;ln&#xED; prost&#x159;ed&#xED;\nses nau&#x10D;ila p&#x159;i <a href=\"/2019/plzen-podzim-2019/beginners/install/\">instalaci Pythonu</a>,\nale teprve te&#x10F; to za&#x10D;&#xED;n&#xE1; b&#xFD;t opravdu d&#x16F;le&#x17E;it&#xE9;.\nUjisti se, &#x17E;e m&#xE1;&#x161; virtu&#xE1;ln&#xED; prost&#x159;ed&#xED; aktivovan&#xE9;.</p>\n<p>Potom zadej n&#xE1;sleduj&#xED;c&#xED; p&#x159;&#xED;kaz.\n(Je to p&#x159;&#xED;kaz p&#x159;&#xED;kazov&#xE9; &#x159;&#xE1;dky, podobn&#x11B; jako\n<code>cd</code> nebo <code>mkdir</code>; nezad&#xE1;vej ho do Pythonu.)</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(venv)$ </span>python -m pip install pytest\n</pre></div><div class=\"admonition note\"><p class=\"admonition-title\">Co to znamen&#xE1;?</p>\n<p><code>python -m pip</code> zavol&#xE1; Python s t&#xED;m, &#x17E;e m&#xE1; pustit modul\n<code>pip</code>. Tento modul um&#xED; instalovat nebo\nodinstalov&#xE1;vat knihovny.\n(Jestli si pamatuje&#x161; vytv&#xE1;&#x159;en&#xED; virtu&#xE1;ln&#xED;ho prost&#x159;ed&#xED;, pou&#x17E;ila jsi tam\np&#x159;&#xED;kaz <code>python -m venv</code> &#x2013; modul <code>venv</code> um&#xED; vytv&#xE1;&#x159;et virtu&#xE1;ln&#xED; prost&#x159;ed&#xED;.)\nNo a slova <code>install pytest</code> &#x159;&#xED;kaj&#xED; Pipu, &#x17E;e m&#xE1; nainstalovat <code>pytest</code>.</p>\n<p>N&#xE1;pov&#x11B;da k pou&#x17E;it&#xED; Pipu se d&#xE1; vypsat pomoc&#xED; p&#x159;&#xED;kazu\n<code>python -m pip --help</code>.</p>\n</div><div class=\"admonition warning\"><p class=\"admonition-title\">Pro Windows</p>\n<p>Jsi-li na Windows, od t&#xE9;to lekce za&#x10D;ne b&#xFD;t d&#x16F;le&#x17E;it&#xE9;\nspou&#x161;t&#x11B;t pythonn&#xED; programy pomoc&#xED; <code>python program.py</code>, ne jen\n<code>program.py</code>.\nA&#x10D;koli se v t&#x11B;chto materi&#xE1;lech v&#x161;ude pou&#x17E;&#xED;v&#xE1; <code>python</code> na za&#x10D;&#xE1;tku, zat&#xED;m\nmohlo v&#x161;echno fungovat i bez toho.\nProgram se ale bez p&#x159;&#xED;kazu <code>python</code> m&#x16F;&#x17E;e spustit v&#xA0;jin&#xE9;m Pythonu,\nne&#x17E; v&#xA0;tom z&#xA0;virtu&#xE1;ln&#xED;ho prost&#x159;ed&#xED; &#x2013; a tam <code>pytest</code> nebude k&#xA0;dispozici.</p>\n</div><h2>Psan&#xED; test&#x16F;</h2>\n<p>Nejd&#x159;&#xED;ve si testov&#xE1;n&#xED; uk&#xE1;&#x17E;eme na jednoduch&#xE9;m p&#x159;&#xED;kladu.\nTady je funkce <code>secti</code>, kter&#xE1; um&#xED; se&#x10D;&#xED;st\ndv&#x11B; &#x10D;&#xED;sla, a dal&#x161;&#xED; funkce, kter&#xE1; testuje, jestli se\n<code>secti</code> pro ur&#x10D;it&#xE9; hodnoty\nchov&#xE1; spr&#xE1;vn&#x11B;.</p>\n<p>K&#xF3;d si opi&#x161; do souboru <code>test_secteni.py</code>,\nv nov&#xE9;m pr&#xE1;zdn&#xE9;m adres&#xE1;&#x159;i.\nPro <code>pytest</code> je (ve v&#xFD;choz&#xED;m nastaven&#xED;)\nd&#x16F;le&#x17E;it&#xE9;, aby jm&#xE9;na jak soubor&#x16F; s testy, tak\nsamotn&#xFD;ch testovac&#xED;ch funkc&#xED;, za&#x10D;&#xED;nala na\n<code>test_</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">secti</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"k\">return</span> <span class=\"n\">a</span> <span class=\"o\">+</span> <span class=\"n\">b</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">test_secti</span><span class=\"p\">():</span>\n    <span class=\"k\">assert</span> <span class=\"n\">secti</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">3</span>\n</pre></div><p>Co se v&#xA0;t&#xE9; testovac&#xED; funkci d&#x11B;je?</p>\n<p>P&#x159;&#xED;kaz <code>assert</code> vyhodnot&#xED; v&#xFD;raz za n&#xED;m\na pokud v&#xFD;sledek nen&#xED; pravdiv&#xFD;, vyvol&#xE1; v&#xFD;jimku,\nkter&#xE1; zp&#x16F;sob&#xED;, &#x17E;e test sel&#x17E;e.\nM&#x16F;&#x17E;e&#x161; si p&#x159;edstavit, &#x17E;e <code>assert a == b</code> d&#x11B;l&#xE1; n&#xE1;sleduj&#xED;c&#xED;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"p\">(</span><span class=\"n\">a</span> <span class=\"o\">==</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"k\">raise</span> <span class=\"ne\">AssertionError</span><span class=\"p\">(</span><span class=\"s1\">&apos;Test selhal!&apos;</span><span class=\"p\">)</span>\n</pre></div><div class=\"admonition note\"><p>Zat&#xED;m <code>assert</code> nepou&#x17E;&#xED;vej jinde ne&#x17E; v&#xA0;testovac&#xED;ch funkc&#xED;ch.\nV &#x201E;norm&#xE1;ln&#xED;m&#x201D; k&#xF3;du m&#xE1; <code>assert</code> vlastnosti,\ndo kter&#xFD;ch te&#x10F; nebudeme zab&#x159;ed&#xE1;vat.</p>\n</div><h2>Spou&#x161;t&#x11B;n&#xED; test&#x16F;</h2>\n<p>Testy se spou&#x161;t&#xED; zad&#xE1;n&#xED;m p&#x159;&#xED;kazu\n<code>python -m pytest -v</code> n&#xE1;sledovan&#xFD;m n&#xE1;zvem souboru s&#xA0;testy.\nTedy v p&#x159;ekladu: <strong>Python</strong>e, pus&#x165;\n<strong>m</strong>odul <strong>pytest</strong>,\nv &#x201E;ukecan&#xE9;m&#x201D; re&#x17E;imu (angl. <strong>v</strong>erbose) nad zadan&#xFD;m souborem.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m pytest -v test_secteni.py\n</pre></div><div class=\"highlight\"><pre><span></span><span class=\" -Color -Color-Bold\">============================= test session starts ==============================</span>\nplatform linux -- Python 3.7.1, pytest-3.6.4, py-1.5.4, pluggy-0.6.0 -- venv/bin/python\ncachedir: .pytest_cache\nrootdir: naucse, inifile:\n<span class=\" -Color -Color-Bold\">collecting ...</span> collected 1 item\n\ntest_secteni.py::test_secti <span class=\" -Color -Color-Green\">PASSED</span>                                       <span class=\" -Color -Color-Cyan\">[100%]</span>\n\n<span class=\" -Color -Color-BoldGreen\">=========================== 1 passed in 0.00 seconds ===========================</span>\n</pre></div><p>Tento p&#x159;&#xED;kaz projde zadan&#xFD; soubor, zavol&#xE1; v&#xA0;n&#x11B;m v&#x161;echny funkce,\njejich&#x17E; jm&#xE9;no za&#x10D;&#xED;n&#xE1; na <code>test_</code>, a ov&#x11B;&#x159;&#xED;, &#x17E;e nevyvolaj&#xED; &#x17E;&#xE1;dnou\nv&#xFD;jimku &#x2013; typicky v&#xFD;jimku z&#xA0;p&#x159;&#xED;kazu <code>assert</code>.\nPokud v&#xFD;jimka nastane, d&#xE1; to <code>pytest</code> velice &#x10D;erven&#x11B;\nnajevo a p&#x159;id&#xE1; n&#x11B;kolik informac&#xED;, kter&#xE9; m&#x16F;&#x17E;ou\nusnadnit nalezen&#xED; a opravu chyby.</p>\n<div class=\"admonition note\"><p>Argument s&#xA0;n&#xE1;zvem souboru m&#x16F;&#x17E;eme vynechat: <code>python -m pytest -v</code>\nV&#xA0;takov&#xE9;m p&#x159;&#xED;pad&#x11B; <code>pytest</code> projde aktu&#xE1;ln&#xED; adres&#xE1;&#x159; a spust&#xED; testy\nze v&#x161;ech soubor&#x16F;, jejich&#x17E; jm&#xE9;no za&#x10D;&#xED;n&#xE1; na <code>test_</code>. M&#xED;sto souboru\nlze t&#xE9;&#x17E; uv&#xE9;st adres&#xE1;&#x159; a <code>pytest</code> vyhled&#xE1; testy v&#xA0;n&#x11B;m.</p>\n</div><p>Zkus si zm&#x11B;nit funkci <code>secti</code> (nebo jej&#xED; test) a pod&#xED;vat se,\njak to vypad&#xE1; kdy&#x17E; test &#x201E;neprojde&#x201C;.</p>\n<h2>Testovac&#xED; moduly</h2>\n<p>Testy se v&#x11B;t&#x161;inou nep&#xED;&#x161;ou p&#x159;&#xED;mo ke k&#xF3;du,\nale do souboru vedle.\nJe to tak p&#x159;ehledn&#x11B;j&#x161;&#xED; a taky to pak zjednodu&#x161;uje\ndistribuci &#x2013; p&#x159;ed&#xE1;v&#xE1;n&#xED; k&#xF3;du n&#x11B;komu, kdo ho chce\njen spustit a testy nepot&#x159;ebuje.</p>\n<p>Rozd&#x11B;l soubor s testem se&#x10D;&#xED;t&#xE1;n&#xED;: funkci <code>secti</code> p&#x159;esu&#x148; do modulu <code>secteni.py</code>,\na v&#xA0;<code>test_secteni.py</code> nech jenom test.\nDo <code>test_secteni.py</code> pak na za&#x10D;&#xE1;tek p&#x159;idej <code>from secteni import secti</code>,\naby byla funkce testu k dispozici.</p>\n<p>Test by m&#x11B;l op&#x11B;t proj&#xED;t.</p>\n<h2>Spou&#x161;t&#x11B;c&#xED; moduly</h2>\n<p>Automatick&#xE9; testy mus&#xED; proj&#xED;t &#x201E;bez dozoru&#x201C;.\nV&#xA0;praxi se &#x10D;asto automaticky spou&#x161;t&#xED;, p&#x159;&#xED;padn&#xE9; chyby se automaticky\noznamuj&#xED; (nap&#x159;. e-mailem) a funguj&#xED;c&#xED; k&#xF3;d se automaticky\nza&#x10D;ne pou&#x17E;&#xED;vat d&#xE1;l (nebo se rovnou vyd&#xE1; z&#xE1;kazn&#xED;k&#x16F;m).</p>\n<p>Co to znamen&#xE1; pro n&#xE1;s?\nFunkce <code>input</code> v&#xA0;testech nefunguje. Nem&#xE1; koho by se zeptala; &#x201E;za kl&#xE1;vesnic&#xED;&#x201C;\nnemus&#xED; nikdo sed&#x11B;t.</p>\n<p>To m&#x16F;&#x17E;e n&#x11B;kdy &#x201E;zt&#x11B;&#x17E;ovat pr&#xE1;ci&#x201C;. Uk&#xE1;&#x17E;eme si to na slo&#x17E;it&#x11B;j&#x161;&#xED;m projektu:\nna 1D pi&#x161;kvork&#xE1;ch.</p>\n<div class=\"admonition note\"><p>Nem&#xE1;&#x161;-li hotov&#xE9; 1D pi&#x161;kvorky, n&#xE1;sleduj&#xED;c&#xED; sekce budou jen teorietick&#xE9;.\nU&#x10D;&#xED;&#x161;-li se z domu, dod&#x11B;lej si Pi&#x161;kvorky ne&#x17E; bude&#x161; pokra&#x10D;ovat d&#xE1;l!\nZad&#xE1;n&#xED; najde&#x161; (prozat&#xED;m)\nv&#xA0;<a href=\"http://pyladies.cz/v1/s004-strings/handout/handout4.pdf\">projektech pro PyLadies</a>\nna stran&#x11B; 2.</p>\n</div><p>K&#xF3;d pro 1D Pi&#x161;kvorky m&#x16F;&#x17E;e r&#xE1;mcov&#x11B; vypadat zhruba takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">random</span>  <span class=\"c1\"># (p&#x159;&#xED;p. import jin&#xFD;ch v&#x11B;ci, kter&#xE9; budou pot&#x159;eba)</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">tah</span><span class=\"p\">(</span><span class=\"n\">pole</span><span class=\"p\">,</span> <span class=\"n\">cislo_policka</span><span class=\"p\">,</span> <span class=\"n\">symbol</span><span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;Vr&#xE1;t&#xED; pole s dan&#xFD;m symbolem um&#xED;st&#x11B;n&#xFD;m na danou pozici&quot;&quot;&quot;</span>\n    <span class=\"o\">...</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">tah_hrace</span><span class=\"p\">(</span><span class=\"n\">pole</span><span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;Zept&#xE1; se hr&#xE1;&#x10D;e kam chce hr&#xE1;t a vr&#xE1;t&#xED; pole se zaznamenan&#xFD;m tahem&quot;&quot;&quot;</span>\n    <span class=\"o\">...</span>\n    <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">&apos;Kam chce&#x161; hr&#xE1;t? &apos;</span><span class=\"p\">)</span>\n    <span class=\"o\">...</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">piskvorky1d</span><span class=\"p\">():</span>\n    <span class=\"sd\">&quot;&quot;&quot;Spust&#xED; hru</span>\n\n<span class=\"sd\">    Vytvo&#x159;&#xED; hrac&#xED; pole a st&#x159;&#xED;dav&#x11B; vol&#xE1; tah_hrace a tah_pocitace</span>\n<span class=\"sd\">    dokud n&#x11B;kdo nevyhraje&quot;&quot;&quot;</span>\n    <span class=\"k\">while</span> <span class=\"o\">...</span><span class=\"p\">:</span>\n        <span class=\"o\">...</span>\n        <span class=\"n\">tah_hrace</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n        <span class=\"o\">...</span>\n\n<span class=\"c1\"># Pu&#x161;t&#x11B;n&#xED; hry!</span>\n<span class=\"n\">piskvorky1d</span><span class=\"p\">()</span>\n</pre></div><p>Kdy&#x17E; tenhle modul naimportuje&#x161;, Python v&#xA0;n&#x11B;m postupn&#x11B;, odshora dol&#x16F;,\nprovede v&#x161;echny p&#x159;&#xED;kazy.</p>\n<p>Prvn&#xED; p&#x159;&#xED;kaz, <code>import</code>, jen zp&#x159;&#xED;stupn&#xED; n&#x11B;jak&#xE9; prom&#x11B;nn&#xE9; a funkce;\nje-li importovan&#xFD; modul spr&#xE1;vn&#x11B; napsan&#xFD;, nem&#xE1; vedlej&#x161;&#xED; &#xFA;&#x10D;inek.\nDefinice funkc&#xED; (p&#x159;&#xED;kazy <code>def</code> a v&#x161;echno v&#xA0;nich) podobn&#x11B; jen definuj&#xED; funkce.\nAle zavol&#xE1;n&#xED;m funkce <code>piskvorky1d</code> se spust&#xED; hra:\nfunkce <code>piskvorky1d</code> zavol&#xE1; funkci <code>tah_hrace()</code> a ta zavol&#xE1; <code>input()</code>.</p>\n<p>Importuje&#x161;-li tenhle modul z test&#x16F;, <code>input</code> sel&#x17E;e a import se nepovede.</p>\n<div class=\"admonition note\"><p>A kdybys modul importovala odjinud &#x2013; nap&#x159;&#xED;klad bys cht&#x11B;la funkci\n<code>tah</code> pou&#x17E;&#xED;t v&#xA0;n&#x11B;jak&#xE9; jin&#xE9; h&#x159;e &#x2013; u&#x17E;ivatel si bude muset v&#xA0;r&#xE1;mci importu\nzahr&#xE1;t Pi&#x161;kvorky!</p>\n</div><p>Vol&#xE1;n&#xED; funkce <code>piskvorky1d</code> je vedlej&#x161;&#xED; efekt, a je pot&#x159;eba ho odstranit.\nNo jo, ale po takov&#xE9;m odstran&#x11B;n&#xED;\nu&#x17E; nejde jednodu&#x161;e spustit hra! Co s t&#xED;m?</p>\n<p>M&#x16F;&#x17E;e&#x161; na to vytvo&#x159;it nov&#xFD; modul.\nPojmenuj ho <code>hra.py</code> a dej do n&#x11B;j jenom to odstran&#x11B;n&#xE9; vol&#xE1;n&#xED;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">piskvorky</span>\n\n<span class=\"n\">piskvorky</span><span class=\"o\">.</span><span class=\"n\">piskvorky1d</span><span class=\"p\">()</span>\n</pre></div><p>Tenhle modul nebude&#x161; moci testovat (proto&#x17E;e nep&#x159;&#xED;mo vol&#xE1; funkci <code>input</code>),\nale m&#x16F;&#x17E;e&#x161; ho spustit, kdy&#x17E; si bude&#x161; cht&#xED;t zahr&#xE1;t.\nProto&#x17E;e k&#xA0;n&#x11B;mu nem&#xE1;&#x161; napsan&#xE9; testy, nepozn&#xE1;&#x161;\nz&#xA0;nich, kdy&#x17E; se takov&#xFD; spou&#x161;t&#x11B;c&#xED; modul rozbije.\nM&#x11B;l by b&#xFD;t proto nejjednodu&#x161;&#x161;&#xED; &#x2013; jeden import a jedno vol&#xE1;n&#xED;.</p>\n<p>P&#x16F;vodn&#xED; modul te&#x10F; m&#x16F;&#x17E;e&#x161; importovat bez obav &#x2013; a&#x165; u&#x17E; z&#xA0;test&#x16F; nebo z&#xA0;jin&#xFD;ch\nmodul&#x16F;.\nTest m&#x16F;&#x17E;e vypadat t&#x159;eba takhle:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">piskvorky</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">test_tah_na_prazdne_pole</span><span class=\"p\">():</span>\n    <span class=\"n\">pole</span> <span class=\"o\">=</span> <span class=\"n\">piskvorky</span><span class=\"o\">.</span><span class=\"n\">tah_pocitace</span><span class=\"p\">(</span><span class=\"s1\">&apos;--------------------&apos;</span><span class=\"p\">)</span>\n    <span class=\"k\">assert</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">pole</span><span class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">20</span>\n    <span class=\"k\">assert</span> <span class=\"n\">pole</span><span class=\"o\">.</span><span class=\"n\">count</span><span class=\"p\">(</span><span class=\"s1\">&apos;x&apos;</span><span class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">1</span>\n    <span class=\"k\">assert</span> <span class=\"n\">pole</span><span class=\"o\">.</span><span class=\"n\">count</span><span class=\"p\">(</span><span class=\"s1\">&apos;-&apos;</span><span class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">19</span>\n</pre></div><h2>Pozitivn&#xED; a negativn&#xED; testy</h2>\n<p>Test&#x16F;m, kter&#xE9; kontroluj&#xED; &#x17E;e se program za spr&#xE1;vn&#xFD;ch podm&#xED;nek chov&#xE1; spr&#xE1;vn&#x11B;,\nse &#x159;&#xED;k&#xE1; <em>pozitivn&#xED; testy</em>.\nM&#x16F;&#x17E;e&#x161; ale testovat i reakci programu na &#x161;patn&#xE9; nebo neo&#x10D;ek&#xE1;van&#xE9; podm&#xED;nky.</p>\n<p>Testy, kter&#xE9; kontroluj&#xED; reakci na &#x201E;&#x161;patn&#xFD;&#x201C; vstup,\nse jmenuj&#xED; <em>negativn&#xED; testy</em>.\nM&#x16F;&#x17E;ou kontrolovat n&#x11B;jak&#xFD; negativn&#xED; v&#xFD;sledek (nap&#x159;.\n&#x17E;e vol&#xE1;n&#xED; jako <code>cislo_je_sude(7)</code> vr&#xE1;t&#xED; <code>False</code>),\na nebo to, &#x17E;e nastane &#x201E;rozumn&#xE1;&#x201C; v&#xFD;jimka.</p>\n<p>Nap&#x159;&#xED;klad funkce <code>tah_pocitace</code> by m&#x11B;la zp&#x16F;sobit\nchybu (t&#x159;eba <code>ValueError</code>), kdy&#x17E; je hern&#xED; pole u&#x17E; pln&#xE9;.</p>\n<div class=\"admonition note\"><p>Vyvolat v&#xFD;jimku je mnohem lep&#x161;&#xED; ne&#x17E; alternativy, nap&#x159;. kdyby takov&#xE9; vol&#xE1;n&#xED;\n&#x201E;ti&#x161;e&#x201C; &#x2013; bez ozn&#xE1;men&#xED; &#x2013; zablokovalo cel&#xFD; program.\nKdy&#x17E; k&#xF3;d pak pou&#x17E;ije&#x161; ve v&#x11B;t&#x161;&#xED;m programu,\nm&#x16F;&#x17E;e&#x161; si b&#xFD;t jist&#xE1;, &#x17E;e p&#x159;i &#x161;patn&#xE9;m vol&#xE1;n&#xED;\ndostane&#x161; srozumitelnou chybu &#x2013; tedy takovou,\nkter&#xE1; se co nejsnadn&#x11B;ji opravuje.</p>\n</div><p>Na otestov&#xE1;n&#xED; v&#xFD;jimky pou&#x17E;ij p&#x159;&#xED;kaz <code>with</code> a funkci <code>raises</code> naimportovanou\nz&#xA0;modulu <code>pytest</code>.\nJak p&#x159;&#xED;kaz <code>with</code> p&#x159;esn&#x11B; funguje, se dozv&#xED;me pozd&#x11B;ji;\nte&#x10F; sta&#x10D;&#xED; &#x159;&#xED;ct, &#x17E;e ov&#x11B;&#x159;&#xED;, &#x17E;e odsazen&#xFD; blok k&#xF3;du\npod n&#xED;m vyvol&#xE1; danou v&#xFD;jimku:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">pytest</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">piskvorky</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">test_tah_chyba</span><span class=\"p\">():</span>\n    <span class=\"k\">with</span> <span class=\"n\">pytest</span><span class=\"o\">.</span><span class=\"n\">raises</span><span class=\"p\">(</span><span class=\"ne\">ValueError</span><span class=\"p\">):</span>\n        <span class=\"n\">piskvorky</span><span class=\"o\">.</span><span class=\"n\">tah_pocitace</span><span class=\"p\">(</span><span class=\"s1\">&apos;oxoxoxoxoxoxoxoxoxox&apos;</span><span class=\"p\">)</span>\n</pre></div>\n\n\n        "
    }
  }
}