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.
Questa esercitazione illustra come eseguire unit test dei grani per assicurarsi che si comportino correttamente. Esistono due modi principali per eseguire unit test dei grani e il metodo scelto dipende dal tipo di funzionalità che si sta testando. Usare il pacchetto NuGet Microsoft.Orleans.TestingHost per creare test silos per i tuoi grain, o usare un framework di mocking come Moq per simulare parti del runtime con cui il tuo grain interagisce.
Usare InProcessTestCluster (scelta consigliata)
InProcessTestCluster è l'infrastruttura di test consigliata per Orleans. Fornisce un'API semplificata basata su delegati per la configurazione dei cluster di test, semplificando la condivisione dei servizi tra i test e il cluster.
Vantaggi principali
Il vantaggio principale di InProcessTestCluster over TestCluster è l'ergonomicità:
- Configurazione basata su delegato: Configura i silo e i client usando delegati in linea anziché classi di configurazione separate
- Istanze di servizio condivise: condividi facilmente servizi mock, test doubles e altre istanze tra il codice di test e gli host silo
-
Meno boilerplate: non è necessario creare classi
ISiloConfiguratoroIClientConfiguratorseparate - Inserimento delle dipendenze più semplice: registrare i servizi direttamente nell'API Fluent del generatore
Sia InProcessTestCluster che TestCluster usano lo stesso host silo in-process sottostante per impostazione predefinita, quindi l'utilizzo della memoria e il tempo di avvio sono equivalenti. L'API TestCluster è progettata per supportare anche scenari multiprocesso (per la simulazione simile alla produzione), che richiede l'approccio di configurazione basato sulla classe, ma per impostazione predefinita viene eseguito in-process proprio come InProcessTestCluster.
Utilizzo di base
using Orleans.TestingHost;
using Xunit;
public class HelloGrainTests : IAsyncLifetime
{
private InProcessTestCluster _cluster = null!;
public async Task InitializeAsync()
{
var builder = new InProcessTestClusterBuilder();
_cluster = builder.Build();
await _cluster.DeployAsync();
}
public async Task DisposeAsync()
{
await _cluster.DisposeAsync();
}
[Fact]
public async Task SaysHello()
{
var grain = _cluster.Client.GetGrain<IHelloGrain>(0);
var result = await grain.SayHello("World");
Assert.Equal("Hello, World!", result);
}
}
Configurare il cluster di test
Usare InProcessTestClusterBuilder per configurare silo, client e servizi:
var builder = new InProcessTestClusterBuilder(initialSilosCount: 2);
// Configure silos
builder.ConfigureSilo((options, siloBuilder) =>
{
siloBuilder.AddMemoryGrainStorage("Default");
siloBuilder.AddMemoryGrainStorage("PubSubStore");
});
// Configure clients
builder.ConfigureClient(clientBuilder =>
{
// Client-specific configuration
});
// Configure both silos and clients (shared services)
builder.ConfigureHost(hostBuilder =>
{
hostBuilder.Services.AddSingleton<IMyService, MyService>();
});
var cluster = builder.Build();
await cluster.DeployAsync();
InProcessTestClusterOptions
| Opzione | TIPO | Predefinito | Description |
|---|---|---|---|
| ClusterId | string |
Generato automaticamente | Identificatore del cluster. |
| ServiceId | string |
Generato automaticamente | Identificatore del servizio. |
InitialSilosCount |
int |
1 | Numero di silo da avviare inizialmente. |
InitializeClientOnDeploy |
bool |
true |
Indica se inizializzare automaticamente il client in fase di distribuzione. |
ConfigureFileLogging |
bool |
true |
Abilitare la registrazione dei file per il debug. |
UseRealEnvironmentStatistics |
bool |
false |
Usare statistiche di memoria/CPU reali anziché valori simulati. |
GatewayPerSilo |
bool |
true |
Indica se ogni silo ospita un gateway per le connessioni client. |
Condividere un cluster di test fra diverse prove
Per migliorare le prestazioni dei test, condividere un singolo cluster in più test case usando le fixture xUnit:
public class ClusterFixture : IAsyncLifetime
{
public InProcessTestCluster Cluster { get; private set; } = null!;
public async Task InitializeAsync()
{
var builder = new InProcessTestClusterBuilder();
builder.ConfigureSilo((options, siloBuilder) =>
{
siloBuilder.AddMemoryGrainStorageAsDefault();
});
Cluster = builder.Build();
await Cluster.DeployAsync();
}
public async Task DisposeAsync()
{
await Cluster.DisposeAsync();
}
}
[CollectionDefinition(nameof(ClusterCollection))]
public class ClusterCollection : ICollectionFixture<ClusterFixture>
{
}
[Collection(nameof(ClusterCollection))]
public class HelloGrainTests
{
private readonly ClusterFixture _fixture;
public HelloGrainTests(ClusterFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task SaysHello()
{
var grain = _fixture.Cluster.Client.GetGrain<IHelloGrain>(0);
var result = await grain.SayHello("World");
Assert.Equal("Hello, World!", result);
}
}
Aggiungere e rimuovere i silos durante i test
InProcessTestCluster supporta la gestione dinamica del silo per il test del comportamento del cluster:
// Start with 2 silos
var builder = new InProcessTestClusterBuilder(initialSilosCount: 2);
var cluster = builder.Build();
await cluster.DeployAsync();
// Add a third silo
var newSilo = await cluster.StartSiloAsync();
// Stop a silo
await cluster.StopSiloAsync(newSilo);
// Restart all silos
await cluster.RestartAsync();
Usa TestCluster
TestCluster utilizza un approccio di configurazione basato su classi che richiede l'implementazione delle interfacce ISiloConfigurator e IClientConfigurator. Questa progettazione supporta scenari di test a più processi, in cui i silo vengono eseguiti in processi separati, il che è utile per test di simulazione a livello produttivo. Tuttavia, per impostazione predefinita TestCluster viene eseguito anche in-process con prestazioni equivalenti a InProcessTestCluster.
Scegliere TestCluster invece di InProcessTestCluster quando:
- Sono necessari test multiprocesso per la simulazione di produzione
- Sono presenti test esistenti usando l'API
TestCluster - È necessaria la compatibilità con Orleans 7.x o 8.x
Per i nuovi test, InProcessTestCluster è raccomandato grazie alla configurazione più semplice basata su delegati.
Il pacchetto NuGet Microsoft.Orleans.TestingHost contiene TestCluster, che è possibile utilizzare per creare un cluster in memoria, composto per impostazione predefinita da due silos, utile per testare i granelli.
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 di avvio di un cluster in memoria, si potrebbe voler creare un TestCluster e riutilizzarlo tra più test case. Ad esempio, ottenere questo risultato usando la classe o le fixture di raccolta di xUnit.
Per condividere un oggetto 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();
}
Successivamente, crea un oggetto di raccolta:
[CollectionDefinition(Name)]
public sealed class ClusterCollection : ICollectionFixture<ClusterFixture>
{
public const string Name = nameof(ClusterCollection);
}
È ora possibile riutilizzare un oggetto 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);
}
}
Quando tutti i test sono completati e i silo del cluster in memoria vengono arrestati, xUnit chiama il metodo del tipo Dispose().
TestCluster ha anche un costruttore che accetta TestClusterOptions che è possibile usare per configurare i silo nel cluster.
Se utilizzi Dependency Injection (iniezione di dipendenze) nel tuo Silo per fornire servizi ai Grains di Orleans, puoi usare anche 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)
{
// TODO: Call required service registrations here.
// siloBuilder.Services.AddSingleton<T, Impl>(/* ... */);
}
}
Usare simulazioni
Orleans consente anche di simulare molte parti del sistema. Per molti scenari, questo è il modo più semplice per eseguire unit test dei grani. Questo approccio presenta limitazioni (ad esempio, nella gestione della reentrancy e nella serializzazione) e potrebbe richiedere che i grain includano codice utilizzato solo nei test unitari. TestKitOrleans offre un approccio alternativo che impedisce molte di queste limitazioni.
Si supponga, ad esempio, che il grano che si sta testando interagisca con altri grani. Per simulare questi altri grani, è anche necessario simulare il GrainFactory membro del grano sottoposto a test. Per impostazione predefinita, GrainFactory è una normale proprietà protected, ma la maggior parte dei framework di mocking richiede che le proprietà siano public e virtual per abilitare il mocking. Quindi, il primo passaggio consiste nel fare GrainFactory sia public che virtual:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
Ora puoi creare il tuo grain al di fuori del runtime Orleans e utilizzare il mocking 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 caso, creare il livello di granularità sottoposto a test, WorkerGrain, usando Moq. In questo modo è 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.