Při testování aplikací, zejména při psaní jednotkových testů, se poměrně často dostaneme do situace, kdy potřebujeme nahradit nějakou funkci či metodu používanou v reálné aplikaci za „falešnou“ funkci resp. metodu vytvořenou pouze pro účely testů. V programovacím jazyku Python je možné pro tvorbu a použití takových „falešných“ funkcí 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. 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.

Obsah

1. Python pro vývojáře: použití knihovny unittest.mock (nejenom) při testování

2. Zdrojový soubor s funkcí, kterou budeme mockovat

3. Test s volanou i mockovanou funkcí

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

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

6. Úplný zdrojový kód druhého demonstračního příkladu

7. Otestování, zda byla mockovaná funkce zavolána

8. Úplný zdrojový kód třetího demonstračního příkladu

9. Problematika mockování funkce, která je volaná nepřímo

10. Mockování funkce volané nepřímo

11. Úplný zdrojový kód čtvrtého demonstračního příkladu

12. Pátý příklad – změna kódu v případě, že použijeme import a nikoli from X import

13. Mockování metod

14. Testovaná třída

15. Nepřímé volání metody, kterou budeme mockovat

16. Mockování nepřímo volané metody

17. Úplný zdrojový kód šestého demonstračního příkladu

18. Obsah druhé části článku

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

20. Odkazy na Internetu

1. Python pro vývojáře: použití knihovny unittest.mock (nejenom) při testování

S následující situací se již setkal pravděpodobně každý vývojář – 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í, a příslušný náhradní kód pak test double. V dnešním č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, který se ve Fedoře stal již před poměrně dlouhou dobou standardem.

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/pypi/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).

2. Zdrojový soubor s funkcí, kterou budeme mockovat

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 jediným souborem se zdrojovým kódem application.py. Tento soubor obsahuje jedinou funkci nazvanou 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á pouhé tři řádky:

def function1():
    print("function1 called")
    return "tested function"

Spuštění je snadné:

python3 application.py

3. Test s volanou i mockovanou funkcí

Nyní se podívejme na to, jakým způsobem se může funkce function1 volat v 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())

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í kapitoly). 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).

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

4. 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.

5. 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_value i side_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())

Pokud 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 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ě.

6. Úplný zdrojový kód druhého demonstračního příkladu

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í

def function1():
    print("function1 called")
    return "tested function"

Soubor test.py s testy

from unittest.mock import *

import application


def test1():
    print(application.function1())


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


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


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


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


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

7. Otestování, zda byla mockovaná funkce zavolána

Mnohdy, zejména při testování složitěji strukturovaného programového kódu s mnoha podmínkami, je důležité zjistit, zda se vůbec mockovaná funkce zavolala. A právě v těchto případech nám přijde vhod objekt, který je do testovací funkce automaticky předáván, v našem případě v prvním parametru (jméno tohoto parametru si můžeme vybrat sami, důležité je pouze znát jeho pořadí/index):

@patch('application.function1', side_effect=side_effect_handler)
def test3(mocked_function_object):

Tento objekt obsahuje mj. i vlastnost (property) pojmenovanou jednoduše called. Ve výchozím stavu je tato vlastnost nastavena na hodnotu False, ale po prvním zavolání mockované funkce se vlastnost nastaví na hodnotu True. Ostatně se o tomto chování můžeme přesvědčit, a to velmi snadno – vypíšeme hodnotu vlastnosti called před vlastním voláním mockované funkce a taktéž ihned po tomto volání. Upravený test bude vypadat následovně:

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("mocked function called: {c}".format(c=mocked_function_object.called))
    print(application.function1())
    print("mocked function called: {c}".format(c=mocked_function_object.called))

Po zavolání testovací funkce test3 by se na standardní výstup měla vypsat následující sekvence zpráv:

mocked function called: False
side_effect function called
-1
mocked function called: True

Podobně tomu bude v případě, že kombinujeme vlastní handler se specifikací návratové hodnoty (handler je tedy zavolán, jeho volání je korektně zaregistrováno, ovšem nakonec se použije programátorem specifikovaná návratová hodnota):

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):
    print("mocked function called: {c}".format(c=mocked_function.called))
    print(application.function1())
    print("mocked function called: {c}".format(c=mocked_function.called))

Výsledky:

mocked function called: False
side_effect function called
42
mocked function called: True

8. Úplný zdrojový kód třetího demonstračního příkladu

Pro přehlednost si v této kapitole opět ukážeme úplný zdrojový kód dnešního třetího demonstračního příkladu rozděleného do dvou modulů. Funkce tohoto příkladu byla popsána v předchozí kapitole.

Soubor application.py s testovanou funkcí

def function1():
    print("function1 called")
    return "tested function"

Soubor test.py

