Функция patch() в Python — использование и примеры
- Знакомство с функцией patch() в Python
- Определение цели
- Вызов patch()
- Примеры патчей в Python
- 1) Использование patch() в качестве декоратора
- 2) Использование patch() в качестве менеджера контекста
- 3) Использование patch() вручную
Знакомство с функцией patch() в Python
Модуль unittest.mock имеет функцию patch() в Python, которая позволяет временно заменить цель mock-объектом.
Целью может быть функция, метод или класс. Это строка следующего формата:
'package.module.className'
Чтобы правильно использовать patch(), вам необходимо понять два важных шага:
- Определить цель
- Как вызвать patch().
Определение цели
Чтобы определить цель:
- Цель должна быть импортируемой.
- И исправьте цель там, где она используется, а не там, откуда она пришла.
Вызов patch()
Python предоставляет вам три способа вызова patch():
- Декораторы для функции или класса.
- Контекстный менеджер.
- Ручной запуск/остановка.
Когда вы используете patch() в качестве декоратора функции или класса, внутри функции или класса цель заменяется новым объектом. Если вы используете patch() в диспетчере контекста, внутри оператора with цель заменяется новым объектом.
В обоих случаях при выходе из функции или оператора with исправление отменяется.
Примеры патчей в Python
Давайте создадим новый модуль под названием total.py для демонстрационных целей:
def read(filename): """ read a text file and return a list of numbers """ with open(filename) as f: lines = f.readlines() return [float(line.strip()) for line in lines] def calculate_total(filename): """ return the sum of numbers in a text file """ numbers = read(filename) return sum(numbers)
Как это работает.
Функция read() считывает текстовый файл, преобразует каждую строку в число и возвращает список чисел. Например, текстовый файл имеет следующие строки:
1 2 3
функция read() вернет следующий список:
[1, 2, 3]
Функция Calcult_total() использует функцию read() для получения списка чисел из файла и возвращает сумму чисел.
Чтобы протестировать Calculate_total(), вы можете создать модуль test_total_mock.py и имитировать функцию read() следующим образом:
import unittest from unittest.mock import MagicMock import total class TestTotal(unittest.TestCase): def test_calculate_total(self): total.read = MagicMock() total.read.return_value = [1, 2, 3] result = total.calculate_total('') self.assertEqual(result, 6)
Запустите тест:
python -m unittest test_total_mock.py -v
Выход:
test_calculate_total(test_total_mock.TestTotal) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Вместо прямого использования объекта MagicMock() вы можете использовать patch().
1) Использование patch() в качестве декоратора
Следующий тестовый модуль test_total_with_patch_decorator.py тестирует модуль total.py, используя patch() в качестве декоратора функции:
import unittest from unittest.mock import patch import total class TestTotal(unittest.TestCase): @patch('total.read') def test_calculate_total(self, mock_read): mock_read.return_value = [1, 2, 3] result = total.calculate_total('') self.assertEqual(result, 6)
Как это работает.
- Сначала импортируйте патч из модуля unittest.mock:
from unittest.mock import patch
- Во-вторых, декорируйте тестовый метод test_calculate_total() декоратором @patch. Целью является функция чтения всего модуля.
@patch('total.read') def test_calculate_total(self, mock_read): # ...
Из-за декоратора @patch метод test_calculate_total() имеет дополнительный аргумент mock_read, который является экземпляром MagicMock.
Обратите внимание, что вы можете назвать параметр как угодно.
Внутри метода test_calculate_total() patch() заменит функцию total.read() объектом макета_read.
- В-третьих, назначьте список return_value макетного объекта:
mock_read.return_value = [1, 2, 3]
Наконец, вызовите функцию Calcul_total() и используйте метод AssertEqual(), чтобы проверить, равна ли сумма 6.
Поскольку вместо функции total.read() будет вызываться объект mock_read(), вы можете передать любое имя файла в функцию Calculation_total():
result = total.calculate_total('') self.assertEqual(result, 6)
Запустите тест:
python -m unittest test_total_patch_decorator -v
Выход:
test_calculate_total(test_total_patch_decorator.TestTotal) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
2) Использование patch() в качестве менеджера контекста
Следующий пример иллюстрирует, как использовать patch() в качестве менеджера контекста:
import unittest from unittest.mock import patch import total class TestTotal(unittest.TestCase): def test_calculate_total(self): with patch('total.read') as mock_read: mock_read.return_value = [1, 2, 3] result = total.calculate_total('') self.assertEqual(result, 6)
Как это работает.
- Сначала исправьте функцию total.read(), используя ее в качестве объекта mock_read в диспетчере контекста:
with patch('total.read') as mock_read:
Это означает, что внутри блока with patch() заменяет функцию total.read() объектом макета_read.
- Во-вторых, присвойте список чисел свойству return_value объекта mock_read:
mock_read.return_value = [1, 2, 3]
- В-третьих, вызовите функцию Calculate_total() и проверьте, равен ли результат функции Calculate_total() 6, используя метод AssertEqual():
result = total.calculate_total('') self.assertEqual(result, 6)
Запустите тест:
python -m unittest test_total_patch_ctx_mgr -v
Выход:
test_calculate_total(test_total_patch_ctx_mgr.TestTotal) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
3) Использование patch() вручную
Следующий тестовый модуль(test_total_patch_manual.py) показывает, как использовать patch() вручную:
import unittest from unittest.mock import patch import total class TestTotal(unittest.TestCase): def test_calculate_total(self): # start patching patcher = patch('total.read') # create a mock object mock_read = patcher.start() # assign the return value mock_read.return_value = [1, 2, 3] # test the calculate_total result = total.calculate_total('') self.assertEqual(result, 6) # stop patching patcher.stop()
Как это работает.
Сначала запустите патч, вызвав patch() с целью — функцией read() всего модуля:
patcher = patch('total.read')
Затем создайте mock-объект для функции read():
mock_read = patcher.start()
Присвойте список чисел return_value объекта mock_read:
result = total.calculate_total('') self.assertEqual(result, 6)
После этого вызовите метод Calculate_total() и проверьте его результат.
def test_calculate_total(self): self.mock_read.return_value = [1, 2, 3] result = total.calculate_total('') self.assertEqual(result, 6)
Наконец, прекратите установку исправлений, вызвав метод stop() объекта patcher:
patcher.stop()