Python Simulando uma função de um módulo importado


125

Eu quero entender como @patchuma função de um módulo importado.

É aqui que estou até agora.

app / mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

app / my_module / __ init__.py:

def get_user_name():
  return "Unmocked User"

test / mock-test.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

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

Isso não funciona como eu esperava. O módulo "corrigido" simplesmente retorna o valor desbloqueado de get_user_name. Como faço para simular métodos de outros pacotes que estou importando para um namespace em teste?


1
A questão é sobre "zombar das melhores práticas" ou se o que você está fazendo faz sentido ou não? Em relação ao primeiro, eu diria para usar uma biblioteca de simulação como Mock, que está incluída em python3.3 + as unittest.mock.
Bakuriu de

Estou perguntando se estou fazendo isso direito. Eu olhei para Mock, mas não vejo uma maneira de resolver esse problema em particular. Existe uma maneira de recriar o que fiz acima no Mock?
nsfyn55 de

Respostas:


167

Quando você está usando o patchdecorador do unittest.mockpacote, você não está corrigindo o namespace do qual o módulo é importado (neste caso app.my_module.get_user_name), você o está corrigindo no namespace em teste app.mocking.get_user_name.

Para fazer o acima, Mocktente algo como o seguinte:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):

    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

A documentação da biblioteca padrão inclui uma seção útil que descreve isso.


isso chega ao meu problema. get_user_nameestá em um módulo diferente de test_method. Existe uma maneira de simular algo em um sub_module? Eu consertei de uma maneira feia abaixo.
nsfyn55

6
Não importa que get_user_nameesteja em um módulo diferente daquele em que test_methodvocê está importando a função, app.mockingeles estão no mesmo namespace.
Matti John

2
De onde veio test_patch, o que é exatamente?
Mike G

2
test_patch é passado pelo decorador do patch e é o objeto get_user_name simulado (ou seja, uma instância da classe MagicMock). Poderia ficar mais claro se tivesse um nome semelhante get_user_name_patch.
Matti John

Como você está referenciando test_method? Isso resultará em erro, NameError: nome global 'test_method' não está definido
Aditya

12

Embora a resposta de Matti John resolva seu problema (e me ajude também, obrigado!), Gostaria, no entanto, de sugerir localizar a substituição da função 'get_user_name' original pela função simulada. Isso permitirá que você controle quando a função é substituída e quando não é. Além disso, isso permitirá que você faça várias substituições no mesmo teste. Para fazer isso, use a instrução 'com' de uma maneira bastante semelhante:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')

6
Isso é meio irrelevante para a questão colocada. O fato de você usar patchcomo decorador ou gerenciador de contexto é específico para o caso de uso. Por exemplo, você pode usar patchcomo um decorador para simular um valor para todos os testes em uma classe xunitou, pytestenquanto em outros casos é útil ter o controle refinado fornecido pelo gerenciador de contexto.
nsfyn55 de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.