Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Gli unit test sono una parte importante delle procedure di sviluppo software moderne. I test unitari verificano il comportamento della logica di business e proteggono dall'introduzione di cambiamenti importanti che potrebbero passare inosservati in futuro. La complessità di Durable Functions può facilmente aumentare. Pertanto, l'introduzione di unit test consente di evitare modifiche che causano un'interruzione. Le sezioni seguenti illustrano come eseguire unit test dei tre tipi di funzione: client di orchestrazione, agenti di orchestrazione e funzioni di attività.
Annotazioni
Questo articolo fornisce indicazioni sugli unit test per le app di Durable Functions scritte in C# per il nodo di lavoro in-process .NET e destinate a Durable Functions 2.x. Per altre informazioni sulle differenze tra le versioni, vedere l'articolo Versioni di Durable Functions .
Prerequisiti
Gli esempi in questo articolo richiedono la conoscenza dei concetti e dei framework seguenti:
Classi base per la simulazione
Il mocking è supportato tramite l'interfaccia seguente:
Queste interfacce possono essere usate con i vari trigger e associazioni supportati da Durable Functions. Durante l'esecuzione delle tue Funzioni di Azure, il runtime delle funzioni esegue il codice della tua funzione utilizzando un'implementazione concreta di queste interfacce. Per gli unit test, è possibile passare una versione fittizia di queste interfacce per testare la logica di business.
Funzioni di trigger degli unit test
In questa sezione, lo unit test convalida la logica della funzione trigger HTTP seguente per avviare nuove orchestrazioni.
// 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);
}
}
}
L'attività di unit test verifica il valore dell'intestazione Retry-After
specificato nel payload della risposta. Lo unit test simula quindi alcuni metodi IDurableClient
per garantire un comportamento prevedibile.
Prima di tutto, si usa un framework fittizio (moq in questo caso) per simulare IDurableClient
:
// Mock IDurableClient
var durableClientMock = new Mock<IDurableClient>();
Annotazioni
Sebbene sia possibile simulare le interfacce implementando direttamente l'interfaccia come classe, i framework fittizi semplificano il processo in vari modi. Ad esempio, se un nuovo metodo viene aggiunto all'interfaccia nelle versioni secondarie, moq non richiede modifiche al codice a differenza delle implementazioni concrete.
Viene quindi simulato il metodo StartNewAsync
per restituire un ID istanza noto.
// Mock StartNewAsync method
durableClientMock.
Setup(x => x.StartNewAsync(functionName, It.IsAny<object>())).
ReturnsAsync(instanceId);
Il prossimo CreateCheckStatusResponse
è configurato in modo da restituire sempre una risposta HTTP 200 vuota.
// 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))
}
});
Viene simulato anche ILogger
:
// Mock ILogger
var loggerMock = new Mock<ILogger>();
Run
Il metodo viene ora chiamato dallo unit test:
// 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);
L'ultimo passaggio consiste nel confrontare l'output con il valore previsto:
// 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);
Dopo aver combinato tutti questi passaggi, lo unit test ha il codice seguente:
// 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);
}
}
}
Funzioni dell'agente di orchestrazione per gli unit test
Le funzioni dell'agente di orchestrazione sono ancora più interessanti per gli unit test, perché in genere hanno una logica di business molto maggiore.
In questa sezione gli unit test convalidano l'output della funzione dell'agente di orchestrazione E1_HelloSequence
:
// 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}!";
}
}
}
Il codice di unit test inizia con la creazione di una simulazione:
var durableOrchestrationContextMock = new Mock<IDurableOrchestrationContext>();
Verranno quindi simulate le chiamate al metodo dell'attività:
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!");
Successivamente, lo unit test chiama il HelloSequence.Run
metodo :
var result = await HelloSequence.Run(durableOrchestrationContextMock.Object);
Infine, l'output viene convalidato:
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
Dopo aver combinato i passaggi precedenti, lo unit test ha il codice seguente:
// 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]);
}
}
}
Funzioni dell'attività di unit test
Le funzioni di attività vengono sottoposte a unit test nello stesso modo delle funzioni non permanenti.
In questa sezione lo unit test convalida il comportamento della E1_SayHello
funzione 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}!";
}
}
}
E gli unit test verificano il formato dell'output. Questi unit test usano direttamente i tipi di parametro o la classe fittizia 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);
}
}
}