Partilhar via


Teste unitário: funções duráveis em Python

O teste de unidade é uma parte importante das práticas modernas de desenvolvimento de software. Os testes de unidade verificam o comportamento da lógica de negócios e protegem contra a introdução de alterações significativas que passem despercebidas no futuro. As funções duráveis podem facilmente crescer em complexidade, portanto, a introdução de testes de unidade ajuda a evitar alterações de rutura. As seções a seguir explicam como realizar testes unitários nos três tipos de funções - cliente de orquestração, orquestrador e funções de entidade.

Observação

Este guia aplica-se apenas a aplicativos Durable Functions escritos no modelo de programação Python v2.

Pré-requisitos

Os exemplos neste artigo exigem o conhecimento dos seguintes conceitos e estruturas:

Configurando o ambiente de teste

Para testar funções duráveis, é crucial configurar um ambiente de teste adequado. Isso inclui a criação de um diretório de teste e a instalação do módulo Python unittest em seu ambiente Python. Para obter mais informações, consulte Visão geral do teste de unidade Python do Azure Functions.

Teste unitário de funções de gatilho

As funções de gatilho, muitas vezes referidas como funções de cliente , iniciam orquestrações e eventos externos. Para testar estas funções:

  • Utilize o DurableOrchestrationClient para simular a execução da orquestração e o gerenciamento de status.
  • Atribua DurableOrchestrationClient métodos como start_new, get_statusou raise_event com funções fictícias que retornam valores esperados.
  • Invoque diretamente a função de cliente HTTP utilizando um cliente simulado e outros dados necessários, como um req (objeto de solicitação HTTP) para funções de cliente ativador HTTP.
  • Utilize ferramentas e asserções unittest.mock para verificar o comportamento esperado de início de orquestração, parâmetros e respostas HTTP.
import asyncio
import unittest
import azure.functions as func
from unittest.mock import AsyncMock, Mock, patch

from function_app import start_orchestrator

class TestFunction(unittest.TestCase):
  @patch('azure.durable_functions.DurableOrchestrationClient')
  def test_HttpStart(self, client):
    # Get the original method definition as seen in the function_app.py file
    func_call = http_start.build().get_user_function().client_function

    req = func.HttpRequest(method='GET',
                           body=b'{}',
                           url='/api/my_second_function',
                           route_params={"functionName": "my_orchestrator"})

    client.start_new = AsyncMock(return_value="instance_id")
    client.create_check_status_response = Mock(return_value="check_status_response")

    # Execute the function code
    result = asyncio.run(func_call(req, client))

    client.start_new.assert_called_once_with("my_orchestrator")
    client.create_check_status_response.assert_called_once_with(req, "instance_id")
    self.assertEqual(result, "check_status_response")

Funções do orquestrador de testes de unidade

As funções do Orchestrator gerenciam a execução de várias funções de atividade. Para testar um orquestrador:

  • Faça o mock da DurableOrchestrationContext para controlar a execução da função.
  • Substitua DurableOrchestrationContext os métodos necessários para a execução do orquestrador, como call_activity ou create_timer por funções simuladas. Essas funções normalmente retornarão objetos do tipo TaskBase com uma result propriedade.
  • Chame o orquestrador recursivamente, passando o resultado da tarefa gerada pela declaração yield anterior para a próxima.
  • Verifique o resultado do orchestrador usando os resultados retornados do orchestrador e unittest.mock.
import unittest
from unittest.mock import Mock, patch, call
from datetime import timedelta
from azure.durable_functions.testing import orchestrator_generator_wrapper

from function_app import my_orchestrator


class TestFunction(unittest.TestCase):
  @patch('azure.durable_functions.DurableOrchestrationContext')
  def test_chaining_orchestrator(self, context):
    # Get the original method definition as seen in the function_app.py file
    func_call = my_orchestrator.build().get_user_function().orchestrator_function

    # The mock_activity method is defined above with behavior specific to your app.
    # It returns a TaskBase object with the result expected from the activity call.
    context.call_activity = Mock(side_effect=mock_activity)

    # Create a generator using the method and mocked context
    user_orchestrator = func_call(context)

    # Use orchestrator_generator_wrapper to get the values from the generator.
    # Processes the orchestrator in a way that is equivalent to the Durable replay logic
    values = [val for val in orchestrator_generator_wrapper(user_orchestrator)]

    expected_activity_calls = [call('say_hello', 'Tokyo'),
                               call('say_hello', 'Seattle'),
                               call('say_hello', 'London')]
    
    self.assertEqual(context.call_activity.call_count, 3)
    self.assertEqual(context.call_activity.call_args_list, expected_activity_calls)
    self.assertEqual(values[3], ["Hello Tokyo!", "Hello Seattle!", "Hello London!"])

Funções da entidade de teste de unidade

As funções de entidade gerem objetos com estado através de operações. Para testar uma função de entidade:

  • Simule o DurableEntityContext para simular o estado interno da entidade e as entradas de operação.
  • Substitua DurableEntityContext métodos como get_state, set_statee operation_name por simulações que retornam valores controlados.
  • Invoque a função de entidade diretamente com o contexto simulado.
  • Use afirmações para verificar alterações de estado e valores retornados, juntamente com utilitários unittest.mock.
import unittest
from unittest.mock import Mock, patch

from function_app import Counter

class TestEntityFunction(unittest.TestCase):
  @patch('azure.durable_functions.DurableEntityContext')
  def test_entity_add_operation(self, context_mock):
    # Get the original method definition as seen in function_app.py
    func_call = Counter.build().get_user_function().entity_function
    
    # Setup mock context behavior
    state = 0
    result = None

    def set_state(new_state):
        nonlocal state
        state = new_state

    def set_result(new_result):
        nonlocal result
        result = new_result

    context_mock.get_state = Mock(return_value=state)
    context_mock.set_state = Mock(side_effect=set_state)

    context_mock.operation_name = "add"
    context_mock.get_input = Mock(return_value=5)

    context_mock.set_result = Mock(side_effect=lambda x: set_result)

    # Call the entity function with the mocked context
    func_call(context_mock)

    # Verify the state was updated correctly
    context_mock.set_state.assert_called_once_with(5)
    self.assertEqual(state, 5)
    self.assertEqual(result, None)

Funções de atividade de teste unitário

As funções de atividade não requerem modificações específicas do Durable para serem testadas. A orientação encontrada na visão geral de teste de unidade do Python do Azure Functions é suficiente para testar essas funções.