Python-Mock-Klassenattribut

Salman Mehmood 21 Juni 2023
  1. Grund, ein Klassenattribut zu verspotten
  2. Mögliche Lösungen zum Mocken eines Klassenattributs
  3. Python-Mock-Klassenkonstruktor
Python-Mock-Klassenattribut

Das Hauptziel dieses Artikels besteht darin, zu demonstrieren, wie man ein Klassenattribut mit dem Python-Unit-Testing-Modul unittest zu Test- und Debugging-Zwecken manipuliert.

Grund, ein Klassenattribut zu verspotten

Das Testen des entwickelten Codes auf Bugs, Fehler und Sonderfälle ist einer der wichtigsten Aspekte bei der Entwicklung einer Anwendung, vor allem, wenn die Anwendung für mehrere Benutzer bestimmt ist.

Mit dem eingebauten Python-Modul unittest können wir Testfälle durchführen, um die Integrität unseres Codes zu testen. Eines der häufigsten Elemente, das strenge Tests erfordert, sind Klassenattribute.

Das Klassenattribut kann zufällige Eingaben verarbeiten, um unerwartetes Verhalten zu verhindern.

Betrachten Sie den folgenden Code:

class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass

Stellen Sie sich eine Klasse namens Calculate vor, die ein Attribut namens value und eine Methode namens Process enthält. Innerhalb des Wertes wird ein Wörterbuch gespeichert, das später je nach Anforderung und Datentyp verarbeitet wird.

Um sicherzustellen, dass das Attribut fast jede Art von Wörterbuch speichern kann und fehlerfrei verarbeitet wird, muss man das Attribut testen, um sicherzustellen, dass die Implementierung fehlerfrei ist und keine Revisionen benötigt.

Mögliche Lösungen zum Mocken eines Klassenattributs

Wir können ein Klassenattribut auf zwei Arten verspotten; mit PropertyMock und ohne PropertyMock. Lassen Sie uns jeden von ihnen unten anhand von Beispielcode lernen.

Verwenden Sie PropertyMock, um ein Klassenattribut zu simulieren

Um ein Attribut zu simulieren, können wir PropertyMock verwenden, das hauptsächlich als Mock für eine Eigenschaft oder als Deskriptor für eine Klasse gedacht ist.

Es ist erwähnenswert, dass PropertyMock die Methoden __get__ und __set__ bereitstellt, um den Rückgabewert der Eigenschaft zu ändern, sobald sie abgerufen wurde.

Betrachten Sie den folgenden Code:

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock


class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "value", new_callable=PropertyMock)
    def test_method(self, mocked_attrib):
        mocked_attrib.return_value = 1
        # Will pass without any complaints
        self.assertEqual(Calculate().value, 1)
        print("First Assertion Passed!")

        self.assertEqual(Calculate().value, 22)  # Will throw an assertion
        print("Second Assertion Passed!")


if __name__ == "__main__":
    Test().test_method()

Ausgang:

First Assertion Passed!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 24, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 20, in test_method
    self.assertEqual(Calculate().value, 22) # Will throw an assertion 
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 1 != 22

In der Lösung wird eine neue Methode test_method erstellt, um den Wert von Calculate.value zu ändern. Es ist wichtig zu beachten, dass eine Funktion mit einem patch.object verziert wird.

Der Decorator patch im Modul hilft beim Patchen von Modulen und Attributen auf Klassenebene. Um etwas genauer zu machen, was gepatcht werden soll, verwenden wir patch.object anstelle von patch, um die Methode direkt zu patchen.

Im Decorator wird zunächst der Klassenname Calculate übergeben, der anzeigt, dass das zu patchende Objekt ein Teil von Calculate ist, wobei der Name des Attributs value übergeben wird.

Der letzte Parameter ist ein PropertyMock-Objekt, bei dem wir das value-Attribut überschreiben, indem wir eine andere Zahl übergeben.

