23 listopada 2013

python(3) - mock-i w testach jednostkowych

Python - czasami dziwie się jak coś z tak chujową dokumentacją może istnieć, no ale ... gdyby problemy nie istniały nie było by czego rozwiązywać. Swoje eksperymenty oparłem na tym linku:
Bawiłem się jedynie MagicMock-iem, chociaż w bibliotece istnieje coś takiego jak Mock. Poniżej trzy, testy do trzech metod, których przetestowanie sprawiło mi problemy. Na początku ważna kwestia, MagicMock jest klasą, więc tworząc obiekt mock-a, należy pamiętać o nawiasach, aby wywołać konstruktor! Za pomocą parametru spec, wskazujemy też klasę, z które zaczerpnięty zostanie interfejs mock-owanej klasy.

Pierwszy z testów (test_show), miał wykazać sekwencyjne zawołanie metody Deciratir.rich(). Robi się to za pomocą dziwnego obiektu call, którego nasyca się atrybutami z jakimi zostanie wywołana mock-owana metoda. Inna kwestia to wartość zwracana. Za pomocą pola return_value, można ustalić jaka wartość będzie zawsze zwracana z mocka.

Drugi test, pokazuje w działaniu jedną z metod (asercji), dostarczoną przez bibliotekę. Tutaj oczekujemy, że metoda zostanie zawołana tylko raz z określonym parametrem (assert_called_once_with).

Trzeci test, również bada, czy mock-i zostały zawołane z określonymi parametrami, ale tym razem, każdy z nich zwraca inną wartość, aby przetestować bardzo specyficzną ścieżkę wykonania programu. Służy temu pole side_effect, z którego pobierane są kolejne zwracane wartości (można też podobna włożyć tam metody, które zostaną zawołane).
#!/usr/bin/env python3
# python -m unittest discover --pattern=test_unittest.py

import unittest
from unittest.mock import MagicMock
from unittest.mock import call

class TestPrinter(unittest.TestCase):
    def setUp(self):
        self.decorMock = MagicMock(spec=Decorator, name='decorator')()

    def test_show(self):
        self.decorMock.rich.return_value = True

        sut = Printer(self.decorMock)
        sut.show('asdf')
        expected = [call.decorMock.attribute.rich('asdf'),
                    call.decorMock.attribute.rich('xxx')]
        self.assertTrue(self.decorMock.rich.mock_calls == expected)

    def test_show_once(self):
        sut = Printer(self.decorMock)
        sut.show_once()
        self.decorMock.rich.assert_called_once_with('once')

    def test_show_comlicated(self):
        self.decorMock.rich.side_effect = [False, True]

        sut = Printer(self.decorMock)
        self.assertEqual(sut.show_complicated(), 2)
        expected = [call.decorMock.attribute.rich('a'),
                    call.decorMock.attribute.rich('b')]
        self.assertTrue(self.decorMock.rich.mock_calls == expected)

class Decorator():
    def rich(self, descr):
        raise Exception()

class Printer():
    def __init__(self, decorator):
        self._decorator = decorator

    def show(self, descr):
        self._decorator.rich(descr)
        if self._decorator.rich('xxx'):
            return True
        return False

    def show_once(self):
        self._decorator.rich('once')

    def show_complicated(self):
        if self._decorator.rich('a'):
            return 1
        if self._decorator.rich('b'):
            return 2
        return 0
Z otwartych kwestii, wciąż nie wiem jak testować dowolne parametry przekazywane do mock-a. Testowanie biblioteki do testów trwa nadal.

Brak komentarzy:

Prześlij komentarz