Gibt es eine saubere Möglichkeit, ein Objekt zu patchen, damit Sie die assert_call*
Helfer in Ihrem Testfall erhalten, ohne die Aktion tatsächlich zu entfernen?
Wie kann ich beispielsweise die @patch
Zeile ändern , um den folgenden Test zu bestehen:
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 dies wahrscheinlich zusammen mit hacken side_effect
, aber ich hatte gehofft, dass es einen schöneren Weg gibt, der bei allen Funktionen, Klassenmethoden, statischen Methoden, ungebundenen Methoden usw. gleich funktioniert.
foo
aufgerufen wurde, da der Test selbst ihn aufruft und nicht irgendein anderer Code, der getestet wird. Ebenso scheintforty_two
das Testen , das von Ihrem Test auf einen bestimmten Wert festgelegt wird , und nicht der getestete Code, von geringem Wert zu sein.spud
in diesem Beispiel) und meine Berufungspud.foo
. Ich mussspud
vonfoo
Anfang an mit einer verspotteten Methode erstellt werden, da ich den Codepfad, derspud
diefoo
Methode erstellt und aufruft, nicht kontrolliere .spy
, dass es in meiner Pytest-Suite nützlich ist.Antworten:
Ähnliche Lösung mit Ihrer, 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)
Laut Dokumentation :
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)
quelle
patch.object(spud, 'foo', wraps=spud.foo)
stattdessen sein sollte, damit der Code die spezifische Instanz patchen würde. In diesem Fall macht es zwar keinen praktischen Unterschied, aber der aktuelle Code wird auf Klassenebene (alle Instanzen) gepatcht, umschließt jedoch eine an eine bestimmte Instanz gebundene Funktion. Ich denke, das könnte jemanden auf der Straße verbrennen.mock
Bibliothek für Python 2.7 (es von docs nicht sofort offensichtlich ist , alswraps
nicht ein dokumentiertes kwarg von istpatch.object
, sondern es wird übergeben , wie**kwargs
inMagicMock
dem es wird dokumentiert)Diese Antwort adressiert die zusätzliche Anforderung, die in der Prämie von Benutzer Quuxplusone erwähnt wird:
Der oben beschriebene Anwendungsfall konnte ohne allzu große Probleme mit einem Dekorateur erreicht werden:
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 ersetzte Methode veränderbare Argumente akzeptiert, die während des Tests geändert werden, möchten Sie möglicherweise ein * anstelle des Inneren des spy_decorator initialisieren.
CopyingMock
MagicMock
* Es ist ein Rezept aus den Dokumenten, die ich auf PyPI als CopyingMock Lib veröffentlicht habe
quelle