2014-09-01 6 views
23

Gibt es eine saubere Möglichkeit, ein Objekt zu patchen, so dass Sie die assert_call* Helfer in Ihrem Testfall erhalten, ohne die Aktion tatsächlich zu entfernen?Python Mock - Patchen einer Methode ohne Behinderung der Implementierung

Zum Beispiel, wie kann ich die @patch Linie ändern Sie den folgenden Test bestanden zu erhalten:

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) 

Ich könnte wahrscheinlich hacken zusammen mit side_effect, aber ich hatte gehofft, es wäre ein schöner Weg sein, die das Werk gleiche Art und Weise auf alle Funktionen, Class, staticmethods, ungebundenen Methoden etc.

+0

'foo' und' bar' sind nicht richtig definiert. Sie sollten 'def foo (self, n)' und 'def bar (self, n)' sein. – chepner

+0

ja, danke ... fixed – wim

+0

Es gibt auch nicht viel zu behaupten, dass 'foo' aufgerufen wurde, da der Test es selbst aufruft, anstatt irgendein anderer Code, der getestet wird. Ebenso scheint das Testen von 'vierzig_zwei 'auf einen bestimmten Wert durch Ihren * Test * und nicht den getesteten Code von geringem Wert zu sein. – chepner

Antwort

15

ähnliche Lösung mit Ihnen, aber mit 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) 

Nach the documentation:

wickelt: Item für das Mock-Objekt zu wickeln. Wenn Wraps nicht None ist, wird durch Aufrufen des Mocks den Aufruf an das umschlossene Objekt übergeben (das echte Ergebnis wird zurückgegeben). Der Attributzugriff auf den Schein liefert ein Mock-Objekt, das das entsprechende Attribut des umgebrochenen -Objekts umschließt (der Versuch, auf ein Attribut zuzugreifen, das nicht existiert, wird einen AttributeError auslösen).


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

danke, das ist etwas netter als meins .. weißt du irgendeinen Weg, es in der Dekorator Verwendung von Patch zu tun, anstatt in der Context-Manager-Nutzung? – wim

+0

@wim, meinst du das? http://pastebin.com/pNyWRBNq – falsetru

+1

nein, weil durch das Erstellen einer neuen Potato-Instanz im Decorator verlieren Sie den Zustand auf dem Objekt, das tatsächlich getestet wird, benötigen Sie die gebundene Methode .. – wim

3

Diese Antwort-Adresse die zusätzliche Anforderung in der Prämie von Benutzern Quuxplusone erwähnt:

Die wichtige Sache für meinen Anwendungsfall ist, dass es mit @patch.mock, dass es also arbeiten nicht verlangen, dass ich Code zwischen meinem Aufbau der Instanz Potato (spud in diesem Beispiel) und meine Berufung von spud.foo einfügen. Ich brauche spud, um mit einer verspotteten foo Methode von Anfang an erstellt zu werden, weil ich den Platz nicht kontrolliere, wo spud geschaffen wird.

die oben beschriebenen Anwendungsfall unter Verwendung eines Dekorateur ohne allzu viel Mühe erreicht werden konnte:

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() 

Wenn die ersetzt Methode wandelbar Argumente akzeptiert, die unter Test geändert werden, können Sie eine CopyingMock initialisieren möchten anstelle der MagicMock im spy_decorator.

Verwandte Themen