170 votes

Mocking d'une classe : Mock() ou patch() ?

J'utilise simuler avec Python et je me demandais laquelle de ces deux approches était la meilleure (lire : la plus pythonique).

Première méthode : Il suffit de créer un objet fantaisie et de l'utiliser. Le code ressemble à ça :

def test_one (self):
    mock = Mock()
    mock.method.return_value = True 
    self.sut.something(mock) # This should called mock.method and checks the result. 
    self.assertTrue(mock.method.called)

Deuxième méthode : Utiliser le patch pour créer un mock. Le code ressemble à cela :

@patch("MyClass")
def test_two (self, mock):
    instance = mock.return_value
    instance.method.return_value = True
    self.sut.something(instance) # This should called mock.method and checks the result. 
    self.assertTrue(instance.method.called)

Les deux méthodes font la même chose. Je ne suis pas sûr des différences.

Quelqu'un peut-il m'éclairer ?

20 votes

En tant que personne n'ayant jamais essayé ni Mock() ni patch, j'ai l'impression que la première version est plus claire et montre ce que vous voulez faire, même si je ne comprends pas la différence réelle. Je ne sais pas si cela peut aider ou non, mais j'ai pensé qu'il pourrait être utile de transmettre ce qu'un programmeur non initié pourrait ressentir.

2 votes

@MichaelBrennan : Merci pour votre commentaire. Il est en effet utile.

188voto

D.Shawley Points 30324

mock.patch est une créature très très différente de mock.Mock . patch remplace la classe avec un objet fantaisie et vous permet de travailler avec l'instance fantaisie. Jetez un coup d'œil à cet extrait :

>>> class MyClass(object):
...   def __init__(self):
...     print 'Created MyClass@{0}'.format(id(self))
... 
>>> def create_instance():
...   return MyClass()
... 
>>> x = create_instance()
Created MyClass@4299548304
>>> 
>>> @mock.patch('__main__.MyClass')
... def create_instance2(MyClass):
...   MyClass.return_value = 'foo'
...   return create_instance()
... 
>>> i = create_instance2()
>>> i
'foo'
>>> def create_instance():
...   print MyClass
...   return MyClass()
...
>>> create_instance2()
<mock.Mock object at 0x100505d90>
'foo'
>>> create_instance()
<class '__main__.MyClass'>
Created MyClass@4300234128
<__main__.MyClass object at 0x100505d90>

patch remplace MyClass d'une manière qui vous permet de contrôler l'utilisation de la classe dans les fonctions que vous appelez. Une fois que vous avez patché une classe, les références à la classe sont complètement remplacées par l'instance fantaisie.

mock.patch est généralement utilisé lorsque vous testez quelque chose qui crée une nouvelle instance d'une classe à l'intérieur du test. mock.Mock Les instances sont plus claires et sont préférées. Si votre self.sut.something a créé une instance de MyClass au lieu de recevoir une instance en tant que paramètre, alors mock.patch serait approprié ici.

2 votes

@D.Shawley comment faire un patch sur une classe instanciée dans une autre classe qui doit être en cours de test.

5 votes

@ravz - donnez le "Where to Patch" une lecture. C'est l'un des éléments les plus difficiles à faire fonctionner correctement.

0 votes

Mon test blanc est similaire à Deuxième méthode . Je veux que l'instance de MyClass lève une exception. J'ai essayé à la fois mock.side_effect et mock.return_value.side_effect et cela n'a pas fonctionné. Que dois-je faire ?

50voto

MikeTwo Points 26

J'ai un Vidéo YouTube sur ce sujet.

Réponse courte : Utilisez mock quand vous passez dans la chose dont vous voulez vous moquer, et patch si vous ne l'êtes pas. Des deux, mock est fortement préféré car il signifie que vous écrivez du code avec une injection de dépendance appropriée.

Exemple stupide :

# Use a mock to test this.
my_custom_tweeter(twitter_api, sentence):
    sentence.replace('cks','x')   # We're cool and hip.
    twitter_api.send(sentence)

