Tartós függvények és tartós feladat SDK-k egységtesztje

A tartós vezénylések egységtesztelése segít az üzleti logika ellenőrzésében és a hibák korai észlelésében. A vezénylések több tevékenységet koordinálnak, és gyorsan összetettek lehetnek, így a tesztek védelmet nyújtanak a regressziók ellen a munkafolyamat fejlődésével.

Válassza ki a projektnek megfelelő lapot: Durable Functions ha Azure Functions használ, vagy Durable Task SDK-k ha az önálló SDK-t Azure Functions nélkül használja.

Durable Functions segítségével a vezénylőket, tevékenységeket és az ügyfél (trigger) függvényeket a keretrendszer által biztosított környezeti objektumok mokkolásával tesztelheti, és közvetlenül meghívhatja a függvényeket. Ez a módszer elkülöníti az üzleti logikát a Azure Functions futtatókörnyezettől.

Íme egy minimális C#-vezénylési teszt a minta megjelenítéséhez:

[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);
}

A cikk további része részletesen ismerteti a C# és a Python mintáját.

A különálló Durable Task SDK-k beépített tesztinfrastruktúrát biztosítanak, amely külső függőségek nélkül futtat vezényléseket a memóriában. Orchesztrálókat és tevékenységeket regisztrálhat egy tesztmunkással, és ütemezheti az orchesztrálásokat tesztügyfél segítségével, valamint ellenőrizheti az eredményeket. A C# és a JavaScript esetében nem szükséges mockolás. Python generátoralapú megközelítést használ manuális eredményinjektálással.

Íme egy minimális C#-teszt a minta megjelenítéséhez:

[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);
}

A cikk további része részletesen ismerteti ezt a mintát a C#, a Python és a JavaScript esetében.

Prerequisites

Orchestrátorfüggvények tesztelése

Az Orchestrator függvények koordinálják a tevékenységeket, az időzítőket és a külső eseményeket. Ezek általában a legtöbb üzleti logikát tartalmazzák, és a legtöbbet ki tudják használni az egységtesztelésből.

Szimulálja az orkesztációs környezetet a tevékenységhívások visszatérési értékeinek vezérlésére. Ezután hívja meg közvetlenül a vezénylőt, és ellenőrizze a kimenetet.

Fontolja meg ezt az orchestrator-t, amely háromszor hív meg egy tevékenységet:

[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;
}

A Moq használatával szimulálhatja TaskOrchestrationContext és beállíthatja az egyes tevékenységhívások várható visszatérési értékeit.

Note

A It.Is<TaskName>(...) minta azért szükséges, mert CallActivityAsync nem egyszerű sztringet, hanem szerkezetet fogad el TaskName . A Moq-nak szüksége van explicit típusegyezésre.

[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]);
}

Az DurableTaskTestHost használható az orchestrációk memóriabeli futtatására. Regisztrálja a produkciós vezénylőt és a tevékenységi osztályokat, ütemezze a vezénylést, és ellenőrizze az eredményt.

Ezeket a termelési osztályokat figyelembe véve:

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}!");
    }
}

Regisztrálja őket közvetlenül a teszthelygépen:

[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 egy teljes mértékben memóriában futó vezénylési motort futtat. Nincs szükség külső szolgáltatásokra vagy kiegészítő folyamatokra.

Tevékenységfüggvények tesztelése

A tevékenységfüggvények tartalmazzák a tényleges munkát – API-k meghívását, adatok feldolgozását vagy külső rendszerekkel való interakciót. Ezek a legegyszerűbben tesztelendő függvénytípusok, mivel nem rendelkeznek keretrendszerspecifikus visszajátszási viselkedésekkel.

A Azure Functions tevékenységfüggvényei bemenetet kapnak, és opcionálisan egy FunctionContext. Tesztelje őket, mint bármely más függvényt:

[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);
}

A tevékenységfüggvények környezeti objektumot és bemenetet kapnak. A környezet olyan metaadatokat biztosít, mint a vezénylési azonosító és a feladatazonosító, de a legtöbb tesztnek nincs rá szüksége.

SayHelloActivity A vezénylői példa osztályának használatával közvetlenül hívjon RunAsync meg egy példakörnyezetet:

[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);
}

Ha használja DurableTaskTestHost, a tevékenységek a vezénylési teszt részeként is futnak. Nincs szükség külön tevékenységtesztre, kivéve, ha a tevékenység összetett logikával rendelkezik.

Az ügyfél programfunkcióinak tesztelése

Az ügyfélfüggvények (más néven triggerfüggvények) elindítják a vezényléseket, és kezelik a példányokat. A tartós ügyfélkapcsolatot használják, hogy kapcsolatba lépjenek a vezénylési motorral.

Vegye tekintetbe ezt az HTTP-eseményindítót, amely folyamatvezérlést indít el.

[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 a jól ismert példányazonosító visszaadásához:

[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);
}

Ügyfélműveletek tesztelése

Önálló Durable Task SDK-k esetén az ügyfélműveletek (vezénylések ütemezése, állapot lekérdezése, események kiváltása) ugyanazt használják, mint TestOrchestrationClient a vezénylő tesztekben. Nincs külön ügyfélfüggvény – közvetlenül meghívja az ügyfél API-t.

DurableTaskTestHost host.Clientteszi elérhetővé , ami egy teljesen működőképes DurableTaskClient. Használatával tesztelheti az ügyfélszintű műveleteket, például az ütemezést, a lekérdezést vagy a vezénylések leállítását:

[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);
}