from unittest.mock import *

import application


def test1():
    print(application.function1())


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


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("mocked function called: {c}".format(c=mocked_function_object.called))
    print(application.function1())
    print("mocked function called: {c}".format(c=mocked_function_object.called))


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


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("mocked function called: {c}".format(c=mocked_function_object.called))
    print(application.function1())
    print("mocked function called: {c}".format(c=mocked_function_object.called))


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

9. Problematika mockování funkce, která je volaná nepřímo

Nyní se dostáváme k velmi důležité vlastnosti knihovny unittest.mock, kterou je užitečné správně pochopit (už jen z toho důvodu, že dokumentace tuto vlastnost podle mého názoru nepopisuje do všech podrobností, o čemž svědčí časté dotazy na fórech). Týká se to způsobu určení plného jména funkce, která má být nahrazena svojí „falešnou“ variantou. Nejdříve si naschvál zkomplikujeme kód aplikace, kterou budeme testovat. Nyní budou aplikaci tvořit dvě funkce nazvané function1 a function2, přičemž si povšimněte, že se z funkce function1 volá funkce function2 (konkrétně v rámci příkazu return, ale to v tomto případě není tak důležité):

def function1():
    print("function1 called")
    return function2()

def function2():
    print("function2 called")
    return "function 2"

10. Mockování funkce volané nepřímo

Komplikace nastanou ve chvíli, kdy budeme potřebovat nahradit funkci function2 za její falešnou variantu. Tuto funkci totiž nebudeme volat přímo z testu, ale pouze nepřímo – přes vlastní kód aplikace. A právě v tomto okamžiku se projevuje již zmíněná vlastnost – do anotace @patch je nutné uvést jméno funkce tak, jak ji vidí volající kód. Co to pro nás znamená? Funkce function2 je volána z funkce pojmenované function1 a přitom k tomuto volání dochází v modulu application. Plné jméno mockované funkce tedy bude znít application.function2, což jen náhodou odpovídá stejnému jménu, jakoby se funkce volala přímo z testů. Podívejme se na příklady testů.

Nejprve změníme import testovaného modulu takovým způsobem, aby nebylo nutné při volání funkcí z modulu application používat celé jméno tohoto modulu s tečkou:

from unittest.mock import *

from application import *

Můžeme si otestovat, že se funkce z modulu application mohou volat přímo (jsou totiž naimportovány do aktuálního jmenného prostoru):

def test1():
    print("function1 returns: {v}".format(v=function1()))

Výsledek:

function1 called
function2 called
function1 returns: function 2

Správné určení funkce, která se má mockovat, bude vypadat následovně – funkce je totiž volána nepřímo v modulu application a nikoli v modulu test (který je aktuálně nastaven):

