Testing unità con Orleans
Questa esercitazione illustra come eseguire unit test dei grani per verificare che si comportino correttamente. Esistono due modi principali per eseguire unit test dei grani e il modo scelto dipende dal tipo di funzionalità da testare. È possibile usare il pacchetto NuGet Microsoft.Orleans.TestingHost per creare silos di test per i grani oppure usare un framework di simulazione come Moq per simulare parti del runtime di Orleans con cui interagisce il grano.
Usare il TestCluster
Il pacchetto NuGet Microsoft.Orleans.TestingHost
contiene la classe TestCluster che può essere usata per creare un cluster in memoria, costituito per impostazione predefinita da due silos con cui è possibile testare i grani.
using Orleans.TestingHost;
namespace Tests;
public class HelloGrainTests
{
[Fact]
public async Task SaysHelloCorrectly()
{
var builder = new TestClusterBuilder();
var cluster = builder.Build();
cluster.Deploy();
var hello = cluster.GrainFactory.GetGrain<IHelloGrain>(Guid.NewGuid());
var greeting = await hello.SayHello("World");
cluster.StopAllSilos();
Assert.Equal("Hello, World!", greeting);
}
}
A causa del sovraccarico che comporta l'avvio di un cluster in memoria, è possibile creare un oggetto TestCluster
e riutilizzarlo per più test case. Ad esempio, questa operazione può essere eseguita usando fixture di classe o di raccolta di xUnit.
Per condividere un TestCluster
tra più test case, creare prima di tutto un tipo di fixture:
using Orleans.TestingHost;
public sealed class ClusterFixture : IDisposable
{
public TestCluster Cluster { get; } = new TestClusterBuilder().Build();
public ClusterFixture() => Cluster.Deploy();
void IDisposable.Dispose() => Cluster.StopAllSilos();
}
Creare quindi una fixture di raccolta:
[CollectionDefinition(Name)]
public sealed class ClusterCollection : ICollectionFixture<ClusterFixture>
{
public const string Name = nameof(ClusterCollection);
}
È ora possibile riutilizzare un TestCluster
nei test case:
using Orleans.TestingHost;
namespace Tests;
[Collection(ClusterCollection.Name)]
public class HelloGrainTestsWithFixture(ClusterFixture fixture)
{
private readonly TestCluster _cluster = fixture.Cluster;
[Fact]
public async Task SaysHelloCorrectly()
{
var hello = _cluster.GrainFactory.GetGrain<IHelloGrain>(Guid.NewGuid());
var greeting = await hello.SayHello("World");
Assert.Equal("Hello, World!", greeting);
}
}
xUnit chiama il metodo Dispose() del tipo ClusterFixture
quando tutti i test sono stati completati e i silos del cluster in memoria sono arrestati. TestCluster
dispone anche di un costruttore che accetta TestClusterOptions, che è possibile usare per configurare i silos nel cluster.
Se si usa l'inserimento delle dipendenze nel silo per rendere disponibili i servizi per i grani, è anche possibile usare questo modello:
using Microsoft.Extensions.DependencyInjection;
using Orleans.TestingHost;
namespace Tests;
public sealed class ClusterFixtureWithConfig : IDisposable
{
public TestCluster Cluster { get; } = new TestClusterBuilder()
.AddSiloBuilderConfigurator<TestSiloConfigurations>()
.Build();
public ClusterFixtureWithConfig() => Cluster.Deploy();
void IDisposable.Dispose() => Cluster.StopAllSilos();
}
file sealed class TestSiloConfigurations : ISiloConfigurator
{
public void Configure(ISiloBuilder siloBuilder)
{
siloBuilder.ConfigureServices(static services =>
{
// TODO: Call required service registrations here.
// services.AddSingleton<T, Impl>(/* ... */);
});
}
}
Usare le simulazioni
Orleans consente anche la simulazione di molte parti del sistema e, per molti scenari, questo è il modo più semplice per eseguire unit test dei grani. Questo approccio presenta limitazioni (ad esempio, per la pianificazione della reentrancy e della serializzazione) e può richiedere che i grani includano il codice usato solo dagli unit test. L'Orleans TestKit offre un approccio alternativo, che consente di ovviare a molte di queste limitazioni.
Si supponga, ad esempio, che il grano da testare interagisca con altri grani. Per essere in grado di simulare gli altri grani, è anche necessario simulare il membro GrainFactory del grano sottoposto a test. Per impostazione predefinita, GrainFactory
è una proprietà protected
normale, ma la maggior parte dei framework di simulazione richiede che le proprietà siano public
e virtual
per poterle simulare. Di conseguenza, la prima cosa da fare è impostare GrainFactory
come proprietà sia public
che virtual
:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
A questo punto è possibile creare il grano all'esterno del runtime Orleans e usare la simulazione per controllare il comportamento di GrainFactory
:
using Xunit;
using Moq;
namespace Tests;
public class WorkerGrainTests
{
[Fact]
public async Task RecordsMessageInJournal()
{
var data = "Hello, World";
var journal = new Mock<IJournalGrain>();
var worker = new Mock<WorkerGrain>();
worker
.Setup(x => x.GrainFactory.GetGrain<IJournalGrain>(It.IsAny<Guid>()))
.Returns(journal.Object);
await worker.DoWork(data)
journal.Verify(x => x.Record(data), Times.Once());
}
}
In questo esempio viene creato il grano da testare, WorkerGrain
, usando Moq. Ciò significa che è possibile eseguire l'override del comportamento di GrainFactory
in modo che restituisca un IJournalGrain
simulato. È quindi possibile verificare che WorkerGrain
interagisca con IJournalGrain
come previsto.