Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este tutorial mostra como testar os grãos para garantir que eles se comportam corretamente. Há duas maneiras principais de testar seus grãos por unidade, e o método escolhido depende do tipo de funcionalidade que você está testando. Use o pacote NuGet Microsoft.Orleans.TestingHost para criar silos de teste para os seus grãos, ou utiliza uma estrutura de simulação como Moq para simular partes do tempo de execução com o Orleans qual o seu grão interage.
Use o InProcessTestCluster (recomendado)
InProcessTestCluster é a infraestrutura de teste recomendada para Orleans. Fornece uma API simplificada baseada em delegados para configurar clusters de teste, facilitando a partilha de serviços entre os seus testes e o cluster.
Principais vantagens
A principal vantagem do InProcessTestCluster over TestCluster é a ergonomia:
- Configuração baseada em delegados: Configurar silos e clientes usando delegados inline em vez de classes de configuração separadas
- Instâncias de serviço partilhado: Partilha facilmente serviços simulados, duplicações de teste e outras instâncias entre o teu código de teste e os anfitriões do silo
-
Menos padrão: Não é necessário criar classes separadas
ISiloConfiguratorouIClientConfigurator - Injeção de dependências mais simples: Registar serviços diretamente na API fluente do builder
Ambos InProcessTestCluster e TestCluster usam, por defeito, o mesmo host silo subjacente em processo, o que significa que o uso de memória e o tempo de arranque são equivalentes. A TestCluster API foi concebida para suportar também cenários de múltiplos processos (para simulação semelhante à produção), o que requer a abordagem de configuração baseada em classes, mas por padrão corre em processo tal como InProcessTestCluster.
Utilização básica
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);
}
}
Configurar o cluster de testes
Use InProcessTestClusterBuilder para configurar silos, clientes e serviços:
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
| Opção | Tipo | Predefinido | Description |
|---|---|---|---|
| ClusterId | string |
Gerado automaticamente | Identificador de cluster. |
| ServiceId | string |
Gerado automaticamente | Identificador do serviço. |
InitialSilosCount |
int |
1 | Número de silos para começar inicialmente. |
InitializeClientOnDeploy |
bool |
true |
Se deve inicializar automaticamente o cliente na implementação. |
ConfigureFileLogging |
bool |
true |
Ativar o registo de ficheiros para depuração. |
UseRealEnvironmentStatistics |
bool |
false |
Use estatísticas reais de memória/CPU em vez de valores simulados. |
GatewayPerSilo |
bool |
true |
Se cada silo aloja um gateway para ligações ao cliente. |
Partilhar um cluster de testes entre testes
Para melhorar o desempenho dos testes, partilhe um único cluster entre múltiplos casos de teste usando fixtures 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);
}
}
Adicionar e remover silos durante os testes
A gestão dinâmica de silos pelo InProcessTestCluster suporta o teste do comportamento do 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();
Utilize o TestCluster
A TestCluster utiliza uma abordagem de configuração baseada em classes que requer a implementação das interfaces ISiloConfigurator e IClientConfigurator. Este design suporta cenários de teste multiprocesso onde silos correm em processos separados, o que é útil para testes de simulação semelhantes à produção. No entanto, por defeito TestCluster também corre em processo com desempenho equivalente a InProcessTestCluster.
Escolha TestCluster em vez de InProcessTestCluster quando:
- Precisas de testes multiprocesso para simulação de produção
- Tem testes existentes que utilizam a API
TestCluster - Precisas de compatibilidade com Orleans 7.x ou 8.x
Para novos testes, InProcessTestCluster é recomendado devido à sua configuração mais simples baseada em delegados.
O Microsoft.Orleans.TestingHost pacote NuGet contém TestCluster, que você pode usar para criar um cluster na memória (composto por dois silos por padrão) para testar grãos.
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);
}
}
Devido à sobrecarga de iniciar um cluster na memória, convém criar um TestCluster e reutilizá-lo entre vários casos de teste. Por exemplo, faça isso usando as fixtures de classe ou de coleção do xUnit.
Para partilhar um TestCluster entre vários casos de teste, primeiro crie um tipo de 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();
}
Em seguida, crie um acessório de coleção:
[CollectionDefinition(Name)]
public sealed class ClusterCollection : ICollectionFixture<ClusterFixture>
{
public const string Name = nameof(ClusterCollection);
}
Agora você pode reutilizar um TestCluster em seus casos de teste:
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);
}
}
Após todos os testes serem concluídos e os silos de cluster na memória pararem, xUnit chama o método Dispose() do tipo ClusterFixture.
TestCluster também tem um construtor que aceita TestClusterOptions, que pode ser usado para configurar os silos no cluster.
Se utilizar a injeção de dependência no seu Silo para disponibilizar serviços aos Grãos, poderá usar este padrão também:
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>(/* ... */);
}
}
Utilizar simulações
Orleans também permite imitar diversas partes do sistema. Para muitos cenários, essa é a maneira mais fácil de testar grãos por unidade. Essa abordagem tem limitações (por exemplo, em torno do agendamento de reentrância e serialização) e pode exigir que as 'grains' incluam código usado apenas pelos seus testes de unidade. O Orleans TestKit fornece uma abordagem alternativa que contorna muitas dessas limitações.
Por exemplo, imagine que o grão que você está testando interage com outros grãos. Para simular esses outros elementos, você também precisa simular o GrainFactory membro do elemento em teste. Por padrão, GrainFactory é uma propriedade normal protected, mas a maioria das estruturas de simulação requer que as propriedades sejam public e virtual para permitir a simulação. Assim, o primeiro passo é fazer GrainFactory tanto public como virtual:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
Agora você pode criar seu grão fora do Orleans tempo de execução e usar mocking para controlar o comportamento de 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());
}
}
Aqui, crie o grão em teste, WorkerGrain usando Moq. Isso permite substituir o comportamento do GrainFactory para que ele retorne um IJournalGrain simulado. Em seguida, pode verificar se WorkerGrain interage com IJournalGrain como esperado.