Assert, funkcja / metoda nie została wywołana przy użyciu Mocka


136

Używam biblioteki Mock do testowania mojej aplikacji, ale chcę zapewnić, że jakaś funkcja nie została wywołana. Mock doktorzy mówią o metodach takich jak mock.assert_called_withi mock.assert_called_once_with, ale nie znalazłem niczego podobnego mock.assert_not_calledlub związanego z weryfikacją, że mock NIE został wywołany .

Mógłbym użyć czegoś takiego jak poniżej, chociaż nie wydaje się to fajne ani pytoniczne:

def test_something:
    # some actions
    with patch('something') as my_var:
        try:
            # args are not important. func should never be called in this test
            my_var.assert_called_with(some, args)
        except AssertionError:
            pass  # this error being raised means it's ok
    # other stuff

Jakieś pomysły, jak to osiągnąć?


Jak @Ahmet wskazuje w swojej odpowiedzi, funkcja assert_not_called jest teraz obsługiwana, również w backport ( docs.python.org/3/library/ ... ).
Martin

Odpowiedzi:


149

To powinno działać w Twoim przypadku;

assert not my_var.called, 'method should not have been called'

Próba;

>>> mock=Mock()
>>> mock.a()
<Mock name='mock.a()' id='4349129872'>
>>> assert not mock.b.called, 'b was called and should not have been'
>>> assert not mock.a.called, 'a was called and should not have been'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: a was called and should not have been

Czy ta odpowiedź wymaga Django? Pojawia się błąd:AttributeError: MockCallable instance has no attribute 'called'
Nathan Arthur

@NathanArthur Hm, nie sądzę, po sudo easy_install -U mocki from mock import Mockna MacOS, powyższe działa bez problemu. Nigdy nie instalowałem Django :)
Joachim Isaksson

Hmm. To dziwne. Używam Pythona 2.7.1 i używam unittest oraz from mock import MockPython Mock 0.1.0 do moich testów. Czy coś z tego brzmi problematycznie?
Nathan Arthur

Kpię z wywoływanej klasy z innego modułu, więc wygląda na to module_to_test.another_module.class = mock.Mock(), czy możesz potwierdzić, że to nie zapamiętuje wywołań w różnych przypadkach testowych (instancje unittest.TestCase)? Myślę, że licznik połączeń nie resetuje się w tym przypadku
0xc0de

71

Chociaż stare pytanie, chciałbym dodać, że obecnie mockbiblioteka (backport unittest.mock) obsługuje assert_not_calledmetodę.

Po prostu zaktualizuj swój;

pip install mock --upgrade


29

Możesz sprawdzić calledatrybut, ale jeśli Twoja asercja się nie powiedzie, następną rzeczą, którą będziesz chciał wiedzieć, jest coś o nieoczekiwanym wywołaniu, więc równie dobrze możesz zorganizować wyświetlanie tych informacji od samego początku. Używając unittest, możesz sprawdzić zawartość call_args_listzamiast:

self.assertItemsEqual(my_var.call_args_list, [])

Gdy to się nie powiedzie, wyświetla następujący komunikat:

AssertionError: Liczba elementów nie była równa:
Pierwsza ma 0, druga ma 1: call ('pierwszy argument', 4)

14

Podczas testowania przy użyciu klasy dziedziczy unittest.TestCase możesz po prostu użyć metod takich jak:

  • assertTrue
  • assertFalse
  • assertEqual

i podobne (w dokumentacji Pythona znajdziesz resztę).

W naszym przykładzie możemy po prostu stwierdzić, czy właściwość mock_method.called ma wartość False , co oznacza, że ​​ta metoda nie została wywołana.

import unittest
from unittest import mock

import my_module

class A(unittest.TestCase):
    def setUp(self):
        self.message = "Method should not be called. Called {times} times!"

    @mock.patch("my_module.method_to_mock")
    def test(self, mock_method):
        my_module.method_to_mock()

        self.assertFalse(mock_method.called,
                         self.message.format(times=mock_method.call_count))

12

Ze python >= 3.5można użyć mock_object.assert_not_called().


1

Sądząc po innych odpowiedziach, nikt oprócz @ rob-kennedy nie mówił o call_args_list.

To potężne narzędzie, dzięki któremu możesz wdrożyć dokładnie odwrotnie niż MagicMock.assert_called_with()

call_args_listto lista callobiektów. Każdy callobiekt reprezentuje wywołanie wykonane na fałszywym wywołaniu.

>>> from unittest.mock import MagicMock
>>> m = MagicMock()
>>> m.call_args_list
[]
>>> m(42)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42)]
>>> m(42, 30)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42), call(42, 30)]

Korzystanie z callobiektu jest łatwe, ponieważ można go porównać z krotką o długości 2, w której pierwszy składnik jest krotką zawierającą wszystkie argumenty pozycyjne powiązanego wywołania, podczas gdy drugi składnik jest słownikiem słów kluczowych arguments.

>>> ((42,),) in m.call_args_list
True
>>> m(42, foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((42,), {'foo': 'bar'}) in m.call_args_list
True
>>> m(foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((), {'foo': 'bar'}) in m.call_args_list
True

Tak więc sposobem rozwiązania konkretnego problemu PO jest

def test_something():
    with patch('something') as my_var:
        assert ((some, args),) not in my_var.call_args_list

Zauważ, że w ten sposób, zamiast po prostu sprawdzać, czy wywołano fałszywe wywołanie, MagicMock.calledmożesz teraz sprawdzić, czy zostało wywołane z określonym zestawem argumentów.

To przydatne. Powiedzmy, że chcesz przetestować funkcję, która pobiera listę i wywołuje inną funkcję compute(), dla każdej wartości listy tylko wtedy, gdy spełniają określony warunek.

Możesz teraz kpić computei sprawdzić, czy została wywołana na jakiejś wartości, ale nie na innych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.