Zombando de uma classe: Mock () ou patch ()?

116

Estou usando simulação com Python e queria saber qual dessas duas abordagens é melhor (leia: mais pythônico).

Método um : basta criar um objeto simulado e usá-lo. O código se parece com:

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)

Método dois : Use patch para criar um mock. O código se parece com:

@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)

Ambos os métodos fazem a mesma coisa. Não tenho certeza das diferenças.

Alguém poderia me esclarecer?

Sardathrion - contra o abuso de SE
fonte
10
Como uma pessoa que nunca experimentou Mock () ou patch, eu sinto que a primeira versão é mais clara e mostra o que você quer fazer, embora eu não tenha nenhum entendimento da diferença real. Não sei se isso ajuda ou não, mas achei que poderia ser útil para transmitir o que um programador não iniciado pode sentir.
Michael Brennan
2
@MichaelBrennan: Obrigado pelo seu comentário. É realmente útil.
Sardathrion - contra abuso de SE

Respostas:

151

mock.patché uma criatura muito diferente de mock.Mock. patch substitui a classe por um objeto simulado e permite que você trabalhe com a instância simulada. Dê uma olhada neste snippet:

>>> 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>

patchsubstitui MyClassde uma forma que permite controlar o uso da classe nas funções que você chama. Depois de corrigir uma classe, as referências à classe são completamente substituídas pela instância simulada.

mock.patchgeralmente é usado quando você está testando algo que cria uma nova instância de uma classe dentro do teste. mock.Mockas instâncias são mais claras e preferidas. Se o seu self.sut.somethingmétodo criou uma instância de em MyClassvez de receber uma instância como parâmetro, mock.patchseria apropriado aqui.

D.Shawley
fonte
2
@ D.Shawley como corrigimos para uma classe instanciada dentro de outra classe que precisa estar em teste.
ravi404
4
@ravz - dê uma leitura do "Where to patch" . Essa é uma das coisas mais difíceis de fazer funcionar corretamente.
D.Shawley
Meu teste simulado é semelhante ao Método dois . Eu quero que a instância de MyClass gere uma exceção. Eu tentei tanto mock.side_effect quanto mock.return_value.side_effect e eles não funcionaram. O que eu faço?
Hussain
5
@ D.Shawley O link está quebrado, ele pode ser encontrado aqui agora: "Where to Patch"
RazerM
2
Para corrigir um objeto de classe, consulte stackoverflow.com/questions/8469680/…
storm_m2138
27

Eu tenho um video do youtube no sobre isso.

Resposta curta: use mockquando estiver passando algo de que deseja zombar epatch se não for. Dos dois, o mock é fortemente preferido porque significa que você está escrevendo código com injeção de dependência adequada.

Exemplo bobo:

# 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
fonte