Verspotten Sie eine Teilmenge der Methoden und Eigenschaften einer Python-Klasse

Oct 31 2020

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.

  1. Eine Möglichkeit besteht darin, die Methode des Mocks wie folgt auf die ursprüngliche Klassenmethode festzulegen:

    some_class_instance.another_method = SomeClass.another_method
    

    Dies ist nicht wirklich wünschenswert, da die Klasse möglicherweise viele Methoden und Eigenschaften zum "Entsperren" hat.

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

1 AmirAfianian Oct 31 2020 at 21:14

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