Eine Klasse verspotten: Mock () oder Patch ()?

116

Ich verwende Mock mit Python und habe mich gefragt, welcher dieser beiden Ansätze besser ist (sprich: mehr Python).

Methode eins : Erstellen Sie einfach ein Scheinobjekt und verwenden Sie dieses. Der Code sieht aus wie:

def test_one (self):
    mock = Mock()
    mock.method.return_value = True 
    self.sut.something(mock) # This should called mock.method and checks the result. 
    self.assertTrue(mock.method.called)

Methode zwei : Verwenden Sie Patch, um ein Modell zu erstellen. Der Code sieht aus wie:

@patch("MyClass")
def test_two (self, mock):
    instance = mock.return_value
    instance.method.return_value = True
    self.sut.something(instance) # This should called mock.method and checks the result. 
    self.assertTrue(instance.method.called)

Beide Methoden machen dasselbe. Ich bin mir der Unterschiede nicht sicher.

Könnte mich jemand aufklären?

Sardathrion - gegen SE-Missbrauch
quelle
10
Als Person, die weder Mock () noch Patch ausprobiert hat, ist die erste Version meiner Meinung nach klarer und zeigt, was Sie tun möchten, obwohl ich den tatsächlichen Unterschied nicht verstehe. Ich weiß nicht, ob dies hilfreich ist oder nicht, aber ich dachte, es könnte nützlich sein, zu vermitteln, was ein nicht eingeweihter Programmierer fühlen könnte.
Michael Brennan
2
@ MichaelBrennan: Danke für deinen Kommentar. Es ist in der Tat nützlich.
Sardathrion - gegen SE Missbrauch

Antworten:

151

mock.patchist ein ganz anderes Tier als mock.Mock. patch Ersetzt die Klasse durch ein Scheinobjekt und lässt Sie mit der Scheininstanz arbeiten. Schauen Sie sich diesen Ausschnitt an:

>>> class MyClass(object):
...   def __init__(self):
...     print 'Created MyClass@{0}'.format(id(self))
... 
>>> def create_instance():
...   return MyClass()
... 
>>> x = create_instance()
Created MyClass@4299548304
>>> 
>>> @mock.patch('__main__.MyClass')
... def create_instance2(MyClass):
...   MyClass.return_value = 'foo'
...   return create_instance()
... 
>>> i = create_instance2()
>>> i
'foo'
>>> def create_instance():
...   print MyClass
...   return MyClass()
...
>>> create_instance2()
<mock.Mock object at 0x100505d90>
'foo'
>>> create_instance()
<class '__main__.MyClass'>
Created MyClass@4300234128
<__main__.MyClass object at 0x100505d90>

patchErsetzt MyClassauf eine Weise, mit der Sie die Verwendung der Klasse in von Ihnen aufgerufenen Funktionen steuern können. Sobald Sie eine Klasse patchen, werden Verweise auf die Klasse vollständig durch die Scheininstanz ersetzt.

mock.patchwird normalerweise verwendet, wenn Sie etwas testen, das eine neue Instanz einer Klasse innerhalb des Tests erstellt. mock.MockInstanzen sind klarer und werden bevorzugt. Wenn Ihre self.sut.somethingMethode eine Instanz von erstellt hat, MyClassanstatt eine Instanz als Parameter zu empfangen, mock.patchwäre dies hier angemessen.

D. Shawley
quelle
2
@ D.Shawley Wie patchen wir auf eine Klasse, die in einer anderen Klasse instanziiert ist, die getestet werden muss?
Ravi404
4
@ravz - lies das "Where to Patch" . Dies ist eines der schwierigeren Dinge, um richtig zur Arbeit zu kommen.
D. Shawley
Mein Mock-Test ähnelt Methode zwei . Ich möchte, dass die MyClass-Instanz eine Ausnahme auslöst. Ich habe sowohl mock.side_effect als auch mock.return_value.side_effect ausprobiert und diese haben nicht funktioniert. Was mache ich?
Hussain
5
@ D.Shawley Der Link ist defekt, er kann jetzt hier gefunden werden: "Where to Patch"
RazerM
2
So patchen Sie a Informationen zum
Patchen
27

Ich habe ein YouTube-Video dazu.

Kurze Antwort: Verwenden mockSie diese Option, wenn Sie das Objekt übergeben, das Sie verspotten möchten, undpatch wenn Sie wenn Sie dies nicht tun. Von den beiden wird Mock stark bevorzugt, da dies bedeutet, dass Sie Code mit der richtigen Abhängigkeitsinjektion schreiben.

Dummes Beispiel:

# Use a mock to test this.
my_custom_tweeter(twitter_api, sentence):
    sentence.replace('cks','x')   # We're cool and hip.
    twitter_api.send(sentence)

# Use a patch to mock out twitter_api. You have to patch the Twitter() module/class 
# and have it return a mock. Much uglier, but sometimes necessary.
my_badly_written_tweeter(sentence):
    twitter_api = Twitter(user="XXX", password="YYY")
    sentence.replace('cks','x') 
    twitter_api.send(sentence)
MikeTwo
quelle