Teste de unidade com Orleans
Este tutorial mostra como executar testes de unidade nos seus grãos para garantir seu comportamento correto. Há duas maneiras principais de executar testes de unidade nos seus grãos, e o método escolhido dependerá do tipo de funcionalidade em teste. O pacote NuGet Microsoft.Orleans.TestingHost pode ser usado para criar silos de teste para seus grãos, ou você pode usar uma estrutura de simulação como Moq para simular partes do runtime do Orleans com as quais seus grãos interagem.
Use o TestCluster
O pacote NuGet Microsoft.Orleans.TestingHost
contém TestCluster que pode ser usado para criar um cluster na memória, por padrão composto por dois silos, que podem ser usados 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 seja interessante criar um TestCluster
e reutilizá-lo entre vários casos de teste. Por exemplo, isso pode ser feito usando-se a classe ou os acessórios de coleção do xUnit.
Para compartilhar um TestCluster
entre vários casos de teste, primeiro crie um tipo de acessório:
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
nos 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);
}
}
O xUnit chama o método Dispose() do tipo ClusterFixture
quando todos os testes foram concluídos e os silos do cluster na memória foram interrompidos. TestCluster
também tem um construtor que aceita TestClusterOptions, que pode ser usado para configurar os silos no cluster.
Se você estiver usando a Injeção de Dependência no seu Silo para disponibilizar serviços para o 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)
{
siloBuilder.ConfigureServices(static services =>
{
// TODO: Call required service registrations here.
// services.AddSingleton<T, Impl>(/* ... */);
});
}
}
Uso de simulações
O Orleans também possibilita simular muitas partes do sistema e, para muitos cenários, esta é a maneira mais fácil de executar testes de unidade em grãos. Essa abordagem tem limitações (por exemplo, em relação ao agendamento de reentrada e serialização), e pode exigir que os grãos incluam código usado apenas pelos seus testes de unidade. O TestKit do Orleans fornece uma abordagem alternativa que contorna muitas dessas limitações.
Por exemplo, imagine que o grão que você está testando interaja com outros. Para poder simular esses outros grãos, você também precisa simular o membro GrainFactory do grão em teste. Por padrão, GrainFactory
é uma propriedade normal protected
, mas a maioria das estruturas de simulação exige que as propriedades sejam public
e virtual
para que consigam simulá-las. Logo, a primeira coisa que você precisa fazer é tornar GrainFactory
uma propriedade public
e virtual
:
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
Agora você pode criar seus grãos fora do runtime do Orleans e usar a simulação 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, você cria o grão em teste, WorkerGrain
, usando Moq, o que significa que você pode substituir o comportamento de GrainFactory
para que retorne uma simulação IJournalGrain
. Em seguida, você pode verificar se o WorkerGrain
interage com o IJournalGrain
como esperado.