Verspotten Sie eine Teilmenge der Methoden und Eigenschaften einer Python-Klasse
Ich verwende das mockPython-Modul zur Durchführung meiner Tests.
Es gibt Zeiten, in denen ich eine Klasse verspotte, aber ich möchte nur einige ihrer Methoden und Eigenschaften verspotten, und nicht alle.
Angenommen, das folgende Szenario:
# 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()
)
SomeClassWenn ich im obigen Code die Klasse gepatcht habe, werden beim Aufrufen von Methoden, deren return_values ich nicht genau festgelegt habe, MagicMockObjekte zurückgegeben.
Meine Frage ist: Wie kann ich nur einige Methoden einer Klasse verspotten, andere aber intakt halten?
Ich kann mir zwei Möglichkeiten vorstellen, aber keine davon ist wirklich gut.
Eine Möglichkeit besteht darin, die Methode des Mocks wie folgt auf die ursprüngliche Klassenmethode festzulegen:
some_class_instance.another_method = SomeClass.another_methodDies ist nicht wirklich wünschenswert, da die Klasse möglicherweise viele Methoden und Eigenschaften zum "Entsperren" hat.
Eine andere Möglichkeit besteht darin, jede gewünschte Methode explizit zu patchen, z.
@patch('module.SomeClass.some_method') def test_some_operation(self, some_method_mock):Dies funktioniert jedoch nicht wirklich, wenn ich die Klasse selbst verspotten möchte, um beispielsweise Aufrufe des Initialisierers zu verspotten. Der folgende Code würde
SomeClasssowieso alle Methoden überschreiben .@patch('module.SomeClass.some_method') @patch('module.SomeClass') def test_some_operation(self, some_class_mock, some_method_mock):
Hier ist ein genaueres Beispiel:
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
In diesem Fall habe ich eine allgemeine Methode, process_eventdie abhängig vom angegebenen Ereignis ein bestimmtes Verarbeitungsereignis aufruft.
Ich möchte nur die Methode testen process_event. Ich möchte nur wissen, ob das richtige Ereignis in Abhängigkeit von dem von mir bereitgestellten Ereignis aufgerufen wird.
In meinem Testfall möchte ich also nur verspotten process_event_aund process_event_bdas Original process_eventmit bestimmten Parametern aufrufen und dann entweder bestätigen process_event_aoder process_event_bwurden mit den richtigen Parametern aufgerufen.
Antworten
Anstatt die gesamte Klasse zu patchen, müssen Sie das Objekt patchen. Erstellen Sie nämlich eine Instanz Ihrer Klasse, und patchen Sie dann die Methoden dieser Instanz.
Beachten Sie, dass Sie @patch.objectanstelle meines Ansatzes auch den Dekorator verwenden können.
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
In deiner 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()
)