Мокинг/патчинг переменных класса и экземпляра в тестировании Python
В этой статье представлен обзор того, как использовать модуль unittest
для моделирования или исправления переменных класса и экземпляра в классе Python. К концу этой статьи вы получите следующие знания:
- Как исправить/ имитировать атрибуты класса.
- Как исправить/имитировать атрибуты экземпляра.
- Различие между
patch
иpatch.object
в модулеunittest
.
Модуль unittest
на Python представляет собой интегрированную платформу тестирования, которая предлагает ряд инструментов для написания и выполнения тестовых примеров. Он является неотъемлемой частью стандартной библиотеки Python и используется для выполнения модульного тестирования - важнейшей практики в разработке программного обеспечения, которая обеспечивает точность отдельных компонентов или функций в программе.
Каждый класс тестовых примеров обычно включает методы, которые исследуют конкретные аспекты вашего кода. Эти методы обычно называются с префиксом “test” в качестве префикса и включают утверждения для проверки ожидаемого поведения.
Использование patch()
:
1. Имитация/исправление атрибута класса!
Атрибуты класса - это переменные или методы, которые принадлежат классу и являются общими для всех экземпляров класса.
Теперь давайте начнем с создания макета переменной класса. Ниже приведен простой класс с именем “Car” в файле “variables.py”, который включает “cost” в качестве переменной класса или атрибута.
#variables.py file class Car: cost = 2000 print(Car.cost) # prints 2000
Когда мы печатаем Car.cost
, отображается ожидаемое значение 2000. Однако я намерен изменить это поведение; моя цель - отображать 5000 при выводе значения стоимость. Для достижения этой цели мы можем использовать функцию patch
из модуля unittest.mock
.
Функция patch
предназначена для модификации объектов в целях тестирования. Она оказывается полезной для манипулирования атрибутами классов, экземпляров и даже атрибутами модулей. Его универсальность позволяет использовать его в самых разных сценариях, где требуется модификация.
Чтобы проиллюстрировать эту функциональность, мы создадим класс TestCar
, посвященный тестированию этих изменений, в отдельном файле с именем test_variables.py
.
# test_variables.py from unittest.mock import patch # import patch from unittest.mock file from variables import Car # importing Car class from variables file class TestCar: def test_value_with_patch(self): # mocking a class variable # provide `file_name.class_name.variable_name` as str with patch('variables.Car.cost', 5000): print("\n", Car.cost) # this prints 5000
Мы используем контекстный менеджер (используя инструкцию with
), который использует функцию patch
для временного изменения переменной класса cost
.
Функция patch
требует указания полного имени целевой переменной класса, которую мы собираемся изменить, а также нового значения, которое временно присваивается переменной класса. В данном конкретном случае мы указываем variables.Car.cost
в качестве пути к переменной класса cost
, а значение 5000
является тем, которому мы временно присваиваем cost
.
В случае variables.Car.cost
термин variables
соответствует имени файла, Car
означает имя класса, а cost
представляет переменную класса, которую мы используем. временно корректируются для возврата значения 5000
.
2. Мокинг/патчинг метода класса!
Теперь, когда мы узнали, как исправить переменную класса, давайте перейдем к пониманию того, как исправить метод класса.
Мы добьемся этого, написав метод класса с именем get_cost()
, который возвращает значение переменной класса cost
.
# variables.py class Car: cost = 2000 @classmethod def get_cost(cls): return cls.cost print(Car.cost) # prints 2000 print(Car.get_cost()) # prints 2000
Строка print(Car.get_cost())
, которая в данный момент выводится 2000. Я намерен изменить поведение метода класса get_cost()
, издеваясь над ним, чтобы он возвращал значение 10000.
Для достижения этой цели я создам новый тестовый пример с именем test_get_cost()
в классе TestCar
. Этот тестовый пример будет разработан для оценки функциональности метода класса get_cost()
.
# test_variables.py from unittest.mock import patch from variables import Car # importing Car class from variables file class TestCar: def test_value_with_patch(self): # mocking a class variable # provide `file_name.class_name.attribute_name` as str with patch('variables.Car.cost', 5000): print("\n", Car.cost) def test_get_cost(self): # mocking a class method # provide `file_name.class_name.attribute_name` as str # return_value tells the what to return for the get_cost() with patch("variables.Car.get_cost", return_value=10000): print("\n", Car.get_cost()) # this prints 10000 # still prints 2000, because we are patching get_cost not cost print(Car.cost) # prints 2000 still
Обратите внимание на строку with patch("variables.Car.get_cost", return_value=10000)
. В данном случае мы используем метод patch
для моделирования поведения get_cost()
. Чтобы достичь этого, нам нужно указать полный путь к методу класса.
variables.Car.get_cost
: В этой строке "переменные" относятся к имени файла, "Car" относится к имени класса, а "get_cost" означает метод класса, который мы собираемся заменить возвращаемым значением 10000.
Параметр return_value
определяет конкретное значение, которое имитационная функция должна выдавать в качестве выходных данных.
Пожалуйста, помните, что мы исправляем метод
get_cost()
. А не непосредственно переменнуюcost
. Итак,print(Car.cost)
все еще печатает 2000.
Использование patch.object()
:
Если значение переменной меняется от объекта к объекту, то такие переменные называются переменными экземпляра. Для каждого объекта будет создана отдельная копия переменной экземпляра.
Переменные экземпляра не являются общими для объектов. Переменная экземпляра - это переменная, которая принадлежит объекту/экземпляру класса. Он создается при создании экземпляра и уничтожается при уничтожении экземпляра.
1. Мокинг переменной экземпляра!
Давайте создадим новую переменную экземпляра insurance
со значением 5000. Измените класс Car
, чтобы включить переменную экземпляра insurance
.
# variables.py class Car: cost = 2000 def __init__(self): # creating an instance variable self.insurance = 5000 @classmethod def get_cost(cls): return cls.cost print(Car.cost) # prints 2000 print(Car.get_cost()) # prints 2000 # create an instance for the class Car car = Car() print(car.insurance) # prints 5000
Я намерен исправить переменную insurance
, которая является одной из переменных нашего экземпляра, чтобы она возвращала значение 1000. Для достижения этой цели библиотека unittest.mock
предоставляет функцию patch.object()
, специально разработанную для исправления атрибутов экземпляра.
Теперь давайте приступим к созданию тестового метода для манипулирования переменной экземпляра. Нам нужно будет внести изменения в файл test_variables.py
, как описано ниже:
# test_variables.py from unittest.mock import patch from variables import Car # importing Car class from variables file class TestCar: def test_value_with_patch(self): # mocking a class variable # provide `file_name.class_name.attribute_name` as str with patch('variables.Car.cost', 5000): print("\n", Car.cost) def test_get_cost(self): # mocking a class method # provide `file_name.class_name.attribute_name` as str with patch("variables.Car.get_cost", return_value=10000): print("\n", Car.get_cost()) # this prints 10000 # still prints 2000, because we are patching get_cost not cost print(Car.cost) # prints 2000 still def test_insurance_variable(self): # mocking an instance variable car1 = Car() # "insurance" is present in the car1 instance, so provide the name # remember that we have to provide the name of variable in str with patch.object(car1, "insurance", new=1000): print(car1.insurance) # prints 1000
Следите за линией with patch.object(car1, "insurance", new=1000)
В данном контексте:
car1
является экземпляром класса, скорее всего, экземпляром классаCar
в вашем коде."insurance"
является атрибутом экземпляраcar1
, который вы хотите временно изменить.new=1000
указывает, что вы хотите установить значение атрибута"insurance"
равным1000
на время действия блока, управляемого контекстом.
Внутри блока, управляемого контекстом, любой код, который обращается к атрибуту insurance
экземпляра car1
, увидит его значение как 1000
вместо исходного значения.
2. Имитация/патчинг метода экземпляра!
Теперь, когда мы узнали, как исправить переменную экземпляра, давайте перейдем к пониманию того, как исправить метод экземпляра — по сути, метод объекта.
Мы добьемся этого, добавив метод экземпляра к классу Car
в файле variables.py
, как показано ниже:
# variables.py class Car: cost = 2000 def __init__(self): # creating an instance variable self.insurance = 5000 @classmethod def get_cost(cls): return cls.cost def get_insurance(self): return self.insurance print(Car.cost) # prints 2000 print(Car.get_cost()) # prints 2000 # create an instance for the class Car car = Car() print(car.insurance) # prints 5000 print(car.get_insurance()) # prints 5000
Я хочу поиздеваться над методом экземпляра get_insurance()
, чтобы он возвращал 3000 в качестве нового значения.
Пожалуйста, помните, что я всего лишь издеваюсь над методом
get_insurance()
, но не над значением переменнойinsurance
.
Давайте напишем новый тестовый пример для тестирования этого метода экземпляра get_insurance()
в классе TestCar
файла test_variables.py
.
# test_variables.py from unittest.mock import patch from variables import Car # importing Car class from variables file class TestCar: def test_value_with_patch(self): # mocking a class variable # provide `file_name.class_name.attribute_name` as str with patch('variables.Car.cost', 5000): print("\n", Car.cost) def test_get_cost(self): # mocking a class method # provide `file_name.class_name.attribute_name` as str with patch("variables.Car.get_cost", return_value=10000): print("\n", Car.get_cost()) # this prints 10000 # still prints 2000, because we are patching get_cost not cost print(Car.cost) # prints 2000 still def test_insurance_variable(self): # mocking an instance variable car1 = Car() # "insurance" is present in the car1 instance, so provide the name # remember that we have to provide the name of variable in str with patch.object(car1, "insurance", new=1000): print(car1.insurance) # prints 1000 def test_insurance_method(self): # mocking an instance method car = Car() # "get_insurance" is present in the car instance, so provide the name # remember that we have to provide the name of the method in str with patch.object(car, "get_insurance", return_value=3000): print(car.get_insurance()) # prints 3000
Давайте разберем код test_insurance_method()
.
Контекстный менеджер patch.object
из модуля unittest.mock
предназначен для временного имитационного моделирования get_insurance()
метода экземпляра из Car
класс.
patch.object(car, "get_insurance", return_value=3000)
: Эта строка устанавливает контекст, в котором методget_insurance()
объектаcar
будет временно заменен макетом, который возвращает3000
в качестве своего результата . Параметрreturn_value
определяет значение, которое должен возвращать mock при вызове метода.print(car.get_insurance())
: Внутри контекста, когда вызывается методget_insurance()
объектаcar
, он возвращает имитируемое значение3000
, а не выполняет реальную логику метода. Это позволяет вам изолировать и тестировать определенные части вашего кода, не влияя на реальное поведение метода.
Цель этого кода - протестировать поведение кода, который взаимодействует с методом get_insurance()
объекта car
. Временно заменив метод макетом, который возвращает известное значение (в данном случае 3000
), вы можете гарантировать, что результаты вашего тестирования будут предсказуемыми и согласованными, независимо от фактической реализации метода get_insurance()
.
Как только код внутри блока
patch.object
будет выполнен, исходное поведение методаget_insurance()
будет восстановлено, и последующие вызовыcar.get_insurance()
будут выполняться как обычно.
К этому времени вы должны были понять основную разницу между patch
и patch.object.
Давайте еще раз подведем итог
patch
это более высокоуровневая функция для исправления / имитирования атрибутов класса.patch.object
полезен при исправлении/издевательстве над атрибутами экземпляра экземпляра класса.- Исправление атрибута экземпляра экземпляра принадлежит этому конкретному экземпляру и не применяется к другому экземпляру, поскольку каждый экземпляр класса получает новую копию атрибутов экземпляра.
Методы, которые я использовал как для patch
, так и для patch.object
, не являются исчерпывающими; существует несколько других способов их использования. Эти функции также могут служить декораторами. Для получения дополнительной информации, пожалуйста, обратитесь к документации, представленной в модуле unittest.