Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Testy jednostkowe trwałych orkiestracji pomagają zweryfikować logikę biznesową i wcześnie wykrywać błędy. Orkiestracje koordynują wiele działań i mogą się szybko skomplikować, więc testy chronią przed regresjami w miarę rozwoju workflow.
Wybierz kartę zgodną z projektem: Durable Functions jeśli używasz Azure Functions lub Durable Task SDK jeśli używasz autonomicznego zestawu SDK bez Azure Functions.
Za pomocą Durable Functions można testować funkcje orkiestratorów, działań i klienta (wyzwalacza) przez mocowanie obiektów kontekstowych dostarczanych przez platformę i wywoływanie funkcji bezpośrednio. Takie podejście izoluje logikę biznesową od środowiska uruchomieniowego Azure Functions.
Oto minimalny test orkiestratora języka C#, aby pokazać wzorzec:
[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);
}
W pozostałej części tego artykułu szczegółowo omówiono ten wzorzec dla języka C# i Python.
Samodzielne zestawy SDK rozszerzenia Durable Task zapewniają wbudowaną infrastrukturę testową, która uruchamia orkiestracje w pamięci operacyjnej bez zależności zewnętrznych. Rejestrujesz orkiestratorów i działania przy użyciu procesu roboczego testowego, planujesz aranżacje za pośrednictwem klienta testowego i potwierdzasz wyniki. Nie jest wymagane wyśmiewanie w językach C# i JavaScript. Python używa podejścia opartego na generatorze z ręcznym wstrzykiwaniem wyników.
Oto minimalny test języka C#, aby pokazać wzorzec:
[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);
}
W pozostałej części tego artykułu szczegółowo opisano ten wzorzec dla języków C#, Python i JavaScript.
Wymagania wstępne
- xUnit — struktura testowa
- Moq — framework do tworzenia obiektów zastępczych
- Znajomość modelu .NET izolowanego procesu roboczego
- xUnit — struktura testowa
- Pakiet NuGet
Microsoft.DurableTask.InProcessTestHost(wersja 1.0.0 lub nowsza)
Funkcje orkiestratora testów
Funkcje programu Orchestrator koordynują działania, czasomierze i zdarzenia zewnętrzne. Zwykle zawierają one najbardziej logikę biznesową i korzystają najbardziej z testów jednostkowych.
Zasymuluj kontekst orkiestracji, aby kontrolować zwracane wartości wywołań aktywności. Następnie bezpośrednio wywołaj orchestratora i zweryfikuj dane wyjściowe.
Rozważ ten orkiestrator, który wywołuje aktywność trzy razy:
[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;
}
Użyj Moq do mockowania TaskOrchestrationContext i skonfiguruj oczekiwane wartości zwracane dla każdego wywołania aktywności.
Note
Wzorzec It.Is<TaskName>(...) jest wymagany, ponieważ CallActivityAsync akceptuje TaskName strukturę, a nie zwykły ciąg. Moq wymaga jawnej zgodności typu.
[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]);
}
Służy DurableTaskTestHost do uruchamiania aranżacji w pamięci. Zarejestruj orkiestratora produkcyjnego i klasy działań, zaplanuj orkiestrację i zweryfikuj wynik.
Biorąc pod uwagę te klasy produkcyjne:
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}!");
}
}
Zarejestruj je bezpośrednio na hoście testowym:
[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 uruchamia kompletny silnik orkiestracji w pamięci. Nie są wymagane żadne usługi zewnętrzne ani procesy pomocnicze.
Funkcje działania testowego
Funkcje działania zawierają rzeczywistą pracę — wywoływanie interfejsów API, przetwarzanie danych lub interakcja z systemami zewnętrznymi. Są najprostszym typem funkcji do przetestowania, ponieważ nie mają zachowania odtwarzania specyficznego dla struktury.
Funkcje działania w Azure Functions odbierają dane wejściowe i opcjonalnie FunctionContext. Przetestuj je tak jak każda inna funkcja:
[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);
}
Funkcje działania odbierają obiekt kontekstu i dane wejściowe. Kontekst zawiera metadane, takie jak identyfikator orkiestracji i identyfikator zadania, ale większość testów tego nie wymaga.
Korzystając z klasy SayHelloActivity z przykładu orkiestratora, wywołaj RunAsync bezpośrednio z pozorowanym kontekstem.
[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);
}
W przypadku korzystania z DurableTaskTestHost, działania są również uruchamiane w ramach testu orkiestracji. Nie potrzebujesz oddzielnych testów aktywności, chyba że działanie ma złożoną logikę.
Testowanie funkcji klienta
Funkcje klienta (nazywane również funkcjami wyzwalającymi) uruchamiają orkiestracje i zarządzają instancjami. Używają trwałego powiązania klienta w celu interakcji z silnikiem orkiestracji.
Rozważmy ten wyzwalacz HTTP, który uruchamia aranżację:
[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);
}
Pozorowanie DurableTaskClient zwracania znanego identyfikatora wystąpienia:
[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);
}
Testowanie operacji klienta
W przypadku autonomicznych zestawów SDK trwałych zadań operacje klienta (planowanie orkiestracji, zapytania o stan, podnoszenie zdarzeń) używają tego samego TestOrchestrationClient, co pokazano już w testach orkiestratora. Nie istnieje żadna oddzielna funkcja klienta — bezpośrednio wywołujesz interfejs API klienta.
DurableTaskTestHost ujawnia host.Client, który jest w pełni funkcjonalnym DurableTaskClient. Służy do testowania operacji na poziomie klienta, takich jak planowanie, wykonywanie zapytań lub kończenie aranżacji:
[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);
}