Ve druhé části článku věnovaném testování zdrojového kódu aplikací naprogramovaných v Pythonu 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 je vlastně možné v jedné testovací funkci použít větší množství funkcí či metod mockovaných.

Obsah

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

2. Složitější aplikace implementovaná ve větším množství modulů

3. Mockování funkcí volaných nepřímo z jiných modulů

4. Výsledek spuštění testů definovaných v prvním příkladu

5. Úplný zdrojový kód prvního demonstračního příkladu

6. Mockování funkce přímo volané z testů

7. Výsledek spuštění testů definovaných ve druhém příkladu

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

9. Přímé použití konstruktoru patch() v těle testů

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

11. Další možnosti nabízené objekty Mock a MagicMock

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

13. Zjištění kolikrát a s jakými parametry byla mockovaná funkce zavolána

14. Výsledek spuštění testů definovaných v pátém příkladu

15. Úplný zdrojový kód pátého demonstračního příkladu

16. Pořadí předávání mock objektů do funkce s anotací @patch

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

18. Výsledek spuštění testů definovaných v šestém příkladu

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í (2.část)

Ve druhé části dvoudílného [1] článku si popíšeme další možnosti, které jsou 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í (otestová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).

Připomeňme si, že v současnosti existuje relativně velké množství různých knihoven, které mockování v Pythonu umožňují. Z nich jsme si již minule jmenovali projekt Flexmock, který naleznete na adrese https://pypi.python.org/pypi/flexmock. 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).

Poznámka: všechny dále popsané demonstrační příklady byly odzkoušeny společně s Pythonem 3.x (od verze 3.4 včetně), neboť tento interpret je v současných verzích Fedory výchozí. Úprava příkladů pro Python 2.x by však měla být velmi snadná.

2. Složitější aplikace implementovaná ve větším množství modulů

Nejdříve navážeme na předchozí článek a ukážeme si způsob mockování funkcí u složitěji strukturované aplikace, v níž se nachází celkem tři moduly pojmenované jednoduše module1.py, module2.py a module3.py:

module1.py
module2.py
module3.py

V prvním modulu module1 nalezneme funkci nazvanou function1, která pouze na standardní výstup vypíše své jméno a zavolá funkci function2 z modulu module2. Tento modul samozřejmě musíme importovat:

from module2 import *


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

Druhý modul pojmenovaný module2 vypadá podobně jako modul první, ovšem nachází se v něm funkce function2 volající funkci function3 ze třetího modulu:

from module3 import *


def function2():
    print("function2")
    return "function2 " + function3()

Konečně se dostáváme ke třetímu modulu, který je nejjednodušší, protože neobsahuje žádný import, ale pouze implementaci funkce nazvané function3:

def function3():
    print("function3")
    return "function3"

Celá aplikace se spouští z modulu main.py:

from module1 import *


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

Aplikaci si můžeme vyzkoušet například v debuggeru, a to poměrně jednoduše. Nejprve debugger spustíme:

$ <strong>python3 -m pdb main.py </strong>

Průběh spuštění a inicializace:

> /home/tester/temp/python/mocking-in-python/mock-test7/main.py(1)<module>()
-> from module1 import *

Dále nastavíme tzv. breakpoint (bod zastavení), a to na poslední volané funkci function3. Tato funkce není v modulu main.py a tudíž ji musíme klasifikovat celým jménem:

(Pdb) <strong>break module3.function3</strong>
 
Breakpoint 1 at /home/tester/temp/python/mocking-in-python/mock-test7/module3.py:1

Program v debuggeru spustíme:

(Pdb) <strong>continue</strong>
function1
function2
> /home/tester/temp/python/mocking-in-python/mock-test7/module3.py(2)function3()
-> print("function3")

A po jeho zastavení se podíváme, jak vypadají zásobníkové rámce:

(Pdb) <strong>where</strong>
  /usr/lib/python3.4/bdb.py(431)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  /home/tester/temp/python/mocking-in-python/mock-test7/main.py(5)<module>()
