Jak używać Python’owej biblioteki Mock #1: Podstawy

Problem z plikami i bazami danych w Unit Testach? Pythonowa Biblioteka Mock może w czasie rzeczywistym, zmieniać działanie dowolnego obiektu.

Jak używać Python’owej biblioteki Mock

Jak używać Python’owej biblioteki Mock


Podczas pisania unit testów (choć może i nie tylko), są metody, które obsługują pliki czy bazy danych, które nie są obiektem testów, ale wpływają na rzeczy testowane. Unikamy przecież tworzenia „przykładowych” plików czy przykładowych ograniczonych baz danych, które prócz testów też potrzebują utrzymania. W dobie feke news’ów można też używać fake obiektów. Biblioteka Mock może w czasie rzeczywistym, symulować działanie dowolnego obiektu, co może bardzo ułatwić testowanie w pyTest’cie.

Instalacja

Mock jest ogólnodostępną biblioteką, więc można ją zainstalować przy użyciu komendy pip install:

pip install mock

W pliku, w którym będzie używany obiektu Mock, importujemy bibliotekę w poniższy sposób:

import unittest.mock

Przygotowanie

Przed rozpoczęciem dodawania Mock’a do testów stworzymy klasę TestObject, i metodę s_method wpływającą na jej działanie, którą magicznie obsłużymy w testach. 

def s_method():
    return False
 
class TestObject:

    @staticmethod
    def testmethod():
        if s_method():
            return True
        return False

Tylko zbyt głośno się nie śmiej, widząc tę klasę. Ale spokojnie, tworzę tak prosty kod w tym przypadku nie bez powodu. Chcę pokazać w jak najklarowniejszy sposób jak używać Mocka. W teście, który teraz zostanie napisany, oczekiwać będziemy, że testmethod zwróci nam True, pomimo że jest zależna od metody s_method.

def test_mock():
    assert TestObject().test_method()

Wynik jest mocno niezadowalający. Nie chcemy testować s_method samej w sobie, gdyż interesuje nas, tylko to co wydarzy się, jeśli metoda ta będzie równa True. Jednak jak na razie ta ścieżka jest dla nas niedostępna:

____________________________________________________________ test_mock ____________________________________________________________
 
 
def test_mock():
> assert TestObject().testmethod()
E assert False
E + where False = >()
E + where > = <test_example.TestObjec
t object at 0x04209808>.testmethod
E + where = TestObject()
 
 
test_example.py:17: AssertionError
================================================= 1 failed, 1 deselected in 0.20s =================================================

Rozwiązanie problemu

I można by pomyśleć, że to sytuacja bez wyjścia, bo cóż poradzisz, skoro te wartości są wręcz „zahardcodowane„. Poniżej bardzo proste rozwiązanie problemu:

from unittest.mock import patch, MagicMock
 
 
@patch.object(TestObject, 'testmethod', MagicMock(return_value=True))
def test_mock():
    assert TestObject().testmethod()
 

Jak używać Python’owej biblioteki Mock #1 instalacja
Poziom trudności
2/5
Podczas pisania unit testów (choć może i nie tylko), są metody, które obsługują pliki czy bazy danych, które nie są obiektem testów, ale wpływają na rzeczy testowane. Unikamy przecież tworzenia „przykładowych” plików czy przykładowych ograniczonych baz danych, które prócz testów też potrzebują utrzymania. W dobie feke news’ów można też używać fake obiektów. Biblioteka Mock może w czasie rzeczywistym, symulować działanie dowolnego obiektu, co może bardzo ułatwić testowanie w pyTest’cie.

Instalacja
Mock jest ogólnodostępną biblioteką, więc można ją zainstalować przy użyciu komendy pip install:

pip install mock
W pliku, w którym będzie używany obiektu Mock, importujemy bibliotekę w poniższy sposób:

import unittest.mock
Przygotowanie
Przed rozpoczęciem dodawania Mock’a do testów stworzymy klasę TestObject, i metodę s_method wpływającą na jej działanie, którą magicznie obsłużymy w testach. 

def s_method():
    return False
 
 
class TestObject:
 
 
    @staticmethod
    def testmethod():
        if s_method():
            return True
        return False
Tylko zbyt głośno się nie śmiej, widząc tę klasę. Ale spokojnie, tworzę tak prosty kod w tym przypadku nie bez powodu. Chcę pokazać w jak najklarowniejszy sposób jak używać Mocka. W teście, który teraz zostanie napisany, oczekiwać będziemy, że testmethod zwróci nam True, pomimo że jest zależna od metody s_method.

def test_mock():
    assert TestObject().test_method()
Wynik jest mocno niezadowalający. Nie chcemy testować s_method samej w sobie, gdyż interesuje nas, tylko to co wydarzy się, jeśli metoda ta będzie równa True. Jednak jak na razie ta ścieżka jest dla nas niedostępna:

____________________________________________________________ test_mock ____________________________________________________________
 
 
def test_mock():
> assert TestObject().testmethod()
E assert False
E + where False = >()
E + where > = <test_example.TestObjec
t object at 0x04209808>.testmethod
E + where = TestObject()
 
 
test_example.py:17: AssertionError
================================================= 1 failed, 1 deselected in 0.20s =================================================
Rozwiązanie problemu
I można by pomyśleć, że to sytuacja bez wyjścia, bo cóż poradzisz, skoro te wartości są wręcz „zahardcodowane„. Poniżej bardzo proste rozwiązanie problemu:

from unittest.mock import patch, MagicMock
 
 
@patch.object(TestObject, 'testmethod', MagicMock(return_value=True))
def test_mock():
    assert TestObject().testmethod()
 
======================================================= test session starts =======================================================
platform win32 -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: D:\test_delete\unit_tests
plugins: cov-2.8.1
collected 2 items / 1 deselected / 1 selected
 
 
test_example.py . [100%]
 
 
================================================= 1 passed, 1 deselected in 0.17s =================================================

Więcej sposobów użycia biblioteki Mock zaprezentuję w kolejnych częściach. Tu użyłem dekoratora. Jest to jeden z wygodniejszych sposobów, gdyż odnosimy się tu do konkretnego testu i nie wpływamy na resztę testów. W pierwszej kolejności, wybieramy w nim , jaki obiekt ma zostać zmieniony, wpisując jego nazwę bez inicjalizacji. Następnie wpisujemy nazwę wybranej metody jako string, a ostatnim argumentem jest funkcja, na jaką chcemy zamienić metodę z poprzedniego argumentu. Można użyć dowolnej innej funkcji lub lambdy, jednak najprościej użyć magii, a dokładniej MagicMock(). Jest to metoda wbudowana w bibliotekę. W metodzie tej (jak na przykładzie powyżej) możemy, na przykład wybrać co zostanie zwrócone za pomocą argumentu return_value. Kod pozostaje czytelny (w odróżnieniu od lambdy), a efekt sami możecie podziwiać poniżej. W moim przykładzie chciałem nadpisać funkcję s_method, aby przetestować pozostałą część metody testmethod. Oczywiście pisząc unit testy nie możemy zapomnieć o testowaniu wszystkich ścieżek, gdzie s_method przyjmuje wartość False.

Sukces, Mock potrafi odmienić nie tylko wyniki metod, ale i życie testera ;). Już w następnej odsłonie serii Jak używać Python’owej biblioteki Mockzamokujemy coś bardziej rzeczywistego. Będzie to nieistniejący, lecz wymagany przez klasę plik.

close

Newsletter