Заглушки в Python — что такое и как использовать

Содержание

Что такое заглушки в Python?

Заглушки в Python — это тестовые двойники, которые возвращают жестко закодированные значения. Основная цель заглушек — подготовить определенное состояние тестируемой системы.

Заглушки полезны, поскольку они возвращают согласованные результаты, что упрощает написание теста. Также вы можете запускать тесты, даже если компоненты, в которых присутствуют заглушки, еще не работают.

Предположим, вам нужно разработать систему сигнализации, которая контролирует температуру в помещении, например в серверной. Для этого вам необходимо настроить устройство датчика температуры и использовать данные этого датчика, чтобы предупреждать, если температура ниже или выше определенной.

Сначала определите класс Sensor в модуле Sensor.py:

import random


class Sensor:
    @property
    def temperature(self):
        return random.randint(10, 45)

Класс Sensor имеет свойство temperature, которое возвращает случайную температуру от 10 до 45. В реальном мире классу Sensor необходимо подключиться к сенсорному устройству, чтобы получить фактическую температуру.

  • Во-вторых, определите класс Alarm, который использует объект Sensor:
from sensor import Sensor


class Alarm:
    def __init__(self, sensor=None) -> None:
        self._low = 18
        self._high = 24
        self._sensor = sensor or Sensor()
        self._is_on = False

    def check(self):
        temperature = self._sensor.temperature
        if temperature < self._low or temperature > self._high:
            self._is_on = True

    @property
    def is_on(self):
        return self._is_on

По умолчанию свойство is_on сигнала тревоги отключено(False). Метод check() включает сигнализацию, если температура ниже 18 или выше 42. Как только свойство is_on объекта Alarm включено, вы можете отправить его на устройство сигнализации для соответствующего оповещения.

Поскольку метод temperature() возвращает случайную температуру, будет сложно протестировать различные сценарии, чтобы убедиться, что класс Alarm работает правильно.

Чтобы решить эту проблему, вы можете определить заглушку для класса Sensor под названием TestSensor. TestSensor имеет свойство температуры, которое возвращает значение, предоставленное при инициализации его объекта.

  • В-третьих, определите TestSensor в модуле test_sensor.py:
class TestSensor:
    def __init__(self, temperature) -> None:
        self._temperature = temperature

    @property
    def temperature(self):
        return self._temperature

Класс TestSensor аналогичен классу Sensor, за исключением того, что свойство температуры возвращает значение, указанное в конструкторе.

  • В-четвертых, определите класс TestAlarm в тестовом модуле test_alarm.py и импортируйте Alarm и TestSensor из модулей Alarm.py и Sensor.py:
import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
   pass
  • В-пятых, проверьте, выключен ли будильник по умолчанию:
import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
    def test_is_alarm_off_by_default(self):
        alarm = Alarm()
        self.assertFalse(alarm.is_on)

В test_is_alarm_off_by_default мы создаем новый экземпляр сигнала тревоги и используем метод AssertFalse(), чтобы проверить, имеет ли свойство is_on объекта сигнала тревоги значение False.

Запустите тест:

python -m unittest -v

Выход:

