2014-09-01 13 views
23

Czy istnieje czysty sposób na łatanie obiektu, aby uzyskać pomocników assert_call* w teście, bez faktycznego usuwania działania?Python mock - łatanie metody bez utrudniania implementacji

Na przykład, w jaki sposób można zmodyfikować linię @patch aby uzyskać następujący test Podania:

from unittest import TestCase 
from mock import patch 


class Potato(object): 
    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 


class PotatoTest(TestCase): 

    @patch.object(Potato, 'foo') 
    def test_something(self, mock): 
     spud = Potato() 
     forty_two = spud.foo(n=40) 
     mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 

mógłbym prawdopodobnie włamywanie tym razem przy użyciu side_effect, ale miałem nadzieję, że będzie ładniejszy sposób, który działa w ten sam sposób na wszystkich funkcji, classmethods, staticmethods, metod niezwiązanych itp

+0

'foo' i' bar' nie są zdefiniowane poprawnie. Powinny to być 'def foo (self, n)' i 'def bar (self, n)'. – chepner

+0

tak, dziękuję ... naprawiono – wim

+0

Nie ma też sensu twierdzić, że 'foo' został wywołany, ponieważ sam test go wywołuje, a nie jakiś inny testowany kod. Podobnie, testowanie, że 'czterdzieści dwa' jest ustawione na konkretną wartość przez twój * test *, a nie testowany kod, wydaje się mieć niewielką wartość. – chepner

Odpowiedz

15

Podobne rozwiązanie z Ciebie, ale przy użyciu wraps:

def test_something(self): 
    spud = Potato() 
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock: 
     forty_two = spud.foo(n=40) 
     mock.assert_called_once_with(n=40) 
    self.assertEqual(forty_two, 42) 

Według the documentation:

owija: pozycja dla atrapa obiektu zawinąć. Jeśli wraps nie jest Brak, wówczas wywołanie Mock spowoduje przekazanie wywołania do owiniętego obiektu (zwracającego prawdziwy wynik). Przypisanie dostępu do makiety zwróci obiekt Mock, który zawinie odpowiedni atrybut owiniętego obiektu (więc próba uzyskania dostępu do nieistniejącego atrybutu spowoduje podniesienie AttributeError).


class Potato(object): 

    def spam(self, n): 
     return self.foo(n=n) 

    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 


class PotatoTest(TestCase): 

    def test_something(self): 
     spud = Potato() 
     with patch.object(Potato, 'foo', wraps=spud.foo) as mock: 
      forty_two = spud.spam(n=40) 
      mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 
+0

dziękuję, to jest trochę ładniejsze niż moje .. czy znasz jakiś sposób to zrobić w użyciu dekoratora poprawki zamiast w użyciu kontekstu menedżera? – wim

+0

@wim, Czy masz na myśli to? http://pastebin.com/pNyWRBNq – falsetru

+1

nie, ponieważ tworząc nową instancję Ziemia w dekoratorze, tracisz stan na obiekcie, który jest aktualnie testowany, potrzebujesz związanej metody .. – wim

3

Ten adres odpowiedź dodatkowy wymóg wymieniony w laski z Quuxplusone użytkownika:

Ważną rzeczą dla mojego użytkowej przypadku jest to, że praca z @patch.mock, to znaczy, że to nie wymaga od mnie wstawiania żadnego kodu pomiędzy moją konstrukcją instancji Potato (spud w tym przykładzie) i moim wywołaniem z spud.foo. Potrzebuję spud, aby utworzyć z użyciem wyłudzonej metody foo od początku, ponieważ nie kontroluję miejsca, w którym jest tworzone spud.

przypadku użycia opisanego powyżej może zostać osiągnięty bez większych problemów za pomocą dekoratora:

import unittest 
import unittest.mock # Python 3 

def spy_decorator(method_to_decorate): 
    mock = unittest.mock.MagicMock() 
    def wrapper(self, *args, **kwargs): 
     mock(*args, **kwargs) 
     return method_to_decorate(self, *args, **kwargs) 
    wrapper.mock = mock 
    return wrapper 

def spam(n=42): 
    spud = Potato() 
    return spud.foo(n=n) 

class Potato(object): 

    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 

class PotatoTest(unittest.TestCase): 

    def test_something(self): 
     foo = spy_decorator(Potato.foo) 
     with unittest.mock.patch.object(Potato, 'foo', foo): 
      forty_two = spam(n=40) 
     foo.mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 


if __name__ == '__main__': 
    unittest.main() 

Jeśli metoda zastąpiona akceptuje zmienne argumentów, które zostały zmodyfikowane pod testu, może chcesz zainicjować CopyingMock w miejsce MagicMock wewnątrz spy_decorator.