Compartir a través de


Pruebas unitarias con Orleans

En este tutorial se muestra cómo realizar pruebas unitarias de los granos para asegurarse de que se comportan correctamente. Hay dos maneras principales de probar los granos de forma unitaria y el método que elija depende del tipo de funcionalidad que esté probando. Utiliza el paquete NuGet de Microsoft.Orleans.TestingHost para crear silos de prueba para tus granos, o utiliza un marco ficticio como Moq para simular partes del entorno de ejecución con las que interactúa tu grano.

Utilice el TestCluster

El Microsoft.Orleans.TestingHost paquete NuGet contiene TestCluster, que puede usar para crear un clúster en memoria (compuesto por dos silos de forma predeterminada) para probar granos.

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);
    }
}

Debido a la sobrecarga de iniciar un clúster en memoria, es posible que desee crear un TestCluster y reutilizarlo entre varios casos de prueba. Por ejemplo, consiga esto con los accesorios de colección o clase de xUnit.

Para compartir un TestCluster entre varios casos de prueba, primero cree un tipo de accesorio:

using Orleans.TestingHost;

public sealed class ClusterFixture : IDisposable
{
    public TestCluster Cluster { get; } = new TestClusterBuilder().Build();

    public ClusterFixture() => Cluster.Deploy();

    void IDisposable.Dispose() => Cluster.StopAllSilos();
}

A continuación, cree un accesorio de colección:

[CollectionDefinition(Name)]
public sealed class ClusterCollection : ICollectionFixture<ClusterFixture>
{
    public const string Name = nameof(ClusterCollection);
}

Ahora puede volver a usar un TestCluster en sus casos de prueba.

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);
    }
}

Cuando se completen todas las pruebas y se detengan los silos del clúster en memoria, xUnit llama al método Dispose() del tipo ClusterFixture. TestCluster también tiene un constructor que acepta TestClusterOptions que puede usar para configurar los silos en el clúster.

Si usa la inyección de dependencias en su Silo para que los servicios estén disponibles para los Granos, también puede usar este patrón:

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>(/* ... */);
        });
    }
}

Usar objetos ficticios

Orleans también permite simular muchas partes del sistema. En muchos escenarios, esta es la manera más fácil de probar granos unitarios. Este enfoque tiene limitaciones (por ejemplo, en torno a la programación de la reentrada y la serialización) y podría requerir que los granos incluyan código usado solo por las pruebas unitarias. TestKitOrleans proporciona un enfoque alternativo que evita muchas de estas limitaciones.

Por ejemplo, imagine el grano que está probando interactúa con otros granos. Para simular esos otros granos, también debe simular el GrainFactory miembro del grano sometido a prueba. De forma predeterminada, GrainFactory es una propiedad normal protected, pero la mayoría de los frameworks de mocking requieren que las propiedades sean public y virtual para habilitar el uso de mocks. Por lo tanto, el primer paso es hacer que GrainFactory sea tanto public como virtual:

public new virtual IGrainFactory GrainFactory
{
    get => base.GrainFactory;
}

Ahora puede crear su grano fuera del Orleans entorno de ejecución y usar el simulacro para controlar el comportamiento 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());
    }
}

Aquí, cree el grano en prueba, WorkerGrain, mediante Moq. Esto permite sobrescribir el comportamiento de GrainFactory para que devuelva un IJournalGrain simulado. A continuación, puede comprobar que WorkerGrain interactúa con IJournalGrain según lo previsto.