Patch Python()

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.mockmó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 ligarpatch()

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 withinstrução, o destino será substituído por um novo objeto.

Em ambos os casos, quando a função ou withinstrução é encerrada, o patch é desfeito.

Exemplos de patches Python

Vamos criar um novo módulo chamado total.pypara 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
3Linguagem 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.pymó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 -vLinguagem de código:  Python  ( python )

Saída:

test_calculate_total (test_total_mock.TestTotal) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OKLinguagem 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.pytesta o total.pymó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.mockmódulo:

from unittest.mock import patchLinguagem de código:  Python  ( python )

Segundo, decore o test_calculate_total()método de teste com o @patchdecorador. 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 @patchdecorador, 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 -vLinguagem de código:  Python  ( python )

Saída:

test_calculate_total (test_total_patch_decorator.TestTotal) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OKLinguagem 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_readobjeto em um gerenciador de contexto:

with patch('total.read') as mock_read:Linguagem de código:  Python  ( python )

Isso significa que dentro do withbloco, patch()substitui a total.read()função pelo objeto mock_read.

Segundo, atribua uma lista de números à return_valuepropriedade do mock_readobjeto:

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

Saída:

test_calculate_total (test_total_patch_ctx_mgr.TestTotal) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OKLinguagem 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 totalmó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_valueobjeto 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()from unittest.mockpara substituir temporariamente um alvo por um objeto simulado.
  • Use-o patch()como decorador, gerenciador de contexto ou chame start()e stop()aplique patches manualmente.

Deixe um comentário

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