Resumo : neste tutorial, você aprenderá como usar o Python patch()
para substituir temporariamente um alvo por um objeto simulado.
Introdução ao patch Python
O unittest.mock
módulo possui um patch()
que permite substituir temporariamente um alvo por um objeto simulado.
Um destino pode ser uma função , um método ou uma classe . É uma string com o seguinte formato:
'package.module.className'
Linguagem de código: Python ( python )
Para usá-lo patch()
corretamente, você precisa entender duas etapas importantes:
- Identifique o alvo
- Como ligar
patch()
Identificando o alvo
Para identificar um alvo:
- O destino deve ser importável.
- E corrija o alvo onde ele é usado, não de onde vem.
Chamando patch
Python fornece três maneiras de chamar patch()
:
- Decoradores para uma função ou classe.
- Gerenciador de contexto
- Partida/parada manual
Quando você usa o patch()
como decorador de uma função ou classe, dentro da função ou classe o alvo é substituído por um novo objeto.
Se você usar o patch em um gerenciador de contexto, dentro da with
instrução, o destino será substituído por um novo objeto.
Em ambos os casos, quando a função ou with
instrução é encerrada, o patch é desfeito.
Exemplos de patches Python
Vamos criar um novo módulo chamado total.py
para fins de demonstração:
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)
Linguagem de código: Python ( python )
Como funciona.
A read()
função lê um arquivo de texto, converte cada linha em um número e retorna uma lista de números. Por exemplo, um arquivo de texto possui as seguintes linhas:
1
2
3
Linguagem de código: Python ( python )
a read()
função retornará a seguinte lista:
[1, 2, 3]
Linguagem de código: Python ( python )
A calculate_total()
função usa a read()
função para obter uma lista de números de um arquivo e retorna a soma dos números.
Para testar calculate_total()
, você pode criar um test_total_mock.py
módulo e simular a read()
função da seguinte maneira:
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)
Linguagem de código: Python ( python )
Execute o teste:
python -m unittest test_total_mock.py -v
Linguagem de código: Python ( python )
Saída:
test_calculate_total (test_total_mock.TestTotal) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Linguagem de código: Python ( python )
Em vez de usar o MagicMock()
objeto diretamente, você pode usar o arquivo patch()
.
1) Usando patch() como decorador
O módulo de teste a seguir test_total_with_patch_decorator.py
testa o total.py
módulo usando o patch()
decorador de função:
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)
Linguagem de código: Python ( python )
Como funciona.
Primeiro, importe o patch do unittest.mock
módulo:
from unittest.mock import patch
Linguagem de código: Python ( python )
Segundo, decore o test_calculate_total()
método de teste com o @patch
decorador. O alvo é a função de leitura do módulo total.
@patch('total.read')
def test_calculate_total(self, mock_read):
# ...
Linguagem de código: Python ( python )
Por causa do @patch
decorador, o test_calculate_total()
método possui um argumento adicional mock_read que é uma instância do MagicMock.
Observe que você pode nomear o parâmetro como quiser.
Dentro do test_calculate_total()
método, patch()
substituirá o total. read()
função com o objeto mock_read.
Terceiro, atribua uma lista ao return_value do objeto simulado:
mock_read.return_value = [1, 2, 3]
Linguagem de código: Python ( python )
Por fim, chame a calculate_total()
função e use o assertEqual()
método para testar se o total é 6.
Porque o objeto mock_read será chamado em vez do total. read()
função, você pode passar qualquer nome de arquivo para a calculate_total()
função:
result = total.calculate_total('')
self.assertEqual(result, 6)
Linguagem de código: Python ( python )
Execute o teste:
python -m unittest test_total_patch_decorator -v
Linguagem de código: Python ( python )
Saída:
test_calculate_total (test_total_patch_decorator.TestTotal) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Linguagem de código: Python ( python )
2) Usando patch() como gerenciador de contexto
O exemplo a seguir ilustra como usar o patch()
como gerenciador de contexto:
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)
Linguagem de código: Python ( python )
Como funciona.
Primeiro, corrija total.read()
a função usando como mock_read
objeto em um gerenciador de contexto:
with patch('total.read') as mock_read:
Linguagem de código: Python ( python )
Isso significa que dentro do with
bloco, patch()
substitui a total.read()
função pelo objeto mock_read.
Segundo, atribua uma lista de números à return_value
propriedade do mock_read
objeto:
mock_read.return_value = [1, 2, 3]
Linguagem de código: Python ( python )
Terceiro, chame a calculate_total()
função e teste se o resultado da calculate_total()
função é igual a 6 usando o assertEqual()
método:
result = total.calculate_total('')
self.assertEqual(result, 6)
Linguagem de código: Python ( python )
Execute o teste:
python -m unittest test_total_patch_ctx_mgr -v
Linguagem de código: Python ( python )
Saída:
test_calculate_total (test_total_patch_ctx_mgr.TestTotal) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Linguagem de código: Python ( python )
3) Usando patch() manualmente
O seguinte módulo de teste ( test_total_patch_manual.py
) mostra como usar patch()
manualmente:
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()
Linguagem de código: Python ( python )
Como funciona.
Primeiro, iniciar um patch chamando patch()
um alvo é a read()
função do total
módulo:
patcher = patch('total.read')
Linguagem de código: Python ( python )
A seguir, crie um objeto simulado para a read()
função:
mock_read = patcher.start()
Linguagem de código: Python ( python )
Em seguida, atribua uma lista de números ao return_value
objeto mock_read
:
result = total.calculate_total('')
self.assertEqual(result, 6)
Linguagem de código: Python ( python )
Depois disso, chame o calculate_total()
e teste seu resultado.
def test_calculate_total(self):
self.mock_read.return_value = [1, 2, 3]
result = total.calculate_total('')
self.assertEqual(result, 6)
Linguagem de código: Python ( python )
Finalmente, pare de aplicar o patch chamando o stop()
método do objeto patcher:
patcher.stop()
Linguagem de código: Python ( python )
Resumo
- Use o módulo
patch()
fromunittest.mock
para substituir temporariamente um alvo por um objeto simulado. - Use-o
patch()
como decorador, gerenciador de contexto ou chamestart()
estop()
aplique patches manualmente.