Python Mockowanie funkcji z zaimportowanego modułu


125

Chcę zrozumieć, jak wykonać @patchfunkcję z zaimportowanego modułu.

To jest, gdzie jestem do tej pory.

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

To nie działa tak, jak bym się spodziewał. „Połatany” moduł po prostu zwraca niezakodowaną wartość get_user_name. Jak pozorować metody z innych pakietów, które importuję do testowanej przestrzeni nazw?


1
Pytanie dotyczy „wyszydzania najlepszych praktyk”, czy też tego, czy to, co robisz, ma sens? Jeśli chodzi o pierwszy, powiedziałbym, że użyłbym fałszywej biblioteki, takiej jak Mock, która jest zawarta w python3.3 + as unittest.mock.
Bakuriu,

Pytam, czy mam się do tego dobrze. Spojrzałem na Mocka, ale nie widzę sposobu na rozwiązanie tego konkretnego problemu. Czy istnieje sposób na odtworzenie tego, co zrobiłem powyżej w Mock?
nsfyn55

Odpowiedzi:


167

Kiedy używasz patchdekoratora z unittest.mockpakietu, nie łatasz przestrzeni nazw, z której moduł jest importowany (w tym przypadku app.my_module.get_user_name), łatasz go w testowanej przestrzeni nazw app.mocking.get_user_name.

Aby zrobić powyższe, Mockspróbuj czegoś takiego jak poniżej:

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

Dokumentacja biblioteki standardowej zawiera przydatną sekcję opisującą to.


to prowadzi do mojego problemu. get_user_namejest w innym module niż test_method. Czy istnieje sposób, aby kpić z czegoś w module podrzędnym? Naprawiłem to w brzydki sposób poniżej.
nsfyn55

6
Nie ma znaczenia, że get_user_nameznajduje się on w innym module, niż test_methodponieważ importujesz do niego funkcję app.mocking, znajdują się one w tej samej przestrzeni nazw.
Matti John

2
Skąd się wziął test_patch, co to dokładnie jest?
Mike G

2
test_patch jest przekazywany przez dekorator łatek i jest wyśmiewanym obiektem get_user_name (tj. instancją klasy MagicMock). Byłoby bardziej zrozumiałe, gdyby zostało nazwane coś w rodzaju get_user_name_patch.
Matti John

W jaki sposób odwołujesz się do metody test_method? Spowoduje to błąd, NameError: nazwa globalna „test_method” nie jest zdefiniowana
Aditya

12

Chociaż odpowiedź Matti Johna rozwiązuje Twój problem (i mi też pomogła, dzięki!), Sugerowałbym jednak zlokalizowanie zamiany oryginalnej funkcji „get_user_name” na fałszywą. Umożliwi to kontrolowanie, kiedy funkcja zostanie zastąpiona, a kiedy nie. Pozwoli to również na dokonanie kilku wymian w tym samym teście. Aby to zrobić, użyj wyrażenia „with” w całkiem podobny sposób:

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
To trochę nieistotne dla postawionego pytania. To, czy używasz patchjako dekoratora, czy menedżera kontekstu, zależy od przypadku użycia. Na przykład, możesz użyć patchjako dekoratora do kpiny z wartości dla wszystkich testów w klasie xunitlub, pytestpodczas gdy w innych przypadkach przydatne jest precyzyjne sterowanie zapewniane przez menedżera kontekstu.
nsfyn55
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.