Se moquer d'une classe: Mock () ou patch ()?

116

J'utilise mock avec Python et je me demande laquelle de ces deux approches est la meilleure (lire: plus pythonique).

Première méthode : créez simplement un objet simulé et utilisez-le. Le code ressemble à ceci:

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 : utilisez un patch pour créer une maquette. Le code ressemble à ceci:

@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 pourrait-il m'éclairer?

Sardathrion - contre les abus SE
la source
10
En tant que personne qui n'a jamais essayé ni Mock () ni patch, je pense que la première version est plus claire et montre ce que vous voulez faire, même si je n'ai aucune compréhension de la différence réelle. Je ne sais pas si cela est utile ou non, mais j'ai pensé qu'il pourrait être utile de transmettre ce qu'un programmeur non initié pourrait ressentir.
Michael Brennan
2
@MichaelBrennan: Merci pour votre commentaire. C'est vraiment utile.
Sardathrion - contre les abus SE

Réponses:

151

mock.patchest une créature très très différente de celle mock.Mock. patch remplace la classe par un objet fictif et vous permet de travailler avec l'instance fictive. Jetez un œ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>

patchremplace MyClassd'une manière qui vous permet de contrôler l'utilisation de la classe dans les fonctions que vous appelez. Une fois que vous corrigez une classe, les références à la classe sont complètement remplacées par l'instance fictive.

mock.patchest généralement utilisé lorsque vous testez quelque chose qui crée une nouvelle instance d'une classe à l'intérieur du test. mock.Mockles instances sont plus claires et sont préférées. Si votre self.sut.somethingméthode a créé une instance de MyClassau lieu de recevoir une instance en tant que paramètre, alors ce mock.patchserait approprié ici.

D.Shawley
la source
2
@ D.Shawley comment appliquer un patch à une classe instanciée dans une autre classe qui doit être en cours de test.
ravi404
4
@ravz - donnez une lecture au "Où patcher" . C'est l'une des choses les plus difficiles à faire fonctionner correctement.
D.Shawley
Mon test simulé est similaire à la deuxième méthode . Je veux que l'instance 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 fais-je?
Hussain
5
@ D.Shawley Le lien est rompu, il peut être trouvé ici maintenant: "Where to Patch"
RazerM
2
Pour patcher un objet de classe, voir stackoverflow.com/questions/8469680/…
storm_m2138
27

J'ai une vidéo YouTube à ce sujet.

Réponse courte: à utiliser mocklorsque vous transmettez ce dont vous voulez vous moquer, et patchsi vous ne l'êtes pas. Parmi les deux, la simulation est fortement préférée car cela signifie que vous écrivez du code avec une injection de dépendances appropriée.

Exemple idiot:

# 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)
MikeTwo
la source