test_is_alarm_off_by_default(test_alarm.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
  • В-шестых, протестируйте метод check() класса Alarm на случай, если температура слишком высокая:
import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
    def test_is_alarm_off_by_default(self):
        alarm = Alarm()
        self.assertFalse(alarm.is_on)

    def test_check_temperature_too_high(self):
        alarm = Alarm(TestSensor(25))
        alarm.check()
        self.assertTrue(alarm.is_on)

В тестовом методе test_check_temperature_too_high():

  • Создайте экземпляр TestSensor с температурой 25 и передайте его конструктору Alarm.
  • Вызовите метод check() объекта сигнализации.
  • Используйте метод AssertTrue(), чтобы проверить, имеет ли свойство is_on сигнала тревоги значение True.

Запустите тест:

python -m unittest -v

Выход:

test_check_temperature_too_high(test_alarm.TestAlarm) ... ok
test_is_alarm_off_by_default(test_alarm.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

Сигнал включен, потому что температура выше 24.

  • В-седьмых, протестируйте метод check() класса Alarm, когда температура слишком низкая:
import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
    def test_is_alarm_off_by_default(self):
        alarm = Alarm()
        self.assertFalse(alarm.is_on)

    def test_check_temperature_too_high(self):
        alarm = Alarm(TestSensor(25))
        alarm.check()
        self.assertTrue(alarm.is_on)

    def test_check_temperature_too_low(self):
        alarm = Alarm(TestSensor(17))
        alarm.check()
        self.assertTrue(alarm.is_on)

В тестовом методе test_check_temperature_too_low():

  • Создайте экземпляр TestSensor с температурой 17 и передайте его конструктору Alarm.
  • Вызовите метод check() объекта сигнализации.
  • Используйте метод AssertTrue(), чтобы проверить, имеет ли свойство is_on сигнала тревоги значение True.

Запустите тест:

python -m unittest -v

Выход:

test_check_temperature_too_high(test_alarm.TestAlarm) ... ok
test_check_temperature_too_low(test_alarm.TestAlarm) ... ok
test_is_alarm_off_by_default(test_alarm.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK
  • В-восьмых, проверьте метод check() класса Alarm, если температура находится в безопасном диапазоне(18, 24):
import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
    def test_is_alarm_off_by_default(self):
        alarm = Alarm()
        self.assertFalse(alarm.is_on)

    def test_check_temperature_too_high(self):
        alarm = Alarm(TestSensor(25))
        alarm.check()
        self.assertTrue(alarm.is_on)

    def test_check_temperature_too_low(self):
        alarm = Alarm(TestSensor(15))
        alarm.check()
        self.assertTrue(alarm.is_on)

    def test_check_normal_temperature(self):
        alarm = Alarm(TestSensor(20))
        alarm.check()
        self.assertFalse(alarm.is_on)

В методе test_check_normal_temperature() мы создаем TestSensor с температурой 20 и передаем его конструктору Alarm. Поскольку температура находится в диапазоне(18, 24), сигнализация должна быть отключена.

Запустите тест:

python -m unittest -v

Выход:

test_check_normal_temperature(test_alarm.TestAlarm) ... ok
test_check_temperature_too_high(test_alarm.TestAlarm) ... ok
test_check_temperature_too_low(test_alarm.TestAlarm) ... ok
test_is_alarm_off_by_default(test_alarm.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

Использование класса MagicMock для создания заглушек

Python предоставляет вам объект MagicMock в модуле unittest.mock, который позволяет упростить создание заглушек.

Чтобы создать заглушку для класса Sensor с помощью класса MagicMock, вы передаете класс Sensor конструктору MagicMock():

mock_sensor = MagicMock(Sensor)

Mock_sensor — это новый экземпляр класса MagicMock, который имитирует класс Sensor.

Используя объект mock_sensor, вы можете установить его свойство или вызвать метод. Например, вы можете назначить определенную температуру, например, 25, свойству температуры макета датчика следующим образом:

mock_sensor.temperature = 25

Ниже показана новая версия TestAlarm, использующая класс MagicMock:

import unittest
from unittest.mock import MagicMock
from alarm import Alarm
from sensor import Sensor


class TestAlarm(unittest.TestCase):
    def setUp(self):
        self.mock_sensor = MagicMock(Sensor)
        self.alarm = Alarm(self.mock_sensor)

    def test_is_alarm_off_by_default(self):
        alarm = Alarm()
        self.assertFalse(alarm.is_on)

    def test_check_temperature_too_high(self):
        self.mock_sensor.temperature = 25
        self.alarm.check()
        self.assertTrue(self.alarm.is_on)

    def test_check_temperature_too_low(self):
        self.mock_sensor.temperature = 15
        self.alarm.check()
        self.assertTrue(self.alarm.is_on)

    def test_check_normal_temperature(self):
        self.mock_sensor.temperature = 20
        self.alarm.check()
        self.assertFalse(self.alarm.is_on)

Использование метода patch()

Чтобы упростить работу с MagicMock, вы можете использовать patch() в качестве декоратора. Например:

import unittest
from unittest.mock import patch
from alarm import Alarm


class TestAlarm(unittest.TestCase):
    @patch('sensor.Sensor')
    def test_check_temperature_too_low(self, sensor):
        sensor.temperature = 10
        alarm = Alarm(sensor)
        alarm.check()
        self.assertTrue(alarm.is_on)

В этом примере мы используем декоратор @patch в методе test_check_temperature_too_low(). В декораторе мы передаем Sensor.Sensor в качестве цели для исправления.

Как только мы воспользуемся декоратором @patch, метод тестирования будет иметь второй параметр, который является экземпляром MagicMock, который имитирует класс Sensor.Sensor.

Внутри метода тестирования мы устанавливаем для свойства температуры датчика значение 10, создаем новый экземпляр класса Alarm, вызываем метод check() и используем метод AssertTrue(), чтобы проверить, включен ли сигнал тревоги.

Запустите тест:

python -m unittest -v

Выход:

test_check_temperature_too_low(test_alarm_with_patch.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s
Похожие посты
Добавить комментарий

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