Python se moquer d'une fonction à partir d'un module importé

125

Je veux comprendre comment activer @patchune fonction à partir d'un module importé.

C'est là que je suis si loin.

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 / mon_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()

Cela ne fonctionne pas comme je m'y attendais. Le module "patché" renvoie simplement la valeur non moquée de get_user_name. Comment simuler des méthodes d'autres packages que j'importe dans un espace de noms en cours de test?

nsfyn55
la source
1
La question est de «se moquer des meilleures pratiques» ou si ce que vous faites a du sens? En ce qui concerne le premier, je dirais d'utiliser une bibliothèque moqueuse telle que Mock, qui est incluse dans python3.3 + as unittest.mock.
Bakuriu
Je demande si je fais ce droit. J'ai regardé Mock, mais je ne vois pas de moyen de résoudre ce problème particulier. Existe-t-il un moyen de recréer ce que j'ai fait ci-dessus dans Mock?
nsfyn55

Réponses:

167

Lorsque vous utilisez le patchdécorateur du unittest.mockpackage, vous ne corrigez pas l'espace de noms à partir duquel le module est importé (dans ce cas app.my_module.get_user_name), vous le corrigez dans l'espace de noms en cours de test app.mocking.get_user_name.

Pour faire ce qui précède, Mockessayez quelque chose comme ci-dessous:

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

La documentation standard de la bibliothèque comprend une section utile décrivant cela.

Matti John
la source
cela arrive à mon problème. get_user_nameest dans un module différent de test_method. Existe-t-il un moyen de se moquer de quelque chose dans un sous_module? Je l'ai réparé d'une manière moche ci-dessous.
nsfyn55
6
Peu importe que ce get_user_namesoit dans un module différent de celui test_methodque vous importez dans la fonction, app.mockingils sont dans le même espace de noms.
Matti John
2
D'où vient test_patch, qu'est-ce que c'est exactement?
Mike G
2
test_patch est passé par le décorateur de patch et est l'objet get_user_name simulé (c'est-à-dire une instance de la classe MagicMock). Ce serait peut-être plus clair s'il était nommé quelque chose comme get_user_name_patch.
Matti John
Comment faites-vous référence à test_method? Cela entraînera une erreur, NameError: le nom global 'test_method' n'est pas défini
Aditya
12

Bien que la réponse de Matti John résout votre problème (et m'a aidé aussi, merci!), Je suggérerais cependant de localiser le remplacement de la fonction originale 'get_user_name' par la fonction simulée. Cela vous permettra de contrôler quand la fonction est remplacée et quand elle ne l'est pas. De plus, cela vous permettra de faire plusieurs remplacements dans le même test. Pour ce faire, utilisez l'instruction 'with' d'une manière assez similaire:

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')
Tgilgul
la source
6
C'est en quelque sorte indifférent à la question posée. Que vous l'utilisiez patchcomme décorateur ou gestionnaire de contexte est spécifique au cas d'utilisation. Par exemple, vous pouvez utiliser patchcomme décorateur pour simuler une valeur pour tous les tests d'une classe xunitou pytest, tandis que dans d'autres cas, il est utile d'avoir le contrôle finement fourni par le gestionnaire de contexte.
nsfyn55