-> print(function1())
  /home/tester/temp/python/mocking-in-python/mock-test7/module1.py(6)function1()
-> return "function1 " + function2()
  /home/tester/temp/python/mocking-in-python/mock-test7/module2.py(6)function2()
-> return "function2 " + function3()
> /home/tester/temp/python/mocking-in-python/mock-test7/module3.py(2)function3()
-> print("function3")

3. Mockování funkcí volaných nepřímo z jiných modulů

Nyní si již můžeme ukázat, jakým způsobem lze mockovat funkce volané z jiných modulů. V našem případě by se jednalo o tato volání:

Voláno z Volaná funkce Modul v němž je funkce definována
(testy).py function1 module1.py
module1.py function2 module2.py
module2.py function3 module3.py

Připomeňme si základní pravidlo – při deklaraci mocku musíme uvést plné jméno funkce v kontextu jejího volání, tj. z jakého modulu je funkce volána a nikoli v jakém modulu je definována.

Zkusme si tedy napsat testovací skript test.py. Jeho začátek je „klasický“:

from unittest.mock import *

from module1 import *

První test test1 bude jednoduše volat funkci function1 z modulu module1:

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

Ve druhém testu voláme stejnou funkci, ale současně se pokusíme mockovat funkci function2 volanou z modulu module2 (což ale nebude mít žádný efekt):

