Функция patch() в Python — использование и примеры

Содержание

Знакомство с функцией 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()
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *