Frage Verspotten von Klasseneigenschaften bei Verwendung von 'autospec = True' in Python


Ich möchte eine Klasse mit folgenden Anforderungen verhöhnen:

  • Die Klasse verfügt über öffentliche Lese- / Schreibeigenschaften, die in der Klasse definiert sind __init__() Methode
  • Die Klasse verfügt über ein öffentliches Attribut, das bei der Objekterstellung automatisch inkrementiert wird
  • Ich möchte verwenden autospec=TrueDaher wird die API der Klasse streng nach Anrufen suchen

Ein vereinfachtes Klassenbeispiel:

class MyClass():
    id = 0

    def __init__(self, x=0.0, y=1.0):
        self.x = x
        self.y = y
        self.id = MyClass._id
        MyClass.id +=1

    def calc_x_times_y(self):
        return self.x*self.y

    def calc_x_div_y(self, raise_if_y_not_zero=True):
        try:
            return self.x/self.y
        except ZeroDivisionError:
            if raise_if_y_not_zero:
                raise ZeroDivisionError
            else:
                return float('nan')

Ich brauche, dass sich das Mock-Objekt wie das ursprüngliche Objekt verhält, was die Eigenschaften betrifft:

  • Es sollte die jedem neu erstellten Scheinobjekt zugewiesene ID automatisch erhöhen
  • Es sollte Zugang zu seinem erlauben x,y Eigenschaften Die Mock-Methodenaufrufe sollten jedoch vom Mock abgefangen und die Rufsignatur validiert werden

Was ist der beste Weg, darüber weiter zu machen?

BEARBEITEN

Ich habe bereits verschiedene Ansätze ausprobiert, einschließlich der Unterklassen Mock Klasse, verwenden attach_mock(), und mock_add_spec(), aber lief immer in eine Sackgasse.

Ich benutze den Standard spotten Bibliothek.


5
2017-11-25 09:14


Ursprung


Antworten:


Da keine Antworten reinkommen, poste ich, was für mich funktioniert hat (nicht unbedingt der beste Ansatz, aber hier geht es):

Ich habe eine Scheinfabrik erstellt, die ein Mock() Objekt, legt es fest id Eigenschaft mit der beschriebenen Syntax Hierund gibt das Objekt zurück:

 class MyClassMockFactory():
     _id = 0

     def get_mock_object(self, *args,**kwargs):
        mock = Mock(MyClass, autospec = True)
        self._attach_mock_property(mock , 'x', kwargs['x'])
        self._attach_mock_property(mock , 'y', kwargs['y'])
        self._attach_mock_property(mock , 'id', MyClassMockFactory._id)
        MyClassMockFactory._id += 1
        return mock

     def _attach_mock_property(self, mock_object, name, value):
         p = PropertyMock(return_value=value)
         setattr(type(mock_object), name, p)

Jetzt kann ich die MyClass() Konstruktor für meine Tests:

class TestMyClass(TestCase):
     mock_factory = MyClassMockFactory()

     @patch('MyClass',side_effect=mock_factory.get_mock_object)
     test_my_class(self,*args):
         obj0 = MyClass()
         obj1 = MyClass(1.0,2.2)
         obj0.calc_x_times_y()
         # Assertions
         obj0.calc_x_times_y.assert_called_once_with()
         self.assertEqaul(obj0.id, 0)
         self.assertEqaul(obj1.id, 1)

2
2017-11-29 08:40



Es tut mir leid, einen alten Post zu graben, aber etwas, das es dir erlauben würde, genau das zu tun, was du erreichen willst, ist ein Patch calc_x_times_y und calc_x_div_y und einstellen autospec=True da, im Gegensatz zu Mocking die Schaffung der gesamten Klasse.

Etwas wie:

@patch('MyClass.calc_x_times_y')
@patch('MyClass.calc_x_div_y')
test_foo(patched_div, patched_times):
my_class = MyClass() #using real class to define attributes
# ...rest of test

0
2018-06-18 18:40