Как использовать `mock.patch()` для замены объектов в тестах?

Для замены объектов в тестах с помощью mock.patch():
  1. Укажите, какой объект нужно заменить (путь к атрибуту, например, 'module.object_to_replace').
  2. Используйте mock.patch() как декоратор, контекстный менеджер или вызовите его непосредственно, передав путь к объекту.
  3. Внутри теста, объект будет заменен на мок-объект, который можно использовать для имитации поведения, проверки вызовов и т.д.
  4. После завершения теста (или выхода из контекстного менеджера), оригинальный объект будет восстановлен.
Пример:

  from unittest.mock import patch

  @patch('module.object_to_replace')
  def test_function(mock_object):
    # mock_object - это мок, заменяющий module.object_to_replace
    mock_object.return_value = 'mocked_value'
    result = my_function_using_object()
    assert result == 'mocked_value'
  

mock.patch() - это мощный инструмент из библиотеки unittest.mock, который позволяет временно заменять объекты (классы, функции, атрибуты) в тестах, чтобы контролировать их поведение и изолировать тестируемый код от внешних зависимостей. Это особенно полезно, когда нужно имитировать взаимодействие с внешними API, базами данных, или другими частями системы, которые сложно или нежелательно подключать к тестам.

Как работает mock.patch():

  1. Указываем, что патчим: Первый аргумент mock.patch() - это строка, представляющая путь к объекту, который нужно заменить. Путь указывается в формате 'module.object'. module - это имя модуля, а object - имя объекта внутри этого модуля.
  2. Заменяем объект: mock.patch() заменяет оригинальный объект фиктивным объектом (Mock или другим указанным вами). Этот фиктивный объект можно настроить для возвращения определенных значений, вызывать определенные исключения или выполнять любые другие действия.
  3. Контекстный менеджер или декоратор: mock.patch() можно использовать как контекстный менеджер (с with) или как декоратор для тестовой функции или класса. Использование контекстного менеджера гарантирует, что патч автоматически отменяется после завершения блока with. Декоратор патчит объект на время выполнения тестовой функции.
  4. Доступ к Mock-объекту: Когда mock.patch() используется как контекстный менеджер или декоратор, он автоматически передает Mock-объект как аргумент в блок with или в тестовую функцию. Этот Mock-объект можно использовать для проверки, как был вызван замененный объект (mock.call, mock.called, mock.assert_called_with() и т.д.).
  5. Автоматическая отмена патча: Самое главное, mock.patch() гарантирует, что оригинальный объект будет восстановлен после завершения теста или блока with. Это важно, чтобы не повлиять на другие тесты или код.

Примеры:

Пример 1: Использование как контекстного менеджера


    import unittest
    from unittest.mock import patch

    # Представим, что модуль external_api содержит функцию get_data()
    # которую мы хотим заменить в тесте.
    import external_api

    class MyTest(unittest.TestCase):
        def test_my_function(self):
            with patch('external_api.get_data') as mock_get_data:
                # Настраиваем Mock-объект (mock_get_data)
                mock_get_data.return_value = "Mocked data"

                # Вызываем тестируемый код, который использует external_api.get_data()
                result = my_function_that_uses_external_api()

                # Проверяем, что external_api.get_data() был вызван (и, возможно, с какими-то аргументами)
                mock_get_data.assert_called_once()

                # Проверяем результат тестируемого кода
                self.assertEqual(result, "Expected result based on mocked data")
  

Пример 2: Использование как декоратора


    import unittest
    from unittest.mock import patch

    import external_api

    class MyTest(unittest.TestCase):
        @patch('external_api.get_data')
        def test_my_function(self, mock_get_data):
            # mock_get_data - это Mock-объект, переданный декоратором
            mock_get_data.return_value = "Mocked data"

            result = my_function_that_uses_external_api()

            mock_get_data.assert_called_once()
            self.assertEqual(result, "Expected result based on mocked data")
  

Пример 3: Замена атрибута класса:


  import unittest
  from unittest.mock import patch

  class MyClass:
      CONSTANT = "Original Value"

      def do_something(self):
          return self.CONSTANT

  class TestMyClass(unittest.TestCase):
      @patch('__main__.MyClass.CONSTANT', "Mocked Value")  # __main__ - имя текущего модуля
      def test_do_something(self):
          instance = MyClass()
          self.assertEqual(instance.do_something(), "Mocked Value")
  

Важные моменты:

  • Точное указание пути: Убедитесь, что путь к объекту указан правильно. Частая ошибка - забыть указать имя модуля или неправильно написать имя объекта.
  • Видимость: Объект, который вы пытаетесь патчить, должен быть видим в том месте, где вы вызываете mock.patch(). Проблемы с видимостью часто возникают при использовании относительных импортов.
  • Настройка Mock-объекта: Важно настроить Mock-объект так, чтобы он имитировал поведение заменяемого объекта. Это включает в себя установку return_value, side_effect (для вызова функции или выброса исключения), mock_add_spec (для имитации интерфейса другого объекта) и т.д.
  • Проверка вызовов: Используйте методы assert_called_once(), assert_called_with(), call_args и другие методы Mock-объекта, чтобы убедиться, что замененный объект был вызван так, как ожидалось, и с правильными аргументами. Это гарантирует, что тестируемый код правильно использует замененную зависимость.
  • Управление побочными эффектами: При использовании side_effect для Mock-объекта, важно тщательно управлять побочными эффектами, чтобы тесты оставались предсказуемыми и изолированными.
  • Использование autospec: Параметр autospec=True для mock.patch позволяет автоматически создавать Mock-объект, который имеет ту же сигнатуру и атрибуты, что и оригинал. Это помогает избежать ошибок, связанных с неправильной конфигурацией Mock-объекта, и делает тесты более надежными. Например: @patch('module.function', autospec=True).

В заключение, mock.patch() - это незаменимый инструмент для написания модульных тестов на Python. Правильное использование mock.patch() позволяет изолировать тестируемый код от внешних зависимостей, контролировать его поведение и писать надежные и воспроизводимые тесты.

0