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 Sensor
classe no sensor.py
módulo:
import random
class Sensor:
@property
def temperature(self):
return random.randint(10, 45)
Linguagem de código: Python ( python )
A Sensor
classe possui uma propriedade de temperatura que retorna uma temperatura aleatória entre 10 e 45. No mundo real, a Sensor
classe precisa se conectar ao dispositivo sensor para obter a temperatura real.
Segundo, defina a Alarm
classe que usa um Sensor
objeto:
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
Linguagem de código: Python ( python )
Por padrão, a is_on
propriedade de Alarm
está desativada ( False
). O check()
método ativa o alarme se a temperatura for inferior a 18 ou superior a 42.
Assim que a is_on
propriedade de um Alarm
objeto estiver ativada, você poderá enviá-lo ao dispositivo de alarme para alertar adequadamente.
Como o temperature()
método Sensor
retorna uma temperatura aleatória, será difícil testar vários cenários para garantir que a Alarm
classe funcione corretamente.
Para resolver isso, você pode definir um stub para a Sensor
classe chamada TestSensor
. O TestSensor
possui a temperature
propriedade que retorna um valor fornecido quando seu objeto é inicializado.
Terceiro, defina o módulo TestSensor
in :test_sensor.py
class TestSensor:
def __init__(self, temperature) -> None:
self._temperature = temperature
@property
def temperature(self):
return self._temperature
Linguagem de código: Python ( python )
A TestSensor
classe é semelhante à Sensor
classe, exceto que a temperature
propriedade retorna um valor especificado no construtor.
Quarto, defina uma TestAlarm
classe no test_alarm.py
módulo de teste e importe o Alarm
e TestSensor
dos módulos alarm.py
e sensor.py
:
import unittest
from alarm import Alarm
from test_sensor import TestSensor
class TestAlarm(unittest.TestCase):
pass
Linguagem 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_default
criamos uma nova instância de alarme e usamos o assertFalse()
método para verificar se a is_on
propriedade do objeto de alarme é False
.
Execute o teste:
python -m unittest -v
Linguagem de código: Python ( python )
Saída:
test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Linguagem de código: Python ( python )
Sexto, teste o check()
método da Alarm
classe 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
TestSensor
com temperatura 25 e passe-a para oAlarm
construtor. - Chame o
check()
método do objeto de alarme. - Use
assertTrue()
para testar se ais_on
propriedade do alarme éTrue
.
Execute o teste:
python -m unittest -v
Linguagem 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
OK
Linguagem 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
TestSensor
temperatura 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 -v
Linguagem 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
OK
Linguagem de código: Python ( python )
Sétimo, teste o check()
método da Alarm
classe 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 -v
Linguagem 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
OK
Linguagem de código: Python ( python )
Usando a classe MagicMock para criar stubs
Python fornece o MagicMock
objeto no unittest.mock
módulo que permite criar stubs com mais facilidade.
Para criar um stub para a Sensor
classe usando a MagicMock
classe, você passa a Sensor
classe para o MagicMock()
construtor:
mock_sensor = MagicMock(Sensor)
Linguagem de código: Python ( python )
A mock_sensor
é a nova instância da MagicMock
classe que zomba da Sensor
classe.
Ao usar o mock_sensor
objeto, você pode definir sua propriedade ou chamar um método. Por exemplo, você pode atribuir uma temperatura específica, por exemplo, 25 à temperature
propriedade do sensor simulado assim:
mock_sensor.temperature = 25
Linguagem de código: Python ( python )
O seguinte mostra a nova versão do TestAlarm
que usa a MagicMock
classe:
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 @patch
decorador no test_check_temperature_too_low()
método. No decorador, passamos o sensor.Sensor
como alvo a ser corrigido.
Assim que usarmos o @patch
decorador, o método de teste terá o segundo parâmetro que é uma instância do MagicMock
que zomba da sensor.Sensor
classe.
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 -v
Linguagem de código: Python ( python )
Saída:
test_check_temperature_too_low (test_alarm_with_patch.TestAlarm) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
Linguagem de código: Python ( python )
Resumo
- Use stubs para retornar valores codificados para teste.
- Use
MagicMock
a classe dounittest.mock
módulo para criar stubs. - Use
patch()
para criarMagicMock
com mais facilidade.