@patch('application.function2', return_value=42)
def test2(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print("function1 returns: {v}".format(v=function1()))
    print("mocked function called: {c}".format(c=mocked_function_object.called))

Výsledek:

mocked function called: False
function1 called
function1 returns: 42  < zde se volá mock namísto "pravé" funkce function2
mocked function called: True

Jakmile známe správné jméno funkce, můžeme samozřejmě použít i její falešnou verzi, například následovně:

def side_effect_handler():
    print("side_effect_handler function called")
    return -1


@patch('application.function2', side_effect=side_effect_handler)
def test3(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print(function1())
    print("mocked function called: {c}".format(c=mocked_function_object.called))

Výsledek:

mocked function called: False
function1 called
side_effect_handler function called
-1
mocked function called: True

11. Úplný zdrojový kód čtvrtého demonstračního příkladu

Podobně, jako tomu bylo v předchozích kapitolách, si uvedeme úplný zdrojový kód dnešního čtvrtého demonstračního příkladu, jenž je opět složen ze dvou souborů – application.py a test.py.

Soubor application.py s testovanou funkcí

def function1():
    print("function1 called")
    return function2()

def function2():
    print("function2 called")
    return "function 2"

Soubor test.py

from unittest.mock import *

from application import *


def test1():
    print("function1 returns: {v}".format(v=function1()))


@patch('application.function2', return_value=42)
def test2(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print("function1 returns: {v}".format(v=function1()))
    print("mocked function called: {c}".format(c=mocked_function_object.called))


def side_effect_handler():
    print("side_effect_handler function called")
    return -1


@patch('application.function2', side_effect=side_effect_handler)
def test3(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print(function1())
    print("mocked function called: {c}".format(c=mocked_function_object.called))


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

    test2()
    print()

    test3()
    print()

12. Pátý příklad – změna kódu v případě, že použijeme import a nikoli from X import

Pátý příklad je založen na stejném kódu jako příklad čtvrtý, ovšem s tím podstatným rozdílem, že se modul application importuje do testů nikoli příkazem:

from application import *

ale:

import application

To vede k tomu, že přímé volání funkcí z modulu application je nutné provádět s uvedením jména modulu. Samotné testy a dokonce ani obsah anotace @patch se ovšem žádným způsobem nezmění. Testy nyní budou vypadat následovně:

from unittest.mock import *

import application


def test1():
    print("function1 returns: {v}".format(v=application.function1()))


@patch('application.function2', return_value=42)
def test2(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print("function1 returns: {v}".format(v=application.function1()))
    print("mocked function called: {c}".format(c=mocked_function_object.called))


def side_effect_handler():
    print("side_effect_handler function called")
    return -1


@patch('application.function2', side_effect=side_effect_handler)
def test3(mocked_function_object):
    print("mocked function called: {c}".format(c=mocked_function_object.called))
    print("function1 returns: {v}".format(v=application.function1()))
    print("mocked function called: {c}".format(c=mocked_function_object.called))


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

    test2()
    print()

    test3()
    print()

Výsledek běhu testů již dokážeme dobře předvídat:

function1 called
function2 called
function1 returns: function 2

mocked function called: False
function1 called
function1 returns: 42
mocked function called: True

mocked function called: False
function1 called
side_effect_handler function called
function1 returns: -1
mocked function called: True

13. Mockování metod

Prakticky stejným způsobem, jakým jsme vytvářeli „falešné“ varianty běžných funkcí, je možné nahrazovat metody vybraných tříd. Ostatně v Pythonu není mezi funkcemi a metodami tak velký rozdíl, jako v některých jiných programovacích jazycích, které před programátory skrývají implicitní parametr self či this. V následujících čtyřech kapitolách si ukážeme vytvoření jednoduchého mocku nepřímo volané (nestatické) metody.

14. Testovaná třída

Třída, kterou budeme testovat, je opět velmi jednoduchá. Kromě konstruktoru obsahuje i dvě metody nazvané method1 a method2, přičemž druhá metoda je automaticky volána z metody první (opět v příkazu return, což ovšem není podstatné):

class Application:

    def __init__(self):
        pass

    def method1(self):
        print("method1 called")
        return self.method2()

    def method2(self):
        print("method2 called")
        return "method 2"

Poznámka: ve skutečnosti obě metody nepřistupují k žádnému atributu objektu, takže by se z nich mohly stát statické metody pomocí anotace @staticmethod; alternativně i třídní metody. Nicméně pro jednoduchost zatím uvažujme o nestatických metodách, kterým se při jejich volání předává i parametr self.

15. Nepřímé volání metody, kterou budeme mockovat

Samotný modul s testem bude začínat přesně tak, jak jsme zvyklí, tj. importem třídy unittest.mock i třídy Application uložené v modulu application:

from unittest.mock import *

from application import Application

První test prozatím žádnou mockovanou funkci nepoužívá, pouze je v něm ukázán způsob volání metody method1 společně s výpisem návratové hodnoty této metody:

def test1():
    app = Application()
    print("method1 returns: {v}".format(v=app.method1()))

Po spuštění tohoto testu by se na standardní výstup měly vypsat následující řádky oznamující, že se z první metody volá metoda druhá:

method1 called
method2 called
method1 returns: method 2

16. Mockování nepřímo volané metody

Nyní se již dostáváme k popisu mockování metod. Opět si musíme uvědomit, že je zapotřebí zadat plné jméno metody, a to podle toho, v jakém kontextu bude metoda volána. Vzhledem k tomu, že mockujeme metodu se jménem Application.method2, která bude volána nepřímo (nikoli z testu, ale z první metody method1), bude plné jméno mockované metody znít „application.Application.method2“.

Zkusme si tento test napsat:

@patch('application.Application.method2', return_value=42)
def test2(mocked_method):
    app = Application()
    print("mocked method called: {c}".format(c=mocked_method.called))
    print("method1 returns: {v}".format(v=app.method1()))
    print("mocked method called: {c}".format(c=mocked_method.called))

Výsledek po spuštění testu:

mocked method called: False
method1 called
method1 returns: 42
mocked method called: True

Vzhledem k tomu, že vlastnosti mockovaných funkcí a metod jsou prakticky stejné, můžeme metodu nahradit jiným handlerem atd. atd.:

def side_effect_handler():
    print("side_effect_handler method called")
    return -1


@patch('application.Application.method2', side_effect=side_effect_handler)
def test3(mocked_method):
    app = Application()
    print("mocked method called: {c}".format(c=mocked_method.called))
    print("method1 returns: {v}".format(v=app.method1()))
    print("mocked method called: {c}".format(c=mocked_method.called))

Výsledek po spuštění testu:

mocked method called: False
method1 called
side_effect_handler method called
method1 returns: -1
mocked method called: True

17. Úplný zdrojový kód šestého demonstračního příkladu

Na závěr si ukažme úplný zdrojový kód dnešního šestého demonstračního příkladu.

Soubor application.py s testovanou třídou Application

class Application:

    def __init__(self):
        pass

    def method1(self):
        print("method1 called")
        return self.method2()

    def method2(self):
        print("method2 called")
        return "method 2"

Soubor test.py s testy třídy Application

from unittest.mock import *

from application import Application


def test1():
    app = Application()
    print("method1 returns: {v}".format(v=app.method1()))


@patch('application.Application.method2', return_value=42)
def test2(mocked_method):
    app = Application()
    print("mocked method called: {c}".format(c=mocked_method.called))
    print("method1 returns: {v}".format(v=app.method1()))
    print("mocked method called: {c}".format(c=mocked_method.called))


def side_effect_handler():
    print("side_effect_handler method called")
    return -1


@patch('application.Application.method2', side_effect=side_effect_handler)
def test3(mocked_method):
    app = Application()
    print("mocked method called: {c}".format(c=mocked_method.called))
    print("method1 returns: {v}".format(v=app.method1()))
    print("mocked method called: {c}".format(c=mocked_method.called))


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

    test2()
    print()

    test3()
    print()

18. Obsah druhé části článku

Ve druhé části tohoto článku si popíšeme další možnosti nabízené knihovnou unittest.mock. Zejména si ukážeme použití tříd nazvaných Mock a MagicMock, seznámíme se s možnostmi zjištění, zda jsou mockované funkce volány s očekávanými parametry a samozřejmě nezapomeneme ani na velmi důležitou „maličkost“ – jakým způsobem se mockování použije společně s frameworkem pytest (protože právě při skutečném testování se mockování funkcí a metod provádí nejčastěji).

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

Zdrojové kódy všech šesti dnes popsaných demonstračních příkladů (každý společně s pomocným skriptem určeným pro spuštění testů) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/mocking-in-python. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem prozatím velmi malý, doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

ProjektCesta
mock-test1https://github.com/tisnik/mocking-in-python/blob/master/mock-test1/
mock-test2https://github.com/tisnik/mocking-in-python/blob/master/mock-test2/
mock-test3https://github.com/tisnik/mocking-in-python/blob/master/mock-test3/
mock-test4https://github.com/tisnik/mocking-in-python/blob/master/mock-test4/
mock-test5https://github.com/tisnik/mocking-in-python/blob/master/mock-test5/
mock-test6https://github.com/tisnik/mocking-in-python/blob/master/mock-test6/

20. Odkazy na Internetu

  1. unittest.mock — mock object library
    https://docs.python.org/3.5/library/unittest.mock.html
  2. mock 2.0.0
    https://pypi.python.org/pypi/mock
  3. An Introduction to Mocking in Python
    https://www.toptal.com/python/an-introduction-to-mocking-in-python
  4. Mock – Mocking and Testing Library
    http://mock.readthedocs.io/en/stable/
  5. Python Mocking 101: Fake It Before You Make It
    https://blog.fugue.co/2016-02-11-python-mocking-101.html
  6. Nauč se Python! – Testování
    http://naucse.python.cz/lessons/intro/testing/
  7. Flexmock (dokumentace)
    https://flexmock.readthedocs.io/en/latest/
  8. Test Fixture (Wikipedia)
    https://en.wikipedia.org/wiki/Test_fixture
  9. Mock object (Wikipedia)
    https://en.wikipedia.org/wiki/Mock_object
  10. Unit testing (Wikipedia)
    https://en.wikipedia.org/wiki/Unit_testing
  11. Unit testing
    https://cs.wikipedia.org/wiki/Unit_testing
  12. Extrémní programování
    https://cs.wikipedia.org/wiki/Extr%C3%A9mn%C3%AD_programov%C3%A1n%C3%AD
  13. Programování řízené testy
    https://cs.wikipedia.org/wiki/Programov%C3%A1n%C3%AD_%C5%99%C3%ADzen%C3%A9_testy
  14. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  15. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  16. Tox
    https://tox.readthedocs.io/en/latest/
  17. IPython Qt Console aneb vylepšený pseudoterminál
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06
  18. Vývojová prostředí ve Fedoře (4. díl)
    https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/
  19. pytest: helps you write better programs
    https://docs.pytest.org/en/latest/
  20. doctest — Test interactive Python examples
    https://docs.python.org/dev/library/doctest.html#module-doctest
  21. unittest — Unit testing framework
    https://docs.python.org/dev/library/unittest.html
  22. Python namespaces
    https://bytebaker.com/2008/07/30/python-namespaces/
  23. Namespaces and Scopes
    https://www.python-course.eu/namespaces.php