Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Модульные тесты проверяют бизнес-логику и защищают от регрессий. Устойчивые оркестрации координируют различные процессы и могут быстро наращивать сложность. Добавление модульных тестов помогает ловить ошибки раньше.
С помощью Durable Functions вы тестируете функции оркестратора, действий и клиента (триггера), имитируя объекты контекста, предоставляемые платформой и вызывая ваши функции напрямую. Этот подход изолирует бизнес-логику от среды выполнения Azure Functions.
Автономные пакеты SDK для устойчивых задач предоставляют встроенную тестовую инфраструктуру , которая выполняет оркестрации в памяти без внешних зависимостей. Вы регистрируете оркестраторы и действия у тестового работника, планируете оркестрации с помощью тестового клиента и проверяете результаты. Для C# и JavaScript не требуется мокирование. Python использует подход на основе исполнителя с событиями имитации истории.
Необходимые условия
- xUnit — тестовая платформа
- Moq — макетная платформа
- Знакомство с .NET изолированной рабочей моделью
- xUnit — тестовая платформа
-
Microsoft.DurableTask.InProcessTestHostПакет NuGet
Тестирование функций оркестратора
Функции оркестратора координируют действия, таймеры и внешние события. Обычно они содержат большую бизнес-логику и получают максимальную выгоду от модульного тестирования.
Макет контекста оркестрации для управления возвращаемыми значениями вызовов действий. Затем вызовите оркестратор напрямую и проверьте выходные данные.
Рассмотрим этот оркестратор, который вызывает действие три раза:
[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;
}
Используйте Moq для создания имитации TaskOrchestrationContext и настройки ожидаемых возвращаемых значений для каждого вызова функции.
[Fact]
public async Task HelloCities_ReturnsExpectedGreetings()
{
var contextMock = new Mock<TaskOrchestrationContext>();
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]);
}
Используйте DurableTaskTestHost для выполнения оркестраций в памяти. Зарегистрируйте производственный оркестратор и классы активности, запланируйте оркестрацию и проверьте результат.
Учитывая следующие производственные классы:
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}!");
}
}
Зарегистрируйте их непосредственно на тестовом узле:
[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 запускает полный модуль оркестрации в памяти. Не требуются внешние службы или дополнительные процессы.
Функции тестового действия
Функции действий содержат фактическую работу— вызов API, обработку данных или взаимодействие с внешними системами. Это самый простой тип функции для тестирования, так как они не имеют поведения воспроизведения, специфичного для конкретного фреймворка.
Функции действий в Azure Functions получают входные данные и при необходимости FunctionContext. Проверьте их как любую другую функцию:
[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);
}
Функции действия получают объект контекста и входные данные. Контекст предоставляет метаданные, такие как идентификатор оркестрации и идентификатор задачи, но большинству тестов это не нужно.
SayHelloActivity Используя класс из примера оркестратора, вызовите RunAsync непосредственно с помощью контекста макета:
[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);
}
При использовании DurableTaskTestHost действия также выполняются в рамках теста оркестрации. Вам не нужны отдельные тесты действий, если действие не имеет сложной логики.
Тестирование клиентских функций
Клиентские функции (также называемые функциями триггера) запускают процессы оркестрации и управляют их экземплярами. Они используют устойчивую привязку клиента для взаимодействия с подсистемой оркестрации.
Рассмотрим этот триггер HTTP, который запускает оркестрацию:
[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);
}
Макет DurableTaskClient для возврата известного идентификатора экземпляра:
[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);
}