지속성 오케스트레이션을 단위 테스트하면 비즈니스 논리를 확인하고 오류를 조기에 발견할 수 있습니다. 오케스트레이션은 여러 활동을 조정하고 빠르게 복잡해질 수 있으므로 워크플로가 발전함에 따라 테스트가 회귀로부터 보호됩니다.
프로젝트와 일치하는 탭을 선택합니다. Azure Functions 사용하는 경우 Durable Functions 또는 Azure Functions 없이 독립 실행형 SDK를 사용하는 경우 Durable Task SDK.
Durable Functions를 사용하면 프레임워크가 제공하는 컨텍스트 객체를 모킹하여 오케스트레이터, 활동, 클라이언트(트리거) 함수를 직접 호출하여 테스트할 수 있습니다. 이 방법은 Azure Functions 런타임에서 비즈니스 논리를 격리합니다.
다음은 패턴을 표시하는 최소 C# 오케스트레이터 테스트입니다.
[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);
}
이 문서의 나머지 부분에는 C# 및 Python 대한 이 패턴이 자세히 설명되어 있습니다.
독립 실행형 지속성 작업 SDK는 외부 종속성 없이 메모리 내 오케스트레이션을 실행하는 기본 제공 테스트 인프라 를 제공합니다. 오케스트레이터와 활동을 테스트 워커에 등록하고, 테스트 클라이언트를 통해 오케스트레이션을 예약하며, 결과를 검증합니다. C# 및 JavaScript에서는 모킹이 필요하지 않습니다. Python 수동 결과 주입과 함께 생성기 기반 접근 방식을 사용합니다.
다음은 패턴을 표시하는 최소 C# 테스트입니다.
[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);
}
이 문서의 나머지 부분에는 C#, Python 및 JavaScript에 대한 이 패턴이 자세히 설명되어 있습니다.
사전 요구 사항
- xUnit — 테스트 프레임워크
-
Microsoft.DurableTask.InProcessTestHostNuGet 패키지(v1.0.0 이상)
오케스트레이터 함수 테스트
오케스트레이터 함수는 활동, 타이머 및 외부 이벤트를 조정합니다. 일반적으로 가장 많은 비즈니스 논리를 포함하며 단위 테스트를 통해 가장 많은 이점을 얻을 수 있습니다.
오케스트레이션 컨텍스트를 모의하여 활동 호출의 반환 값을 제어합니다. 그런 다음, 오케스트레이터를 직접 호출하고 출력을 확인합니다.
작업을 세 번 호출하는 이 오케스트레이터를 고려합니다.
[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를 모의하고 각 활동 호출에 대한 예상 반환 값을 설정합니다.
Note
It.Is<TaskName>(...) 일반 문자열이 아닌 구조체를 CallActivityAsync 허용하기 때문에 TaskName 패턴이 필요합니다. Moq에는 명시적 형식 일치가 필요합니다.
[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]);
}
메모리 내 오케스트레이션을 실행하는 데 사용합니다 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);
}
활동 함수는 컨텍스트 개체와 입력을 받습니다. 컨텍스트는 오케스트레이션 ID 및 작업 ID와 같은 메타데이터를 제공하지만 대부분의 테스트는 필요하지 않습니다.
오케스트레이터 예제의 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);
}
알려진 인스턴스 ID를 반환하는 모의 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);
}
클라이언트 작업 테스트
독립 실행형 지속성 작업 SDK를 사용하면 클라이언트 작업(오케스트레이션 예약, 상태 쿼리, 이벤트 발생)이 오케스트레이터 테스트에 이미 표시된 것과 동일하게 TestOrchestrationClient 사용됩니다. 별도의 클라이언트 함수가 없습니다. 클라이언트 API를 직접 호출합니다.
DurableTaskTestHost는 host.Client를 노출하며, 이는 완전히 작동하는 DurableTaskClient입니다. 이를 사용하여 오케스트레이션 예약, 쿼리 또는 종료와 같은 클라이언트 수준 작업을 테스트합니다.
[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);
}
관련 콘텐츠
- Durable Functions 개요
- 오케스트레이터 함수 코드 제약 조건
- &l;t;c0>인프로세스에서 격리된 워커(.NET)로 마이그레이션</c0>
- Durable Functions에서의 직렬화 및 지속성