Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Testowanie jednostkowe jest ważną częścią nowoczesnych praktyk programistycznych. Testy jednostkowe weryfikują zachowanie logiki biznesowej i chronią przed wprowadzeniem niezauważonych zmian zakłócających działanie w przyszłości. Durable Functions mogą łatwo zwiększyć złożoność, dlatego wprowadzenie testów jednostkowych pomaga uniknąć zmian powodujących niezgodność. W poniższych sekcjach opisano sposób testowania jednostkowego trzech typów funkcji — klienta orkiestracji, orkiestratora i funkcji działania.
Uwaga / Notatka
Ten artykuł zawiera wskazówki dotyczące testowania jednostkowego aplikacji Durable Functions napisanych w języku C# dla wewnętrznego pracownika procesów platformy .NET, z przeznaczeniem dla rozszerzenia Durable Functions 2.x. Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł Wersje Durable Functions.
Wymagania wstępne
Przykłady w tym artykule wymagają znajomości następujących pojęć i struktur:
Klasy bazowe do mockowania
Mockowanie jest obsługiwane za pośrednictwem następującego interfejsu.
Te interfejsy mogą być używane z różnymi wyzwalaczami i powiązaniami obsługiwanymi przez rozszerzenie Durable Functions. Podczas wykonywania funkcji w Azure Functions, środowisko uruchomieniowe funkcji wykonuje kod funkcji z określoną implementacją tych interfejsów. W przypadku testów jednostkowych można przekazać pozorowaną wersję tych interfejsów, aby przetestować logikę biznesową.
Funkcje wyzwalacza testów jednostkowych
W tej sekcji test jednostkowy weryfikuje logikę następującej funkcji wyzwalacza HTTP na potrzeby uruchamiania nowych aranżacji.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
namespace VSSample
{
public static class HttpStart
{
[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
[DurableClient] IDurableClient starter,
string functionName,
ILogger log)
{
// Function input comes from the request content.
object eventData = await req.Content.ReadAsAsync<object>();
string instanceId = await starter.StartNewAsync(functionName, eventData);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
}
}
Zadanie testu jednostkowego weryfikuje wartość nagłówka Retry-After podanego w treści odpowiedzi. Test jednostkowy symuluje niektóre z metod IDurableClient, aby zapewnić przewidywalne zachowanie.
Najpierw użyjemy biblioteki do mockowania (moq w tym przypadku) do mockowania IDurableClient:
// Mock IDurableClient
var durableClientMock = new Mock<IDurableClient>();
Uwaga / Notatka
Chociaż interfejsy można testować, bezpośrednio implementując je jako klasy, frameworki do pozorowania upraszczają ten proces na wiele sposobów. Jeśli na przykład nowa metoda zostanie dodana do interfejsu w wersjach pomocniczych, moq nie wymaga żadnych zmian kodu w przeciwieństwie do konkretnych implementacji.
Następnie metoda jest imitowana StartNewAsync , aby zwrócić dobrze znany identyfikator wystąpienia.
// Mock StartNewAsync method
durableClientMock.
Setup(x => x.StartNewAsync(functionName, It.IsAny<object>())).
ReturnsAsync(instanceId);
Następnie CreateCheckStatusResponse jest symulowany, aby zawsze zwracać pustą odpowiedź HTTP 200.
// Mock CreateCheckStatusResponse method
durableClientMock
// Notice that even though the HttpStart function does not call IDurableClient.CreateCheckStatusResponse()
// with the optional parameter returnInternalServerErrorOnFailure, moq requires the method to be set up
// with each of the optional parameters provided. Simply use It.IsAny<> for each optional parameter
.Setup(x => x.CreateCheckStatusResponse(It.IsAny<HttpRequestMessage>(), instanceId, returnInternalServerErrorOnFailure: It.IsAny<bool>()))
.Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(string.Empty),
Headers =
{
RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(10))
}
});
ILogger jest również wyśmiewany:
// Mock ILogger
var loggerMock = new Mock<ILogger>();
Teraz Run metoda jest wywoływana z testu jednostkowego.
// Call Orchestration trigger function
var result = await HttpStart.Run(
new HttpRequestMessage()
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
RequestUri = new Uri("http://localhost:7071/orchestrators/E1_HelloSequence"),
},
durableClientMock.Object,
functionName,
loggerMock.Object);
Ostatnim krokiem jest porównanie danych wyjściowych z oczekiwaną wartością:
// Validate that output is not null
Assert.NotNull(result.Headers.RetryAfter);
// Validate output's Retry-After header value
Assert.Equal(TimeSpan.FromSeconds(10), result.Headers.RetryAfter.Delta);
Po połączeniu wszystkich tych kroków test jednostkowy ma następujący kod:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
namespace VSSample.Tests
{
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
public class HttpStartTests
{
[Fact]
public async Task HttpStart_returns_retryafter_header()
{
// Define constants
const string functionName = "SampleFunction";
const string instanceId = "7E467BDB-213F-407A-B86A-1954053D3C24";
// Mock TraceWriter
var loggerMock = new Mock<ILogger>();
// Mock DurableOrchestrationClientBase
var clientMock = new Mock<IDurableClient>();
// Mock StartNewAsync method
clientMock.
Setup(x => x.StartNewAsync(functionName, It.IsAny<string>(), It.IsAny<object>())).
ReturnsAsync(instanceId);
// Mock CreateCheckStatusResponse method
clientMock
.Setup(x => x.CreateCheckStatusResponse(It.IsAny<HttpRequestMessage>(), instanceId, false))
.Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(string.Empty),
Headers =
{
RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(10))
}
});
// Call Orchestration trigger function
var result = await HttpStart.Run(
new HttpRequestMessage()
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
RequestUri = new Uri("http://localhost:7071/orchestrators/E1_HelloSequence"),
},
clientMock.Object,
functionName,
loggerMock.Object);
// Validate that output is not null
Assert.NotNull(result.Headers.RetryAfter);
// Validate output's Retry-After header value
Assert.Equal(TimeSpan.FromSeconds(10), result.Headers.RetryAfter.Delta);
}
}
}
Funkcje orkiestratora testów jednostkowych
Funkcje programu Orchestrator są jeszcze bardziej interesujące w przypadku testów jednostkowych, ponieważ zwykle mają o wiele więcej logiki biznesowej.
W tej sekcji testy jednostkowe weryfikują dane wyjściowe E1_HelloSequence funkcji Orchestrator:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
namespace VSSample
{
public static class HelloSequence
{
[FunctionName("E1_HelloSequence")]
public static async Task<List<string>> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var outputs = new List<string>();
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Seattle"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello_DirectInput", "London"));
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
[FunctionName("E1_SayHello")]
public static string SayHello([ActivityTrigger] IDurableActivityContext context)
{
string name = context.GetInput<string>();
return $"Hello {name}!";
}
[FunctionName("E1_SayHello_DirectInput")]
public static string SayHelloDirectInput([ActivityTrigger] string name)
{
return $"Hello {name}!";
}
}
}
Kod testu jednostkowego rozpoczyna się od utworzenia pozornego kodu:
var durableOrchestrationContextMock = new Mock<IDurableOrchestrationContext>();
Następnie wywołania metody działania są wyśmiewane:
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Tokyo")).ReturnsAsync("Hello Tokyo!");
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Seattle")).ReturnsAsync("Hello Seattle!");
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "London")).ReturnsAsync("Hello London!");
Następnie test jednostkowy wywołuje metodę HelloSequence.Run :
var result = await HelloSequence.Run(durableOrchestrationContextMock.Object);
Na koniec dane wyjściowe są weryfikowane:
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
Po połączeniu poprzednich kroków test jednostkowy ma następujący kod:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
namespace VSSample.Tests
{
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Moq;
using Xunit;
public class HelloSequenceTests
{
[Fact]
public async Task Run_returns_multiple_greetings()
{
var mockContext = new Mock<IDurableOrchestrationContext>();
mockContext.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Tokyo")).ReturnsAsync("Hello Tokyo!");
mockContext.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Seattle")).ReturnsAsync("Hello Seattle!");
mockContext.Setup(x => x.CallActivityAsync<string>("E1_SayHello_DirectInput", "London")).ReturnsAsync("Hello London!");
var result = await HelloSequence.Run(mockContext.Object);
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
}
}
}
Funkcje działania testowania jednostkowego
Funkcje aktywności są testowane jednostkowo w taki sam sposób, jak funkcje nietrwałe.
W tej sekcji test jednostkowy weryfikuje zachowanie E1_SayHello funkcji Activity:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
namespace VSSample
{
public static class HelloSequence
{
[FunctionName("E1_HelloSequence")]
public static async Task<List<string>> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var outputs = new List<string>();
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Seattle"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello_DirectInput", "London"));
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
[FunctionName("E1_SayHello")]
public static string SayHello([ActivityTrigger] IDurableActivityContext context)
{
string name = context.GetInput<string>();
return $"Hello {name}!";
}
[FunctionName("E1_SayHello_DirectInput")]
public static string SayHelloDirectInput([ActivityTrigger] string name)
{
return $"Hello {name}!";
}
}
}
Testy jednostkowe sprawdzają format danych wyjściowych. Te testy jednostkowe albo używają typów parametrów bezpośrednio, albo imitują klasę IDurableActivityContext.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
namespace VSSample.Tests
{
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Xunit;
using Moq;
public class HelloSequenceActivityTests
{
[Fact]
public void SayHello_returns_greeting()
{
var durableActivityContextMock = new Mock<IDurableActivityContext>();
durableActivityContextMock.Setup(x => x.GetInput<string>()).Returns("John");
var result = HelloSequence.SayHello(durableActivityContextMock.Object);
Assert.Equal("Hello John!", result);
}
[Fact]
public void SayHello_returns_greeting_direct_input()
{
var result = HelloSequence.SayHelloDirectInput("John");
Assert.Equal("Hello John!", result);
}
}
}