# Use a patch to mock out twitter_api. You have to patch the Twitter() module/class 
# and have it return a mock. Much uglier, but sometimes necessary.
my_badly_written_tweeter(sentence):
    twitter_api = Twitter(user="XXX", password="YYY")
    sentence.replace('cks','x') 
    twitter_api.send(sentence)

19voto

Andriy Ivaneyko Points 4660

Les points clés qui expliquent la différence et donnent des conseils pour travailler avec unittest.mock

  1. Utilisez Mock si vous voulez remplacer certains éléments d'interface (en passant les args) de l'objet à tester.
  2. Utilisez le patch si vous voulez remplacer l'appel interne à certains objets et modules importés de l'objet testé.
  3. Toujours fournir la spécification de l'objet dont vous vous moquez.
    • Avec patch, vous pouvez toujours fournir autospec
    • Avec Mock, vous pouvez fournir spec
    • Au lieu de Mock, vous pouvez utiliser créer_autospec qui vise à créer des objets Mock avec des spécifications.

Dans la question ci-dessus, la bonne réponse serait d'utiliser Mock ou pour être plus précis create_autospec (parce qu'elle ajoutera une spécification aux méthodes fantaisie de la classe fantaisie), la définition de l'option spec sur le mock sera utile dans le cas d'une tentative d'appel d'une méthode de la classe qui n'existe pas (signature quelconque), veuillez voir quelques

from unittest import TestCase
from unittest.mock import Mock, create_autospec, patch

class MyClass:

    @staticmethod
    def method(foo, bar):
        print(foo)

def something(some_class: MyClass):
    arg = 1
    # Would fail becuase of wrong parameters passed to methd.
    return some_class.method(arg)

def second(some_class: MyClass):
    arg = 1
    return some_class.unexisted_method(arg)

class TestSomethingTestCase(TestCase):
    def test_something_with_autospec(self):
        mock = create_autospec(MyClass)
        mock.method.return_value = True
        # Fails because of signature misuse.
        result = something(mock)
        self.assertTrue(result)
        self.assertTrue(mock.method.called)

    def test_something(self):
        mock = Mock()  # Note that Mock(spec=MyClass) will also pass, because signatures of mock don't have spec.
        mock.method.return_value = True

        result = something(mock)

        self.assertTrue(result)
        self.assertTrue(mock.method.called)

    def test_second_with_patch_autospec(self):
        with patch(f'{__name__}.MyClass', autospec=True) as mock:
            # Fails because of signature misuse.
            result = second(mock)
        self.assertTrue(result)
        self.assertTrue(mock.unexisted_method.called)

class TestSecondTestCase(TestCase):
    def test_second_with_autospec(self):
        mock = Mock(spec=MyClass)
        # Fails because of signature misuse.
        result = second(mock)
        self.assertTrue(result)
        self.assertTrue(mock.unexisted_method.called)

    def test_second_with_patch_autospec(self):
        with patch(f'{__name__}.MyClass', autospec=True) as mock:
            # Fails because of signature misuse.
            result = second(mock)
        self.assertTrue(result)
        self.assertTrue(mock.unexisted_method.called)

    def test_second(self):
        mock = Mock()
        mock.unexisted_method.return_value = True

        result = second(mock)

        self.assertTrue(result)
        self.assertTrue(mock.unexisted_method.called)

Les cas de test avec les spécifications définies utilisées échouer car les méthodes appelées par something y second fonctions ne sont pas des plaintes avec MyClass, ce qui signifie - qu'ils attrapent les bogues, alors que les valeurs par défaut Mock s'affichera.

Il existe encore une autre option : utiliser la fonction patch.objet pour simuler uniquement la méthode de la classe qui est appelée avec.

Les bons cas d'utilisation du patch sont ceux où la classe est utilisée comme partie interne d'une fonction :

def something():
    arg = 1
    return MyClass.method(arg)

Ensuite, vous voudrez utiliser patch comme décorateur pour simuler la MyClass.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X