Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tutorial mostra como fazer o teste de unidade dos grãos para garantir que eles se comportem corretamente. Há duas maneiras principais de testar os 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 seus grãos, ou use um framework de simulação como o Moq para simular partes do runtime com o qual seu grão interage.
Usar o InProcessTestCluster (recomendado)
A InProcessTestCluster é a infraestrutura de teste recomendada para Orleans. Ele fornece uma API simplificada e baseada em delegado para configurar clusters de teste, facilitando o compartilhamento de serviços entre seus testes e o cluster.
Principais vantagens
A principal vantagem do InProcessTestCluster sobre o TestCluster é a ergonomia:
- Configuração baseada em delegado: configurar silos e clientes usando delegados embutidos em vez de classes de configuração separadas
- Instâncias de serviço compartilhado: compartilhar facilmente serviços fictícios, duplas de teste e outras instâncias entre o código de teste e os hosts de silo
-
Menos clichê: não é necessário criar classes ou
ISiloConfiguratorseparadasIClientConfigurator - Injeção de dependência mais simples: registrar serviços diretamente na API fluente do builder
InProcessTestCluster e TestCluster ambos usam o mesmo silo host de processo subjacente por padrão, assim o uso de memória e o tempo de inicialização são equivalentes. A API TestCluster foi projetada para também dar suporte a cenários de múltiplos processos (para simulação similar à produção), que requer a abordagem de configuração baseada em classe, mas, por padrão, é executada no mesmo processo que InProcessTestCluster.
Uso Básico
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 teste
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 | Padrão | Description |
|---|---|---|---|
| ClusterId | string |
Gerado automaticamente | Identificador de cluster. |
| ServiceId | string |
Gerado automaticamente | Identificador de serviço. |
InitialSilosCount |
int |
1 | Número de silos a serem iniciados inicialmente. |
InitializeClientOnDeploy |
bool |
true |
Se o cliente será inicializado automaticamente na implantação. |
ConfigureFileLogging |
bool |
true |
Habilite o registro de arquivos 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 hospeda um gateway para conexões de cliente. |
Compartilhar um cluster de teste entre testes
Para melhorar o desempenho do teste, compartilhe um único cluster em vários casos de teste usando dispositivos 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 testes
O InProcessTestCluster dá suporte ao gerenciamento de silo dinâmico para testar o 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();
Usar o TestCluster
O TestCluster usa uma abordagem de configuração baseada em classe que requer a implementação das interfaces ISiloConfigurator e IClientConfigurator. Esse design dá suporte a cenários de teste de vários processos em que os silos são executados em processos separados, o que é útil para testes de simulação semelhantes à produção. No entanto, por padrão TestCluster , também é executado em processo com desempenho equivalente a InProcessTestCluster.
Escolha TestCluster em vez de InProcessTestCluster quando:
- Você precisa de testes de vários processos para simulação de produção
- Você tem testes existentes usando a
TestClusterAPI - Você precisa de compatibilidade com Orleans 7.x ou 8.x
Para novos testes, InProcessTestCluster é recomendável devido à configuração mais simples baseada em delegado.
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, talvez você queira criar um TestCluster e reutilizá-lo entre vários casos de teste. Por exemplo, obtenha isso usando as instalações de classe ou coleção do xUnit.
Para compartilhar um TestCluster entre vários casos de teste, primeiro crie uma fixura:
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 uma instalação 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);
}
}
Quando todos os testes são concluídos e os silos do cluster na memória param, o xUnit chama o método Dispose() do tipo ClusterFixture.
TestCluster também tem um construtor que aceita TestClusterOptions que você pode usar para configurar os silos no grupo.
Se você usar a Injeção de Dependência em seu Silo para disponibilizar serviços ao Grains, também poderá usar esse padrão:
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>(/* ... */);
}
}
Usar simulações
Orleans também permite zombar de muitas partes do sistema. Para muitos cenários, essa é a maneira mais fácil de unitar os grãos de teste. Essa abordagem tem limitações (por exemplo, em relação ao agendamento de reentrância e serialização) e pode exigir grãos para incluir o código usado apenas pelos testes de unidade. O Orleans TestKit fornece uma abordagem alternativa que ignora muitas dessas limitações.
Por exemplo, imagine que o grão que você está testando interage com outros grãos. Para zombar desses outros grãos, você também precisa zombar do GrainFactory membro do grão em teste. Por padrão, GrainFactory é uma propriedade normal protected, mas a maioria dos frameworks de simulação exige que as propriedades sejam public e virtual para habilitar a simulação. Portanto, a primeira etapa é fazer GrainFactory tanto public quanto virtual:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
Agora você pode criar seus grãos fora do Orleans runtime e usar a zombaria 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, WorkerGrainusando Moq. Isso permite substituir o comportamento do GrainFactory para que ele retorne um IJournalGrain simulado. Em seguida, você pode verificar se WorkerGrain interage com IJournalGrain conforme o esperado.