web-dev-qa-db-fra.com

Comment simuler le patch d'une classe python et obtenir un nouvel objet Mock pour chaque instanciation?

D'ACCORD,
Je sais que cela est mentionné dans le manuel , et a probablement à voir avec side_effect et/ou return_value, mais un exemple simple et direct m'aidera énormément.

J'ai:

class ClassToPatch():
   def __init__(self, *args):
       _do_some_init_stuff()

   def some_func():
       _do_stuff()


class UUT():
    def __init__(self, *args)
       resource_1 = ClassToPatch()
       resource_2 = ClassToPatch()

Maintenant, je veux tester de façon unitaire la classe UUT et mocker la ClassToPatch. Connaissant la classe UUT va instancier exactement deux objets ClassToPatch, je veux que le framework Mock retourne un nouvel objet Mock pour chaque instanciation, afin que je puisse plus tard affirmer des appels sur chacun séparément.

Comment y parvenir en utilisant le @patch décorateur dans un cas de test? À savoir, comment corriger l'exemple de code suivant?

class TestCase1(unittest.TestCase):

    @patch('classToPatch.ClassToPatch',autospec=True)
    def test_1(self,mock1,mock2):
        _assert_stuff()
26
bavaza

Voici un exemple rapide pour vous aider à démarrer:

import mock
import unittest

class ClassToPatch():
   def __init__(self, *args):
       pass

   def some_func(self):
       return id(self)

class UUT():
    def __init__(self, *args):
        resource_1 = ClassToPatch()
        resource_2 = ClassToPatch()
        self.test_property = (resource_1.some_func(), resource_2.some_func())

class TestCase1(unittest.TestCase):
    @mock.patch('__main__.ClassToPatch', autospec = True)
    def test_1(self, mock1):
        ctpMocks = [mock.Mock(), mock.Mock()]
        ctpMocks[0].some_func.return_value = "funky"
        ctpMocks[1].some_func.return_value = "monkey"
        mock1.side_effect = ctpMocks

        u = UUT()
        self.assertEqual(u.test_property, ("funky", "monkey"))

if __name__ == '__main__':
    unittest.main()

J'ai ajouté test_property à UUT pour que le test unitaire fasse quelque chose d'utile. Maintenant, sans la maquette test_property doit être un Tuple contenant les identifiants des deux instances de ClassToPatch. Mais avec la maquette, ce devrait être le Tuple: ("funky", "monkey").

J'ai utilisé le side_effect propriété de l'objet factice afin qu'une instance différente de ClassToPatch soit renvoyée à chaque appel dans l'initialiseur UUT.

J'espère que cela t'aides.

Edit: Oh, au fait, quand je lance le test unitaire, j'obtiens:

.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK
28
srgerg

Voici une autre version plus générique pour gérer n'importe quel nombre d'instances créées:

class TestUUT:
    def test_init(self, mocker):
        class MockedClassToPatchMeta(type):
            static_instance = mocker.MagicMock(spec=ClassToPatch)

            def __getattr__(cls, key):
                return MockedClassToPatchMeta.static_instance.__getattr__(key)

        class MockedClassToPatch(metaclass=MockedClassToPatchMeta):
            original_cls = ClassToPatch
            instances = []

            def __new__(cls, *args, **kwargs):
                MockedClassToPatch.instances.append(
                    mocker.MagicMock(spec=MockedClassToPatch.original_cls))
                MockedClassToPatch.instances[-1].__class__ = MockedClassToPatch
                return MockedClassToPatch.instances[-1]

        mocker.patch(__name__ + '.ClassToPatch', new=MockedClassToPatch)

        UUT()

        # since your original code created two instances
        assert 2 == len(MockedClassToPatch.instances)

Si vous avez besoin d'une validation plus approfondie pour chaque instance, vous pouvez accéder à MockedClassToPatch.instances[0] ou MockedClassToPatch.instances[1].

J'ai également créé ne bibliothèque d'assistance pour générer le passe-partout pour moi. Pour générer le code nécessaire pour votre exemple, j'ai écrit:

print(PytestMocker(mocked=ClassToPatch, name=__name__).mock_classes().mock_classes_static().generate())
0
Peter K