Der allgemeine Ablauf des Programms ist wie folgt:

  1. test_method wird aufgerufen.
  2. value der Klasse Calculate wird gepatcht, mit einem new_callable zugewiesen, einer Instanz von PropertyMock.
  3. Calculate.value wird mit 1 überschrieben, indem die Eigenschaft return_value verwendet wird.
  4. Die erste Assertion wird geprüft, wobei Calculate.value gleich 1 sein muss.
  5. Die zweite Assertion wird geprüft, wobei Calculate.value gleich 22 sein muss.

Ein Klassenattribut simulieren, ohne PropertyMock zu verwenden

Wir können es auch ohne PropertyMock lösen. Es ist nur eine geringfügige Modifikation des obigen Beispiels erforderlich.

Betrachten Sie den folgenden Code:

import unittest
from unittest.mock import patch


class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "value", 1)
    def test_method(self):
        # Will pass without any complaints
        self.assertEqual(Calculate().value, 1)
        print("First Assertion Passed!")

        # Will throw an assertion because "Calculate.value" is now 1
        self.assertEqual(Calculate().value, 22)
        print("Second Assertion Passed!")


if __name__ == "__main__":
    Test().test_method()

Ausgang:

First Assertion Passed!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 23, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 19, in test_method
    self.assertEqual(Calculate().value, 22) # Will throw an assertion because "Calculate.value" is now 1
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 1 != 22

Anstatt eine Instanz von PropertyMock an new_callable zu übergeben, können wir den Wert, mit dem wir gespeichert werden möchten, direkt in Calculate.value übergeben.

Python-Mock-Klassenkonstruktor

Stellen Sie sicher, dass alle initialisierten Variablen wie beabsichtigt funktionieren und kein unbeabsichtigtes Verhalten zeigen. Es ist auch notwendig, Konstrukteure mit unterschiedlichen Eingaben zu testen, um alle Eckfälle zu reduzieren.

Betrachten Sie den folgenden Code:

class Calculate:
    num = 0
    dic = {}

    def __init__(self, number, dictt):
        self.num = number
        self.dic = dictt

    def Process(self):  # An example method
        pass

Nehmen wir an, wir wollen den Konstruktor der Klasse Berechnen testen. Um sicherzustellen, dass die Attribute wie beabsichtigt funktionieren, müssen wir den Konstruktor patchen und ihn mit verschiedenen Eingaben übergeben, um mögliche Fehler auszumerzen.

Wie können wir das machen? Siehe folgende Lösung.

Verwenden Sie den Decorator patch.object, um den Konstruktor zu patchen

Wir können den Dekorator patch.object verwenden, um den Konstruktor zu patchen.

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock


class Calculate:
    num = 0
    dic = {}

    def __init__(self):
        self.num = 1
        self.dic = {"11", 2}

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "__new__")
    def test_method(self, mocked_calc):
        mocked_instance = MagicMock()
        mocked_instance.num = 10
        mocked_instance.dic = {"22": 3}
        mocked_calc.return_value = mocked_instance

        self.assertEqual(Calculate().num, 10)
        self.assertEqual(Calculate().dic, {"22": 3})

        print("First set of Asserts done!")

        self.assertEqual(Calculate().num, 1)
        self.assertEqual(Calculate().dic, {"11", 2})
        print("Second set of Asserts done!")


if __name__ == "__main__":
    Test().test_method()

Ausgang:

The first set of Asserts is done!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 37, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 32, in test_method
    self.assertEqual(Calculate().num, 1)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 10 != 1

Es ist erwähnenswert, dass wir statt __init__ __new__ gepatcht haben.

Denn bei der Ausführung von __new__ wird die Instanz einer Klasse erzeugt, während bei __init__ nur die Variablen initialisiert werden. Da wir also eine neue simulierte Instanz erstellen müssen, warum patchen wir dann __new__ statt __init__?

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Verwandter Artikel - Python Unit Test