Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Testes unitários de orquestrações duradouras ajudam-no a verificar a lógica de negócio e a detetar erros cedo. As orquestrações coordenam múltiplas atividades e podem tornar-se complexas rapidamente, por isso os testes protegem contra regressões à medida que o seu fluxo de trabalho evolui.
Seleciona o separador que corresponde ao teu projeto: Durable Functions se usares Funções do Azure, ou Durable Task SDKs se usares o SDK standalone sem Funções do Azure.
Com o Durable Functions, pode testar orquestradores, atividades e funções cliente (trigger) ao simular os objetos de contexto fornecidos pelo framework e chamar as suas funções diretamente. Esta abordagem isola a sua lógica de negócio do runtime do Funções do Azure.
Aqui está um teste minimalista do orquestrador em C# para mostrar o padrão:
[Fact]
public async Task MyOrchestrator_CallsActivity()
{
var contextMock = new Mock<TaskOrchestrationContext>();
contextMock.Setup(x => x.CallActivityAsync<string>(
It.IsAny<TaskName>(), It.IsAny<string>(), It.IsAny<TaskOptions>()))
.ReturnsAsync("result");
var result = await MyOrchestrator.Run(contextMock.Object);
Assert.Equal("result", result);
}
O resto deste artigo aborda este padrão em detalhe para C# e Python.
Os Durable Task SDKs autónomos fornecem infraestrutura de teste incorporada que executa orquestrações em memória sem dependências externas. Regista orquestradores e atividades com um trabalhador de teste, agenda orquestrações através de um cliente de teste e verificas os resultados. Não é preciso mockagem para C# e JavaScript. Python utiliza uma abordagem baseada em geradores com injeção manual de resultados.
Aqui está um teste mínimo de C# para mostrar o padrão:
[Fact]
public async Task MyOrchestrator_Completes()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<MyOrchestrator>();
tasks.AddActivity<MyActivity>();
});
string id = await host.Client.ScheduleNewOrchestrationInstanceAsync(nameof(MyOrchestrator));
var result = await host.Client.WaitForInstanceCompletionAsync(id, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, result.RuntimeStatus);
}
O resto deste artigo aborda este padrão em detalhe para C#, Python e JavaScript.
Pré-requisitos
- xUnit — estrutura de testes
- O pacote NuGet
Microsoft.DurableTask.InProcessTestHost(v1.0.0 ou posterior)
Funções do orquestrador de testes
As funções do orquestrador coordenam atividades, temporizadores e eventos externos. Normalmente contêm mais lógica de negócio e beneficiam mais dos testes unitários.
Simule o contexto de orquestração para controlar os valores de retorno das invocações de atividade. Depois liga diretamente ao teu orquestrador e verifica a saída.
Considere este orquestrador que chama uma atividade três vezes:
[Function(nameof(HelloCitiesOrchestration))]
public static async Task<List<string>> HelloCities(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var outputs = new List<string>
{
await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"),
await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"),
await context.CallActivityAsync<string>(nameof(SayHello), "London")
};
return outputs;
}
Use o Moq para simular TaskOrchestrationContext e definir os valores esperados de retorno para cada chamada de atividade.
Observação
O padrão It.Is<TaskName>(...) é obrigatório porque CallActivityAsync aceita uma estrutura TaskName, não uma string simples. Moq precisa da correspondência explícita de tipos.
[Fact]
public async Task HelloCities_ReturnsExpectedGreetings()
{
var contextMock = new Mock<TaskOrchestrationContext>();
// Mock each activity call to return a known value
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "Tokyo"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello Tokyo!");
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "Seattle"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello Seattle!");
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "London"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello London!");
var result = await HelloCitiesOrchestration.HelloCities(contextMock.Object);
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
}
Uso DurableTaskTestHost para executar orquestrações em memória. Regista o teu orquestrador de produção e as classes de atividades, agenda uma orquestração e valida o resultado.
Dadas estas classes de produção:
class HelloCitiesOrchestrator : TaskOrchestrator<string, List<string>>
{
public override async Task<List<string>> RunAsync(
TaskOrchestrationContext context, string input)
{
var outputs = new List<string>
{
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Tokyo"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Seattle"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "London")
};
return outputs;
}
}
class SayHelloActivity : TaskActivity<string, string>
{
public override Task<string> RunAsync(TaskActivityContext context, string name)
{
return Task.FromResult($"Hello {name}!");
}
}
Registe-os diretamente no hospedeiro de teste:
[Fact]
public async Task HelloCities_ReturnsExpectedGreetings()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<HelloCitiesOrchestrator>();
tasks.AddActivity<SayHelloActivity>();
});
string instanceId = await host.Client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestrator));
OrchestrationMetadata result = await host.Client.WaitForInstanceCompletionAsync(
instanceId, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, result.RuntimeStatus);
var output = result.ReadOutputAs<List<string>>();
Assert.Equal(3, output.Count);
Assert.Equal("Hello Tokyo!", output[0]);
Assert.Equal("Hello Seattle!", output[1]);
Assert.Equal("Hello London!", output[2]);
}
DurableTaskTestHost Executa um motor completo de orquestração em memória. Não são necessários serviços externos nem processos sidecar.
Funções de atividade de teste
As funções de atividade contêm o trabalho real — chamar APIs, processar dados ou interagir com sistemas externos. São o tipo de função mais simples de testar porque não têm um comportamento de repetição específico do framework.
As funções de atividade em Funções do Azure recebem uma entrada e, opcionalmente, uma FunctionContext. Teste-os como qualquer outra função:
[Function(nameof(SayHello))]
public static string SayHello(
[ActivityTrigger] string name, FunctionContext executionContext)
{
return $"Hello {name}!";
}
[Fact]
public void SayHello_ReturnsExpectedGreeting()
{
var result = HelloCitiesOrchestration.SayHello("Tokyo", Mock.Of<FunctionContext>());
Assert.Equal("Hello Tokyo!", result);
}
As funções de atividade recebem um objeto de contexto e uma entrada. O contexto fornece metadados como o ID de orquestração e o ID da tarefa, mas a maioria dos testes não precisa deles.
Usando a SayHelloActivity classe do exemplo do orquestrador, chame RunAsync diretamente com um contexto simulado:
[Fact]
public async Task SayHello_ReturnsExpectedGreeting()
{
var activity = new SayHelloActivity();
var contextMock = new Mock<TaskActivityContext>();
var result = await activity.RunAsync(contextMock.Object, "Tokyo");
Assert.Equal("Hello Tokyo!", result);
}
Quando usas DurableTaskTestHost, as atividades também são executadas como parte do teste de orquestração. Não precisas de testes de atividade separados a menos que a atividade tenha lógica complexa.
Funções do cliente de teste
As funções de cliente (também chamadas funções de gatilho) iniciam orquestrações e gerenciam instâncias. Usam a vinculação durável do cliente para interagir com o motor de orquestração.
Considere este gatilho HTTP que inicia uma orquestração:
[Function("HelloCitiesOrchestration_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestration));
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
Mock DurableTaskClient para retornar um ID de instância conhecido.
[Fact]
public async Task HttpStart_ReturnsAccepted()
{
var durableClientMock = new Mock<DurableTaskClient>("testClient");
var functionContextMock = new Mock<FunctionContext>();
var instanceId = "test-instance-id";
durableClientMock
.Setup(x => x.ScheduleNewOrchestrationInstanceAsync(
It.IsAny<TaskName>(),
It.IsAny<object>(),
It.IsAny<StartOrchestrationOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(instanceId);
var mockRequest = CreateMockHttpRequest(functionContextMock.Object);
var responseMock = new Mock<HttpResponseData>(functionContextMock.Object);
responseMock.SetupGet(r => r.StatusCode).Returns(HttpStatusCode.Accepted);
durableClientMock
.Setup(x => x.CreateCheckStatusResponseAsync(
It.IsAny<HttpRequestData>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(responseMock.Object);
var result = await HelloCitiesOrchestration.HttpStart(
mockRequest, durableClientMock.Object, functionContextMock.Object);
Assert.Equal(HttpStatusCode.Accepted, result.StatusCode);
}
Operações do cliente de teste
Com os SDKs de Tarefas Duráveis independentes, as operações do cliente (agendamento de orquestrações, consulta de estado, geração de eventos) utilizam o mesmo TestOrchestrationClient que já foi mostrado nos testes do orquestrador. Não existe uma função cliente separada — chama diretamente a API do cliente.
DurableTaskTestHost expõe host.Client, que é um DurableTaskClient totalmente funcional. Use-o para testar operações ao nível do cliente, como agendamento, consulta ou terminação de orquestrações:
[Fact]
public async Task Client_CanQueryOrchestrationStatus()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<HelloCitiesOrchestrator>();
tasks.AddActivity<SayHelloActivity>();
});
string instanceId = await host.Client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestrator));
// Query status while the orchestration runs
OrchestrationMetadata metadata = await host.Client.WaitForInstanceCompletionAsync(
instanceId, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus);
Assert.Equal(instanceId, metadata.InstanceId);
}