Mock subset dari metode dan properti kelas Python
Saya menggunakan mockmodul Python untuk melakukan pengujian saya.
Ada kalanya saya mengejek kelas, namun saya hanya ingin mengejek beberapa metode dan propertinya, dan tidak semuanya.
Misalkan skenario berikut ini:
# module.py
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
# test.py
class Tests(unittest.TestCase):
@patch('module.SomeClass')
def test_some_operation(self, some_class_mock):
some_class_instance = some_class_mock.return_value
# I'm mocking only the some_method method.
some_class_instance.some_method.return_value = 25
# This is ok, the specific method I mocked returns the value I wished.
self.assertEquals(
25,
SomeClass().some_method()
)
# However, another_method, which I didn't mock, returns a MagicMock instance
# instead of the original value 500
self.assertEquals(
500,
SomeClass().another_method()
)
Pada kode di atas, setelah saya menambal SomeClasskelas, panggilan ke metode yang return_values-nya tidak saya setel secara eksplisit akan mengembalikan MagicMockobjek.
Pertanyaan saya adalah: Bagaimana saya bisa mengejek hanya beberapa metode kelas tetapi menjaga yang lain tetap utuh?
Ada dua cara yang bisa saya pikirkan, tetapi tidak ada yang benar-benar baik.
Salah satu caranya adalah dengan menyetel metode tiruan ke metode kelas asli, seperti ini:
some_class_instance.another_method = SomeClass.another_methodIni tidak benar-benar diinginkan karena kelas mungkin memiliki banyak metode dan properti untuk "melepaskan".
Cara lain adalah dengan menambal setiap metode yang saya inginkan secara eksplisit, seperti:
@patch('module.SomeClass.some_method') def test_some_operation(self, some_method_mock):Tetapi ini tidak benar-benar berfungsi jika saya ingin mengejek kelas itu sendiri, untuk mengejek panggilan ke penginisialisasi misalnya. Kode di bawah ini akan menimpa semua
SomeClassmetode.@patch('module.SomeClass.some_method') @patch('module.SomeClass') def test_some_operation(self, some_class_mock, some_method_mock):
Berikut adalah contoh yang lebih spesifik:
class Order:
def process_event(self, event, data):
if event == 'event_a':
return self.process_event_a(data)
elif event == 'event_b':
return self.process_event_b(data)
else:
return None
def process_event_a(self, data):
# do something with data
def process_event_b(self, data):
# do something different with data
Dalam hal ini, saya memiliki metode umum process_eventyang memanggil acara pemrosesan tertentu tergantung pada acara yang disediakan.
Saya hanya ingin menguji metodenya process_event. Saya hanya ingin tahu apakah acara khusus yang tepat dipanggil tergantung pada acara yang saya sediakan.
Jadi, dalam kasus pengujian saya, yang ingin saya lakukan adalah mengejek adil process_event_adan process_event_b, memanggil yang asli process_eventdengan parameter tertentu, lalu menegaskan salah satu process_event_aatau process_event_bdipanggil dengan parameter yang tepat.
Jawaban
Alih-alih menambal seluruh kelas, Anda harus menambal objek. Yaitu, buat instance kelas Anda, lalu tambal metode dari instance itu.
Perhatikan bahwa Anda juga dapat menggunakan dekorator @patch.objectdaripada pendekatan saya.
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
Di Anda test.py
from unittest import mock
class Tests(unittest.TestCase):
def test_some_operation(self):
some_class_instance = SomeClass()
# I'm mocking only the some_method method.
with mock.patch.object(some_class_instance, 'some_method', return_value=25) as cm:
# This is gonna be ok
self.assertEquals(
25,
SomeClass().some_method()
)
# The other methods work as they were supposed to.
self.assertEquals(
500,
SomeClass().another_method()
)