Esboços de Python

Resumo : neste tutorial, você aprenderá como usar stubs Python para isolar partes do seu programa umas das outras para testes de unidade.

Introdução aos stubs do Python

Stubs são testes duplos que retornam valores codificados. O objetivo principal dos stubs é preparar um estado específico do sistema em teste.

Os stubs são benéficos porque retornam resultados consistentes, tornando o teste mais fácil de escrever. Além disso, você pode executar testes mesmo se os componentes presentes nos stubs ainda não estiverem funcionando.

Suponha que você precise desenvolver um sistema de alarme que monitore a temperatura de uma sala como uma sala de servidores.

Para fazer isso, você precisa configurar um dispositivo sensor de temperatura e usar os dados desse sensor para alertar se a temperatura está abaixo ou acima de uma temperatura específica.

Primeiro, defina uma Sensorclasse no sensor.pymódulo:

import random


class Sensor:
    @property
    def temperature(self):
        return random.randint(10, 45)Linguagem de código:  Python  ( python )

A Sensorclasse possui uma propriedade de temperatura que retorna uma temperatura aleatória entre 10 e 45. No mundo real, a Sensorclasse precisa se conectar ao dispositivo sensor para obter a temperatura real.

Segundo, defina a Alarmclasse que usa um Sensorobjeto:

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_onLinguagem de código:  Python  ( python )

Por padrão, a is_onpropriedade de Alarmestá desativada ( False). O check()método ativa o alarme se a temperatura for inferior a 18 ou superior a 42.

Assim que a is_onpropriedade de um Alarmobjeto estiver ativada, você poderá enviá-lo ao dispositivo de alarme para alertar adequadamente.

Como o temperature()método Sensorretorna uma temperatura aleatória, será difícil testar vários cenários para garantir que a Alarmclasse funcione corretamente.

Para resolver isso, você pode definir um stub para a Sensorclasse chamada TestSensor. O TestSensorpossui a temperaturepropriedade que retorna um valor fornecido quando seu objeto é inicializado.

Terceiro, defina o módulo TestSensorin :test_sensor.py

class TestSensor:
    def __init__(self, temperature) -> None:
        self._temperature = temperature

    @property
    def temperature(self):
        return self._temperatureLinguagem de código:  Python  ( python )

A TestSensorclasse é semelhante à Sensorclasse, exceto que a temperaturepropriedade retorna um valor especificado no construtor.

Quarto, defina uma TestAlarmclasse no test_alarm.pymódulo de teste e importe o Alarme TestSensordos módulos alarm.pye sensor.py:

import unittest
from alarm import Alarm
from test_sensor import TestSensor


class TestAlarm(unittest.TestCase):
   passLinguagem de código:  Python  ( python )

Quinto, teste se o alarme está desligado por padrão:

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) Linguagem de código:  Python  ( python )

No test_is_alarm_off_by_defaultcriamos uma nova instância de alarme e usamos o assertFalse()método para verificar se a is_onpropriedade do objeto de alarme é False.

Execute o teste:

python -m unittest -vLinguagem de código:  Python  ( python )

Saída:

test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok

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

OKLinguagem de código:  Python  ( python )

Sexto, teste o check()método da Alarmclasse caso a temperatura esteja muito alta:

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)Linguagem de código:  Python  ( python )

No test_check_temperature_too_high()método de teste:

  • Crie uma instância do TestSensorcom temperatura 25 e passe-a para o Alarmconstrutor.
  • Chame o check()método do objeto de alarme.
  • Use assertTrue()para testar se a is_onpropriedade do alarme é True.

Execute o teste:

python -m unittest -vLinguagem de código:  Python  ( python )

Saída:

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

OKLinguagem de código:  Python  ( python )

O alarme está ativado porque a temperatura é superior a 24.

Sétimo, teste o check()método da classe Alarm quando a temperatura estiver muito baixa:

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)Linguagem de código:  Python  ( python )

No test_check_temperature_too_low()método de teste:

  • Crie uma instância com TestSensortemperatura 17 e passe-a para o construtor Alarm.
  • Chame o check()método do objeto de alarme.
  • Use assertTrue()para testar se a propriedade is_on do alarme é True.

Execute o teste:

python -m unittest -vLinguagem de código:  Python  ( python )

Saída:

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

OKLinguagem de código:  Python  ( python )

Sétimo, teste o check()método da Alarmclasse se a temperatura estiver na faixa segura (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)Linguagem de código:  Python  ( python )

No test_check_normal_temperature()método criamos um TestSensor com temperatura 20 e passamos para o construtor Alarm. Como a temperatura está na faixa (18, 24), o alarme deve estar desligado.

Execute o teste:

python -m unittest -vLinguagem de código:  Python  ( python )

Saída:

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

OKLinguagem de código:  Python  ( python )

Usando a classe MagicMock para criar stubs

Python fornece o MagicMockobjeto no unittest.mockmódulo que permite criar stubs com mais facilidade.

Para criar um stub para a Sensorclasse usando a MagicMockclasse, você passa a Sensorclasse para o MagicMock()construtor:

mock_sensor = MagicMock(Sensor)Linguagem de código:  Python  ( python )

A mock_sensoré a nova instância da MagicMockclasse que zomba da Sensorclasse.

Ao usar o mock_sensorobjeto, você pode definir sua propriedade ou chamar um método. Por exemplo, você pode atribuir uma temperatura específica, por exemplo, 25 à temperaturepropriedade do sensor simulado assim:

mock_sensor.temperature = 25Linguagem de código:  Python  ( python )

O seguinte mostra a nova versão do TestAlarmque usa a MagicMockclasse:

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)Linguagem de código:  Python  ( python )

Usando o método patch()

Para facilitar o trabalho MagicMock, você pode usá-lo patch()como decorador. Por exemplo:

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)Linguagem de código:  Python  ( python )

Neste exemplo, usamos um @patchdecorador no test_check_temperature_too_low()método. No decorador, passamos o sensor.Sensorcomo alvo a ser corrigido.

Assim que usarmos o @patchdecorador, o método de teste terá o segundo parâmetro que é uma instância do MagicMockque zomba da sensor.Sensorclasse.

Dentro do método de teste, definimos a propriedade de temperatura do sensor como 10, criamos uma nova instância da classe Alarm, chamamos o check()método e usamos o assertTrue()método para testar se o alarme está ativado.

Execute o teste:

python -m unittest -vLinguagem de código:  Python  ( python )

Saída:

test_check_temperature_too_low (test_alarm_with_patch.TestAlarm) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001sLinguagem de código:  Python  ( python )

Resumo

  • Use stubs para retornar valores codificados para teste.
  • Use MagicMocka classe do unittest.mockmódulo para criar stubs.
  • Use patch()para criar MagicMockcom mais facilidade.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *