Если вы хотите получить свои тесты Python, чтобы сэкономить ваше время и убедиться, что вас не разбудят посреди ночи болезненные призывы исправить ошибку, продолжайте.

Написание тестов может занять довольно много времени, но если вы знаете, что делаете, и понимаете различные способы имитации кода, вы на правильном пути.

Я быстро расскажу о нескольких различных фиктивных способностях, которые предоставляет Python. Во-первых, вот ссылка на код, обсуждаемый ниже:

GitHub - tzookb / python_mocks_post

Это пути к файлам:

othersrc 
  other_module.py
src
  main.py 
  some_module.py
test
  ...
...

other_module.py

def some_method(): 
  return "real result"
def method_with_param(p1, p2): 
  return p1

some_module.py

def some_method(): 
  return "real result" 
def method_with_param(p1, p2): 
  return p1

main.py

from src.some_module import some_method
import othersrc.other_module as om
def main1():
  return some_method()
def main2():
  return om.other_method()
def code_with_exception_check():
  try:
    some_method()
  except Exception as ex:
    return "error"
def main3():
  return om.method_with_param("a_param", "b_param")
def main4():
  res = {
    "a": some_method(),
    "b": om.other_method()
  }
  return res
class HasDependency:
  def __init__(self, dependency):
    self.dependency = dependency
  
  def run(self):
    return self.dependency.run()

А теперь давайте начнем с демонстрации различных способов их тестирования.

Простой макет модуля

def test_simple_patch_in(self):
  with patch('src.main.some_method', return_value="mocked resp"):
    res = main_module.main1()
    self.assertEqual(res, "mocked resp")

Как видите, здесь мы используем «с патчем». Нам нужно передать в качестве первого аргумента «путь» функции, которую мы хотим имитировать. Именованный параметр «return_value» - это один из вариантов, который мы собираемся изучить сегодня. Это просто означает, что при вызове имитируемой функции она вернет то, что мы здесь установили.

Теперь, когда наш испытуемый импортирует some_method, вместо реального кода он получит наш имитируемый объект.

Мокинг модуля и параметры метода

@patch('src.main.some_method', return_value="mocked resp")
def test_simple_patch_on_method(self, some_method_mocked):
  res = main_module.main1()
  self.assertEqual(res, "mocked resp”)

Таким образом, мы аннотируем наш тестовый метод так же, как в приведенном выше примере. Оба способа делают одно и то же, но таким образом гораздо проще смоделировать несколько объектов без необходимости делать несколько отступов.

Динамическое имитационное действие

@patch('src.main.some_method')
def test_simple_patch_on_method_internal(self, some_method_mocked):
  some_method_mocked.return_value="mocked resp"
  res = main_module.main1()
  self.assertEqual(res, "mocked resp")

То же, что и вышеупомянутая аннотация макета, но здесь мы определяем, как макет будет себя вести или, в частности, что он будет возвращать внутри кода, а не за его пределами.

Несколько моков

@patch('src.main.some_method')
@patch('src.main.om')
def test_simple_patch_on_method_internal(self, om_mock, some_method_mocked):
  om_mock.other_method.return_value = "am_mocked"
  some_method_mocked.return_value="mocked resp"
  res = main_module.main4()
  self.assertEqual(res["a"], "mocked resp")
  self.assertEqual(res["b"], "am_mocked")

Этот пример просто показывает, как вы можете имитировать несколько функций или модулей. Как видите, порядок аннотаций противоположен порядку переменных внутри списка параметров метода тестирования.

Происходит тестирование исключений

@patch('src.main.some_method')
def test_mock_exception(self, some_method_mocked):
  some_method_mocked.side_effect = Exception()
  res = main_module.code_with_exception_check()
  self.assertEqual(res, "error")

Поскольку мы уже узнали «return_value», здесь у нас есть «side_effect» - здесь вы можете установить Exception, если вы хотите, чтобы ваша имитируемая функция вызывала исключение.

Создайте макет и передайте его как зависимость

def test_dependency(self):
  dependency = MagicMock()
  dependency.run.return_value = 55
  res = (main_module.HasDependency(dependency)).run()
  self.assertEqual(res, 55)

Здесь вы можете видеть, что мы не используем «патч», поскольку мы не исправляем какие-либо пути Python. Мы создаем «MagicMock» (именно то, что создает патч), а затем мы можем определить, что нам нужно для этого, и передать его куда угодно.

Глобальное издевательство над объектом

class MainGlobalMockTest(unittest.TestCase):
  def setUp(self):
    self.patcher = patch('src.main.some_method')
    self.some_method_mock = self.patcher.start()
  def tearDown(self):
    self.patcher.stop()
  def test_simple(self):
    self.some_method_mock.return_value="mocked resp"
    res = main_module.main1()
    self.assertEqual(res, "mocked resp")

Два новых метода «setUp» и «tearDown» будут выполняться в начале и в конце каждого теста соответственно. Внутри «setUp» мы определяем «патчер», а затем «запускаем» его. Внутри tearDown мы останавливаем его, чтобы он не повлиял на другие тесты.
После вышеизложенного, внутри test_simple все работает точно так же.

Если вы хотите узнать обо мне больше, посетите мой блог

Первоначально опубликовано на x-team.com 11 декабря 2018 г.