Python Verspotten einer Funktion aus einem importierten Modul

125

Ich möchte verstehen, wie @patcheine Funktion aus einem importierten Modul.

Hier bin ich bisher.

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

Dies funktioniert nicht wie erwartet. Das "gepatchte" Modul gibt einfach den nicht gesperrten Wert von zurück get_user_name. Wie verspotte ich Methoden aus anderen Paketen, die ich in einen zu testenden Namespace importiere?

nsfyn55
quelle
1
Die Frage ist, ob man sich über Best Practices lustig macht oder ob das, was Sie tun, Sinn macht oder nicht. In Bezug auf die erste würde ich sagen, eine MockSpottbibliothek wie zu verwenden , die in python3.3 + as enthalten ist unittest.mock.
Bakuriu
Ich frage, ob ich das richtig mache. Ich habe Mock angesehen, aber ich sehe keinen Weg, um dieses spezielle Problem zu lösen. Gibt es eine Möglichkeit, das, was ich oben in Mock getan habe, nachzubilden?
nsfyn55

Antworten:

167

Wenn Sie den patchDecorator aus dem unittest.mockPaket verwenden, patchen Sie nicht den Namespace, aus dem das Modul importiert wird (in diesem Fall app.my_module.get_user_name), sondern patchen ihn in den zu testenden Namespace app.mocking.get_user_name.

Um das oben genannte zu tun, Mockversuchen Sie etwas wie das Folgende:

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

Die Standardbibliotheksdokumentation enthält einen nützlichen Abschnitt , der dies beschreibt.

Matti John
quelle
das kommt zu meinem Problem. get_user_nameist in einem anderen Modul als test_method. Gibt es eine Möglichkeit, etwas in einem Submodul zu verspotten? Ich habe es unten auf hässliche Weise repariert.
nsfyn55
6
Es spielt keine Rolle, dass get_user_namesich das Modul in einem anderen Modul befindet als test_methodseit dem Importieren. app.mockingEs befindet sich im selben Namespace.
Matti John
2
Woher kommt test_patch, was ist das genau?
Mike G
2
test_patch wird vom Patch-Dekorator übergeben und ist das verspottete Objekt get_user_name (dh eine Instanz der MagicMock-Klasse). Es könnte klarer sein, wenn es so etwas wie benannt wurde get_user_name_patch.
Matti John
Wie verweisen Sie auf test_method? Dies führt zu einem Fehler, NameError: Der globale Name 'test_method' ist nicht definiert
Aditya
12

Während die Antwort von Matti John Ihr Problem löst (und mir auch geholfen hat, danke!), Würde ich jedoch vorschlagen, die Ersetzung der ursprünglichen Funktion 'get_user_name' durch die verspottete zu lokalisieren. Auf diese Weise können Sie steuern, wann die Funktion ersetzt wird und wann nicht. Auf diese Weise können Sie auch mehrere Ersetzungen im selben Test vornehmen. Verwenden Sie dazu die 'with'-Anweisung auf ziemlich ähnliche Weise:

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
quelle
6
Dies ist für die gestellte Frage irgendwie unerheblich. Ob Sie patchals Dekorateur oder Kontextmanager verwenden, ist für den Anwendungsfall spezifisch. Sie können patchbeispielsweise als Dekorateur einen Wert für alle Tests in einer xunitoder einer pytestKlasse verspotten, während es in anderen Fällen nützlich ist, über die fein abgestimmte Steuerung zu verfügen, die der Kontextmanager bietet.
nsfyn55