Как проверять аргументы, передаваемые в функции или методы, используя мок-объекты?

Используя мок-объекты для проверки аргументов, переданных в функции или методы, можно:
  • Assert вызовы с ожидаемыми аргументами: Мок предоставляет методы, такие как mock_object.assert_called_with(arg1, arg2, ...) или mock_object.assert_called_once_with(arg1, arg2, ...). Эти методы проверяют, был ли мок вызван с определенными аргументами.
  • Использовать call для более сложных проверок: Модуль unittest.mock предоставляет call, который позволяет проверять последовательность вызовов и аргументы для каждого вызова. Например, mock_object.mock_calls содержит список всех вызовов, и можно проверять конкретные элементы этого списка с помощью unittest.mock.call.
  • Использовать side_effect для валидации аргументов: Можно присвоить функции с side_effect и внутри нее проверить аргументы, используя assert или выбрасывая исключения, если аргументы не соответствуют ожиданиям. Это позволяет производить более сложные проверки, чем простое сравнение с заданными значениями.
  • ANY для игнорирования значений аргументов: Если конкретное значение аргумента не важно, можно использовать unittest.mock.ANY, чтобы проверить только тип аргумента, а не его точное значение.
Например: mock_obj.method(1, 'a', kwarg='value') можно проверить: mock_obj.method.assert_called_with(1, 'a', kwarg='value') или mock_obj.method.assert_called_with(1, unittest.mock.ANY, kwarg=unittest.mock.ANY)

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

Вот несколько способов проверки аргументов, передаваемых в функции/методы мок-объектов:

1. Использование `mock.call` и `mock.assert_called_with()`/`mock.assert_has_calls()`:

Это наиболее распространенный и рекомендуемый способ.


  import unittest
  from unittest.mock import Mock, call

  class MyClass:
    def do_something(self, arg1, arg2):
      # Здесь код, который должен вызывать другие функции
      pass

  class TestMyClass(unittest.TestCase):
    def test_do_something_with_args(self):
      mock_dependency = Mock()
      my_instance = MyClass()
      my_instance.dependency = mock_dependency # Предположим, что MyClass использует dependency

      # Когда-то в do_something вызывается dependency.some_method(1, 'hello')
      # Сделаем вид, что do_something делает это.
      my_instance.do_something(10, "world")
      mock_dependency.some_method(1, 'hello')

      # Проверяем, что dependency.some_method была вызвана с ожидаемыми аргументами
      mock_dependency.some_method.assert_called_with(1, 'hello')

      # Проверка нескольких вызовов
      calls = [call(1, 'hello'), call(2, 'world')]
      mock_dependency.some_method.assert_has_calls(calls) # Убедимся, что такие вызовы произошли
  
  • `mock_dependency.some_method.assert_called_with(arg1, arg2)`: Проверяет, что метод `some_method` был вызван один раз и именно с указанными аргументами. Если метод вызван несколько раз или с другими аргументами, тест завершится с ошибкой.
  • `mock_dependency.some_method.assert_has_calls([call(arg1, arg2), call(arg3, arg4)])`: Проверяет, что метод `some_method` был вызван с аргументами, перечисленными в списке `calls`, в любом порядке. Важно: все вызовы должны произойти.
  • `mock_dependency.method_calls`: Возвращает список всех вызовов, сделанных к мок-объекту. Можно использовать для более сложных проверок.

2. Использование `side_effect` с функцией проверки:

