Python mock-Patcher une méthode sans entraver l'implémentation

Existe-t-il un moyen propre de patcher un objet afin d'obtenir les aides assert_call* dans votre cas de test, sans réellement supprimer l'action?

Par exemple, comment puis-je modifier la ligne @patch pour obtenir le test suivant:

from unittest import TestCase
from mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    @patch.object(Potato, 'foo')
    def test_something(self, mock):
        spud = Potato()
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)

Je pourrais probablement pirater cela ensemble en utilisant side_effect, mais j'espérais qu'il y aurait un moyen plus agréable qui fonctionne de la même manière sur toutes les fonctions, classmethods, staticmethods, méthodes non liées, etc.

27
demandé sur wim 2014-09-01 18:33:53

2 réponses

Solution similaire avec la vôtre, mais en utilisant wraps:

def test_something(self):
    spud = Potato()
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
    self.assertEqual(forty_two, 42)

Selon la documentation:

wraps : élément pour l'objet fictif à envelopper. Si wraps n'est pas None alors l'appel de la maquette passera l'appel à l'objet encapsulé (retour du résultat réel). L'accès aux attributs sur le mock retournera un objet simulé qui enveloppe l'attribut correspondant de l'encapsulé objet (donc tenter d'accéder à un attribut qui n'existe pas génère une erreur AttributeError).


class Potato(object):

    def spam(self, n):
        return self.foo(n=n)

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    def test_something(self):
        spud = Potato()
        with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
            forty_two = spud.spam(n=40)
            mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
20
répondu falsetru 2014-09-01 14:58:48

Cette réponse répond à l'exigence supplémentaire mentionnée dans la prime de l'utilisateur Quuxplusone:

La chose importante pour mon cas d'utilisation est qu'il fonctionne avec @patch.mock, c'est-à-dire qu'il ne m'oblige pas à insérer du code entre ma construction de l'instance de Potato (spud dans cet exemple) et mon appel de spud.foo. J'ai besoin que spud soit créé avec une méthode mocked-out foo dès le départ, car je ne contrôle pas l'endroit où spud est créé.

L'utilisation le cas décrit ci-dessus pourrait être réalisé sans trop de problèmes en utilisant un décorateur:

import unittest
import unittest.mock  # Python 3

def spy_decorator(method_to_decorate):
    mock = unittest.mock.MagicMock()
    def wrapper(self, *args, **kwargs):
        mock(*args, **kwargs)
        return method_to_decorate(self, *args, **kwargs)
    wrapper.mock = mock
    return wrapper

def spam(n=42):
    spud = Potato()
    return spud.foo(n=n)

class Potato(object):

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2

class PotatoTest(unittest.TestCase):

    def test_something(self):
        foo = spy_decorator(Potato.foo)
        with unittest.mock.patch.object(Potato, 'foo', foo):
            forty_two = spam(n=40)
        foo.mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)


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

Si la méthode replaced accepte des arguments mutables qui sont modifiés en cours de test, vous pouvezCopyingMock à la place du MagicMock à l'intérieur du spy_decorator.

6
répondu wim 2017-01-11 20:06:27