@patch('module2.function2', return_value="*mocked*")
def test2(mocked_function):
    print("*** test2 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))

Třetí test již provede mockování skutečně volané funkce. Stále se jedná o funkci function2, ovšem volanou z modulu module1, což je i případ naší „reálné“ aplikace:

@patch('module1.function2', return_value="*mocked*")
def test3(mocked_function):
    print("*** test3 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))

Čtvrtý test posunuje mock o jednu úroveň dále, konkrétně na funkci function3 volanou z modulu module2:

@patch('module2.function3', return_value="*mocked*")
def test4(mocked_function):
    print("*** test4 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))

A konečně se pokusíme taktéž mockovat funkci function3, ovšem tentokrát volanou z modulu module3, což ve skutečnosti nikdy nenastane:

@patch('module3.function3', return_value="*mocked*")
def test5(mocked_function):
    print("*** test5 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))

Poslední část skriptu pouze testy spustí bez dalších triků:

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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

4. Výsledek spuštění testů definovaných v prvním příkladu

Podívejme se nyní na výsledky, které získáme po spuštění testů definovaných v prvním demonstračním příkladu. Vlastní spuštění je jednoduché, protože postačuje spustit skript nazvaný test (ten spustí interpret Pythonu a předá mu skript test.py):

$ <strong>./test</strong>

První test test1 ve skutečnosti žádné mockování nepoužívá, takže se funkce spustí v pořadí module1.function1module2.function2module3.function3:

*** test1 ***
function1
function2
function3
function1 returns: function1 function2 function3

Ve druhém testu jsme se snažili mockovat funkci module2.function2, ovšem ve skutečnosti se tímto způsobem žádná funkce nevolá (voláme sice function2, ovšem v kontextu prvního modulu), takže výsledek bude stejný, jako v příkladu předchozím:

*** test2 ***
function1
function2
function3
function1 returns: function1 function2 function3

Teprve ve třetím testu, v němž byl mock nastaven na „module1.function2“ se namísto funkce module2.function2 použila nastavená návratová hodnota „mocked“. Třetí funkce se pochopitelně vůbec nezavolá:

*** test3 ***
function1
function1 returns: function1 *mocked*

Čtvrtý příklad je obdobný, ovšem s tím rozdílem, že se mock použil namísto funkce module3.function3, ovšem – to je důležité – v kontextu druhého modulu, nikoli modulu třetího:

*** test4 ***
function1
function2
function1 returns: function1 function2 *mocked*

A konečně v posledním testu byla mockována funkce function3 v kontextu modulu module3, což je sice korektní, ovšem v tomto kontextu žádná funkce zavolána není, takže výsledek je následující:

*** test5 ***
function1
function2
function3
function1 returns: function1 function2 function3

5. Úplný zdrojový kód prvního demonstračního příkladu

Pro větší přehlednost je v této kapitole vypsán úplný zdrojový kód dnešního prvního demonstračního příkladu rozděleného do třech modulů tvořících velmi jednoduchou aplikaci a jednoho modulu s testy.

Soubor module1.py

from module2 import *


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

Soubor module2.py

from module3 import *


def function2():
    print("function2")
    return "function2 " + function3()

Soubor module3.py

def function3():
    print("function3")
    return "function3"

Soubor main.py určený pro spuštění aplikace

from module1 import *


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

Soubor test.py s testy

from unittest.mock import *

from module1 import *


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


@patch('module2.function2', return_value="*mocked*")
def test2(mocked_function):
    print("*** test2 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))


@patch('module1.function2', return_value="*mocked*")
def test3(mocked_function):
    print("*** test3 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))


@patch('module2.function3', return_value="*mocked*")
def test4(mocked_function):
    print("*** test4 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))


@patch('module3.function3', return_value="*mocked*")
def test5(mocked_function):
    print("*** test5 ***")
    value = function1()
    print("function1 returns: {v}".format(v=value))


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

6. Mockování funkce přímo volané z testů

V prvním demonstračním příkladu, s nímž jsme se seznámili v předchozích kapitolách, se modul nazvaný module1 do testovacího modulu importoval tímto způsobem:

from module1 import *

To je samozřejmě zcela legální způsob, ovšem má jednu nevýhodu – všechny funkce a třídy z modulu module1 se stanou součástí jmenného prostoru testů, takže se při tvorbě mocků těchto funkcí/tříd dostaneme do zbytečných problémů. Praktičtější bude import nepatrně změnit a použít následující řádek:

import module1

Nyní je již možné velmi jednoduše mockovat funkci function1 volanou v kontextu modulu module1, protože přesně takto k této funkci budeme muset přistupovat:

@patch('module1.function1', return_value="*mocked*")
def test1(mocked_function):
    print("*** test1 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))

Podobným způsobem je možné zabezpečit mockování dalších funkcí, tentokrát již volaných nepřímo. Povšimněte si, že všechny další anotace @patch jsou shodné s prvním demonstračním příkladem:

@patch('module2.function2', return_value="*mocked*")
def test2(mocked_function):
    print("*** test2 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module1.function2', return_value="*mocked*")
def test3(mocked_function):
    print("*** test3 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module2.function3', return_value="*mocked*")
def test4(mocked_function):
    print("*** test4 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module3.function3', return_value="*mocked*")
def test5(mocked_function):
    print("*** test5 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))

I spuštění testů je pochopitelně stejné (zde se nic nezměnilo):

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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

7. Výsledek spuštění testů definovaných ve druhém příkladu

Pokud druhý demonstrační příklad spustíme, zjistíme snadno, že je skutečně možné mockovat i funkci pojmenovanou function1 z modulu module1 (viz první dva řádky výpisu):

*** test1 ***
function1 returns: *mocked*

Další zprávy vypisované v testech jsou již shodné s prvním příkladem:

*** test2 ***
function1
function2
function3
function1 returns: function1 function2 function3

*** test3 ***
function1
function1 returns: function1 *mocked*

*** test4 ***
function1
function2
function1 returns: function1 function2 *mocked*

*** test5 ***
function1
function2
function3
function1 returns: function1 function2 function3

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

V této kapitole si opět pro přehlednost ukažme výpis zdrojových kódů všech modulů dnešního druhého demonstračního příkladu.

Soubor module1.py

from module2 import *


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

Soubor module2.py

from module3 import *


def function2():
    print("function2")
    return "function2 " + function3()

Soubor module3.py

def function3():
    print("function3")
    return "function3"

Soubor main.py určený pro spuštění aplikace

from module1 import *


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

Soubor test.py s testy

from unittest.mock import *

import module1


@patch('module1.function1', return_value="*mocked*")
def test1(mocked_function):
    print("*** test1 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module2.function2', return_value="*mocked*")
def test2(mocked_function):
    print("*** test2 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module1.function2', return_value="*mocked*")
def test3(mocked_function):
    print("*** test3 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module2.function3', return_value="*mocked*")
def test4(mocked_function):
    print("*** test4 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


@patch('module3.function3', return_value="*mocked*")
def test5(mocked_function):
    print("*** test5 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

9. Přímé použití konstruktoru patch() v těle testů

V některých případech není možné vystačit s tím, že se před testovací funkci napíše anotace @patch. Můžeme se totiž dostat do situace, kdy budeme jednou potřebovat zavolat funkci původní a jindy (v tom samém testu) mock této funkce. I toto chování je samozřejmě podporováno, protože namísto nám již známého zápisu:

@patch('module1.function1', return_value="*mocked*")
def test1(mocked_function):
    print("*** test1 ***")
    value = module1.function1()
    print("function1 returns: {v}".format(v=value))

Je možné použít přímé volání funkce patch importované z modulu unittest. Typicky se volání funkce patch používá společně s řídicí konstrukcí with, která omezuje kontext, v němž je mock platný. Podívejme se na příklad:

def test1():
    with patch("module1.function1", return_value="*mocked"):
        print("*** test1 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))

Podobným způsobem lze zapsat i další testovací funkce – viz výpis zdrojového kódu příkladu, který je uveden v navazující kapitole. Výsledky testů budou shodné s výsledky předchozího příkladu:

*** test1 ***
function1 returns: *mocked

*** test2 ***
function1
function2
function3
function1 returns: function1 function2 function3

*** test3 ***
function1
function1 returns: function1 *mocked

*** test4 ***
function1
function2
function1 returns: function1 function2 *mocked

*** test5 ***
function1
function2
function3
function1 returns: function1 function2 function3

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

Opět si pro přehlednost ukažme zdrojové kódy všech modulů třetího demonstračního příkladu.

Soubor module1.py

from module2 import *


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

Soubor module2.py

from module3 import *


def function2():
    print("function2")
    return "function2 " + function3()

Soubor module3.py

def function3():
    print("function3")
    return "function3"

Soubor main.py určený pro spuštění aplikace

from module1 import *


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

Soubor test.py s testy

from unittest.mock import *

import module1


def test1():
    with patch("module1.function1", return_value="*mocked"):
        print("*** test1 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))


def test2():
    with patch("module2.function2", return_value="*mocked"):
        print("*** test2 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))


def test3():
    with patch("module1.function2", return_value="*mocked"):
        print("*** test3 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))


def test4():
    with patch("module2.function3", return_value="*mocked"):
        print("*** test4 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))


def test5():
    with patch("module3.function3", return_value="*mocked"):
        print("*** test5 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

11. Další možnosti nabízené objekty Mock a MagicMock

Již z předchozího článku víme, že je velmi snadné zjistit, jestli je mockovaná funkce volána či nikoli. To lze samozřejmě provést i ve chvíli, kdy nepoužijeme anotaci @patch, ale namísto toho přímo zavoláme funkci patch v bloku with. Co ovšem musíme doplnit je jméno proměnné obsahující referenci na mock. Tato reference je vrácena funkcí patch, ovšem kvůli jejímu volání v bloku with je syntaxe zápisu nepatrně odlišná:

with patch("jméno_modulu.jméno_funkce") as jméno_proměnné_s_referencí_na mock:
    ...
    ...
    ...

Ve chvíli, kdy máme referenci na mock, můžeme zjistit, zda byl volán přečtením vlastnosti called. Příklad použití pro první test:

def test1():
    with patch("module1.function1") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test1 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))

I zbylé testy budou vypadat podobně – viz úplný zdrojový kód příkladu vypsaný v navazující kapitole. Podívejme se ještě na výsledky těchto testů.

*** test1 ***
function1 returns: *mocked*
mocked function called: True

U druhého testu k volání mocku nedošlo (což již nepřímo víme z výsledné hodnoty vrácené funkcí function1):

*** test2 ***
function1
function2
function3
function1 returns: function1 function2 function3
mocked function called: False

V dalších dvou testech se mockovaná funkce volala:

*** test3 ***
function1
function1 returns: function1 *mocked*
mocked function called: True

*** test4 ***
function1
function2
function1 returns: function1 function2 *mocked*
mocked function called: True

V posledním testu však již opět ne:

*** test5 ***
function1
function2
function3
function1 returns: function1 function2 function3
mocked function called: False

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

Opět si pro přehlednost ukažme zdrojové kódy všech modulů tvořících čtvrtý demonstrační příklad.

Soubor module1.py

from module2 import *


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

Soubor module2.py

from module3 import *


def function2():
    print("function2")
    return "function2 " + function3()

Soubor module3.py

def function3():
    print("function3")
    return "function3"

Soubor main.py určený pro spuštění aplikace

from module1 import *


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

Soubor test.py s testy

from unittest.mock import *

import module1


def test1():
    with patch("module1.function1") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test1 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))


def test2():
    with patch("module2.function2") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test2 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))


def test3():
    with patch("module1.function2") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test3 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))


def test4():
    with patch("module2.function3") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test4 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))


def test5():
    with patch("module3.function3") as mocked_function:
        mocked_function.return_value = "*mocked*"

        print("*** test5 ***")
        value = module1.function1()
        print("function1 returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

    test5()
    print()

13. Zjištění kolikrát a s jakými parametry byla mockovaná funkce zavolána

Další vlastnost je při testování taktéž nemálo užitečná. Zavoláním mock.assert_called_with(hodnota1, hodnota2, ...) je totiž možné zjistit, zda vůbec a s jakými parametry je mock zavolán. To však není zdaleka vše, protože ve vlastnosti mock_calls je uložena sekvence všech volání. Z této sekvence zjistíme jak pořadí volání mocku, tak i hodnoty parametrů, které byly při volání použity. V dalším testu budeme mockovat tuto funkci:

def add(x, y):
    return add_implementation(x, y)

První test může vypadat následovně – nejdříve mock zavoláme, následně vypíšeme informaci o tom, zda byl skutečně zavolán, posléze otestujeme, jestli se použili parametry 1 a 2 (popř. při druhém volání 100 a 100) a konečně si necháme vypsat seznam všech volání dané funkce:

def test1():
    print("*** test1 ***")

    with patch("module1.add_implementation") as mocked_function:
        mocked_function.return_value = 42

        value = module1.add(1, 2)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(1, 2)

        value = module1.add(100, 100)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(100, 100)

        print("calls: ", mocked_function.mock_calls)

Naproti tomu druhý test je schválně napsán takovým způsobem, aby se při kontrole zjistilo, že mock byl sice volán, ale s neočekávanými parametry. Ve skutečnosti totiž voláme funkci s parametry 1 a 2, ale očekáváme volání s parametry 1 a 1:

def test2():
    print("*** test2 ***")

    with patch("module1.add_implementation") as mocked_function:
        mocked_function.return_value = 42

        value = module1.add(1, 2)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(1, 1)

14. Výsledek spuštění testů definovaných v pátém příkladu

Nejzajímavější je samozřejmě výsledek spuštění testů. V prvním testu se skutečně mockovaná funkce volala, a to dokonce dvakrát – poprvé s parametry 1, 2, podruhé s parametry 100, 100:

*** test1 ***
add returns: 42
mocked function called: True
add returns: 42
mocked function called: True
calls:  [call(1, 2), call(100, 100)]

U druhého testu však dojde k pádu, protože se mock nezavolal ani jednou, což kontrola velmi rychle odhalí:

*** test2 ***
add returns: 42
mocked function called: True
Traceback (most recent call last):
  File "test.py", line 41, in 
    test2()
  File "test.py", line 34, in test2
    mocked_function.assert_called_with(1, 1)
  File "/usr/lib/python3.4/unittest/mock.py", line 771, in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: add_implementation(1, 1)
Actual call: add_implementation(1, 2)

15. Úplný zdrojový kód pátého demonstračního příkladu

Opět si pro přehlednost ukažme zdrojové kódy všech modulů tvořících dnešní předposlední demonstrační příklad.

Soubor module1.py

from module2 import *


def add(x, y):
    return add_implementation(x, y)

Soubor module2.py

def add_implementation(x, y):
    return x + y

Soubor main.py určený pro spuštění aplikace

from module1 import *


if __name__ == '__main__':
    print(add(1,0))
    print(add(1,1))
    print(add(1,2))

Soubor test.py s testy

from unittest.mock import *

import module1


def test1():
    print("*** test1 ***")

    with patch("module1.add_implementation") as mocked_function:
        mocked_function.return_value = 42

        value = module1.add(1, 2)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(1, 2)

        value = module1.add(100, 100)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(100, 100)

        print("calls: ", mocked_function.mock_calls)


def test2():
    print("*** test2 ***")

    with patch("module1.add_implementation") as mocked_function:
        mocked_function.return_value = 42

        value = module1.add(1, 2)
        print("add returns: {v}".format(v=value))
        print("mocked function called: {c}".format(c=mocked_function.called))
        mocked_function.assert_called_with(1, 1)


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

    test2()
    print()

16. Pořadí předávání mock objektů do funkce s anotací @patch

Na závěr se ještě musíme zmínit o další vlastnosti anotace @patch. Pořadí předání reference na mockované funkce je totiž přesně opačné, než je pořadí zápisu anotací! Jinými slovy – anotace zapsaná nejblíže definici funkce odpovídá prvnímu parametru, anotace o řádek výše parametru druhému atd.

Test s dvěma mocky tedy bude začínat následovně:

@patch("module1.f1", name="f1", return_value=1)
@patch("module1.f2", name="f2", return_value=2)
def test3(mocked_f2, mocked_f1):
    ...
    ...
    ...

Test se třemi mocky bude vypadat takto:

@patch("module1.f1", name="f1", return_value=1)
@patch("module1.f2", name="f2", return_value=2)
@patch("module1.f3", name="f3", return_value=3)
def test4(mocked_f3, mocked_f2, mocked_f1):
    ...
    ...
    ...

Takto deklarované mocky jsou součástí šestého a současně i dnešního posledního demonstračního příkladu, jehož kód je uveden níže.

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

Před závěrem celého článku si ještě ukažme úplný zdrojový kód dnešního šestého a současně i posledního demonstračního příkladu.

Soubor module1.py

from module2 import *


def compute(x, y, z):
    return f1(x) + f2(y) + f3(z)

Soubor module2.py

def f1(x):
    return x

def f2(x):
    return 2 * x

def f3(x):
    return 3 * x

Soubor main.py určený pro spuštění aplikace

from module1 import *


if __name__ == '__main__':
    print(compute(1, 2, 3))

Soubor test.py s testy

from unittest.mock import *

from module1 import *


def test1():
    print("*** test1 ***")

    value = compute(1, 2, 3)
    print("compute returns: {v}".format(v=value))


def mock_call_info(mocked_function):
    print("mocked function '{n}' called: {c}".format(n=mocked_function._mock_name,
                                                     c=mocked_function.called))
    print("calls: ", mocked_function.mock_calls)
    print()


@patch("module1.f1", name="f1", return_value=0)
def test2(mocked_f1):
    print("*** test1 ***")

    value = compute(10, 20, 30)
    print("compute returns: {v}".format(v=value))

    mock_call_info(mocked_f1)


@patch("module1.f1", name="f1", return_value=0)
@patch("module1.f2", name="f2", return_value=0)
def test3(mocked_f2, mocked_f1):
    print("*** test3 ***")

    value = compute(1, 2, 3)
    print("compute returns: {v}".format(v=value))

    mock_call_info(mocked_f1)
    mock_call_info(mocked_f2)


@patch("module1.f1", name="f1", return_value=0)
@patch("module1.f2", name="f2", return_value=0)
@patch("module1.f3", name="f3", return_value=0)
def test4(mocked_f3, mocked_f2, mocked_f1):
    print("*** test4 ***")

    value = compute(100, 200, 300)
    print("compute returns: {v}".format(v=value))

    mock_call_info(mocked_f1)
    mock_call_info(mocked_f2)
    mock_call_info(mocked_f3)


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

    test2()
    print()

    test3()
    print()

    test4()
    print()

18. Výsledek spuštění testů definovaných v šestém příkladu

Pokud poslední příklad spustíte příkazem ./test, měly by se na standardní výstup vypsat následující řádky:

*** test1 ***
compute returns: 14

*** test2 ***
compute returns: 130
mocked function 'f1' called: True
calls:  [call(10)]


*** test3 ***
compute returns: 9
mocked function 'f1' called: True
calls:  [call(1)]

mocked function 'f2' called: True
calls:  [call(2)]


*** test4 ***
compute returns: 0
mocked function 'f1' called: True
calls:  [call(100)]

mocked function 'f2' called: True
calls:  [call(200)]

mocked function 'f3' called: True
calls:  [call(300)]

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:

Projekt Cesta
mock-test7 https://github.com/tisnik/mocking-in-python/blob/master/mock-test7
mock-test8 https://github.com/tisnik/mocking-in-python/blob/master/mock-test8
mock-test9 https://github.com/tisnik/mocking-in-python/blob/master/mock-test9
mock-testA https://github.com/tisnik/mocking-in-python/blob/master/mock-testA
mock-testB https://github.com/tisnik/mocking-in-python/blob/master/mock-testB
mock-testC https://github.com/tisnik/mocking-in-python/blob/master/mock-testC

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. Behavior-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Behavior-driven_development
  10. Mock object (Wikipedia)
    https://en.wikipedia.org/wiki/Mock_object
  11. Unit testing (Wikipedia)
    https://en.wikipedia.org/wiki/Unit_testing
  12. Unit testing
    https://cs.wikipedia.org/wiki/Unit_testing
  13. Extrémní programování
    https://cs.wikipedia.org/wiki/Extr%C3%A9mn%C3%AD_programov%C3%A1n%C3%AD
  14. Programování řízené testy
    https://cs.wikipedia.org/wiki/Programov%C3%A1n%C3%AD_%C5%99%C3%ADzen%C3%A9_testy
  15. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  16. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  17. Tox
    https://tox.readthedocs.io/en/latest/
  18. Cucumber
    https://cucumber.io/
  19. Jasmine
    https://jasmine.github.io/
  20. IPython Qt Console aneb vylepšený pseudoterminál
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06
  21. Vývojová prostředí ve Fedoře (4. díl)
    https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/
  22. pytest: helps you write better programs
    https://docs.pytest.org/en/latest/
  23. doctest — Test interactive Python examples
    https://docs.python.org/dev/library/doctest.html#module-doctest
  24. Python mock by example
    http://www.alexandrejoseph.com/blog/2015-08-21-python-mock-example.html
  25. unittest — Unit testing framework
    https://docs.python.org/dev/library/unittest.html
  26. Python namespaces
    https://bytebaker.com/2008/07/30/python-namespaces/
  27. Namespaces and Scopes
    https://www.python-course.eu/namespaces.php
  28. pdb — The Python Debugger
    https://docs.python.org/3.6/library/pdb.html
  29. pdb – Interactive Debugger
    https://pymotw.com/2/pdb/