Mengolok-olok kelas: Mock () atau patch ()?

116

Saya menggunakan tiruan dengan Python dan bertanya-tanya mana dari dua pendekatan itu yang lebih baik (baca: lebih banyak pythonic).

Metode satu : Buat saja objek tiruan dan gunakan itu. Kode tersebut terlihat seperti:

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)

Metode dua : Gunakan tambalan untuk membuat tiruan. Kode tersebut terlihat seperti:

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

Kedua metode tersebut melakukan hal yang sama. Saya tidak yakin dengan perbedaannya.

Adakah yang bisa mencerahkan saya?

Sardathrion - melawan penyalahgunaan SE
sumber
10
Sebagai orang yang belum pernah mencoba Mock () atau patch, saya merasa versi pertama lebih jelas dan menunjukkan apa yang ingin Anda lakukan, meskipun saya tidak memahami perbedaan sebenarnya. Saya tidak tahu apakah ini membantu atau tidak, tetapi saya pikir mungkin berguna untuk menyampaikan apa yang mungkin dirasakan oleh programmer yang belum tahu.
Michael Brennan
2
@MichaelBrennan: Terima kasih atas komentar Anda. Ini memang berguna.
Sardathrion - terhadap penyalahgunaan SE

Jawaban:

151

mock.patchadalah makhluk yang sangat berbeda dari mock.Mock. patch mengganti kelas dengan objek tiruan dan memungkinkan Anda bekerja dengan contoh tiruan. Lihat cuplikan ini:

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

patchmenggantikan MyClassdengan cara yang memungkinkan Anda mengontrol penggunaan kelas dalam fungsi yang Anda panggil. Setelah Anda menambal kelas, referensi ke kelas tersebut sepenuhnya diganti dengan contoh tiruan.

mock.patchbiasanya digunakan saat Anda menguji sesuatu yang membuat instance baru dari kelas di dalam pengujian. mock.Mockcontoh lebih jelas dan lebih disukai. Jika self.sut.somethingmetode Anda membuat instance MyClassalih - alih menerima instance sebagai parameter, maka mock.patchakan sesuai di sini.

D. Shawley
sumber
2
@ D. Shawley bagaimana kita menambal ke kelas yang dibuat di dalam kelas lain yang perlu diuji.
ravi404
4
@ravz - berikan pembacaan "Where to Patch" . Ini adalah salah satu hal yang lebih sulit untuk bekerja dengan baik.
D. Shawley
Tes tiruan saya mirip dengan Metode dua . Saya ingin instance MyClass memunculkan pengecualian. Saya telah mencoba mock.side_effect dan mock.return_value.side_effect dan tidak berhasil. Apa yang saya lakukan?
Hussain
5
@ D.Shawley Tautan rusak, sekarang dapat ditemukan di sini: "Di mana Menambal"
RazerM
2
Untuk menambal objek kelas lihat stackoverflow.com/questions/8469680/…
storm_m2138
27

Saya punya video YouTube tentang ini.

Jawaban singkat: Gunakan mockketika Anda menyampaikan hal yang Anda ingin diejek, dan patchjika tidak. Dari keduanya, tiruan sangat disukai karena itu berarti Anda menulis kode dengan injeksi ketergantungan yang tepat.

Contoh konyol:

# 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
sumber