Этот метод позволяет выполнить произвольную проверку аргументов в момент вызова мок-объекта.


  import unittest
  from unittest.mock import Mock

  class TestMyClass(unittest.TestCase):
    def test_do_something_with_side_effect(self):
      mock_dependency = Mock()

      def check_args(arg1, arg2):
        self.assertEqual(arg1, 1)
        self.assertEqual(arg2, 'hello')

      mock_dependency.some_method.side_effect = check_args

      # Сделаем вид, что происходит вызов
      mock_dependency.some_method(1, 'hello')

      # Если assertion в check_args не выполнится, тест упадет.
  
  • `mock_dependency.some_method.side_effect = check_args`: Присваивает функцию `check_args` свойству `side_effect` мок-объекта. Каждый раз при вызове `mock_dependency.some_method`, будет выполняться `check_args` с аргументами, переданными в `some_method`.
  • Внутри `check_args` используйте `self.assertEqual`, `self.assertTrue` и другие методы `unittest.TestCase` для проверки аргументов.
  • Этот способ особенно полезен, если нужно проверить сложные условия или несколько аргументов одновременно.

3. Использование `call_args` и `call_args_list`:

Эти атрибуты мок-объекта предоставляют доступ к аргументам последних и всех вызовов соответственно.


  import unittest
  from unittest.mock import Mock

  class TestMyClass(unittest.TestCase):
    def test_do_something_with_call_args(self):
      mock_dependency = Mock()
      mock_dependency.some_method(1, 'hello')
      mock_dependency.some_method(2, 'world')

      # Проверяем аргументы последнего вызова
      args, kwargs = mock_dependency.some_method.call_args
      self.assertEqual(args, (2, 'world'))

      # Проверяем аргументы всех вызовов
      call_list = mock_dependency.some_method.call_args_list
      self.assertEqual(len(call_list), 2)
      args1, kwargs1 = call_list[0]
      args2, kwargs2 = call_list[1]
      self.assertEqual(args1, (1, 'hello'))
      self.assertEqual(args2, (2, 'world'))
  
  • `mock_dependency.some_method.call_args`: Кортеж, содержащий аргументы последнего вызова в виде `(args, kwargs)`. `args` — это кортеж позиционных аргументов, а `kwargs` — словарь именованных аргументов.
  • `mock_dependency.some_method.call_args_list`: Список кортежей, где каждый кортеж представляет собой вызов в виде `(args, kwargs)`.

Выбор подходящего метода:

  • `assert_called_with` — простой и наиболее читаемый способ проверки одного вызова с определенными аргументами.
  • `assert_has_calls` — подходит для проверки последовательности вызовов (не обязательно подряд).
  • `side_effect` — полезен для сложных проверок или для выполнения какой-либо логики при вызове мок-объекта.
  • `call_args` и `call_args_list` — позволяют получить доступ ко всем аргументам вызовов для более детального анализа.

Пример более сложной проверки (комбинированный подход):


  import unittest
  from unittest.mock import Mock, call

  class TestMyClass(unittest.TestCase):
    def test_complex_argument_check(self):
        mock_obj = Mock()

        def side_effect_check(data, expected_keys):
            self.assertIsInstance(data, dict)
            for key in expected_keys:
                self.assertIn(key, data)
            return True

        mock_obj.some_method.side_effect = side_effect_check

        data1 = {"name": "Alice", "age": 30}
        expected_keys1 = ["name", "age"]
        mock_obj.some_method(data1, expected_keys1)

        data2 = {"city": "New York", "country": "USA"}
        expected_keys2 = ["city"]
        mock_obj.some_method(data2, expected_keys2)

        # Проверяем, что mock_obj.some_method вызывался 2 раза
        self.assertEqual(mock_obj.call_count, 2)

        # Проверяем, что side_effect был вызван корректно и assertion внутри него прошла
        # если бы assertion упала в side_effect_check - тест бы упал

        # Можно также проверить вызовы с assert_has_calls
        calls = [
            call({"name": "Alice", "age": 30}, ["name", "age"]),
            call({"city": "New York", "country": "USA"}, ["city"])
        ]
        mock_obj.some_method.assert_has_calls(calls)
  

Помните, что цель использования мок-объектов — изолировать тестируемый код и убедиться, что он правильно взаимодействует со своими зависимостями. Выбирайте метод проверки аргументов, который лучше всего соответствует вашим потребностям и обеспечивает наиболее ясное и надежное тестирование.

0