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 realizar pruebas unitarias de los granos, y el método que elija dependerá del tipo de funcionalidad que esté probando. El paquete NuGet Microsoft.Orleans.TestingHost puede usarse para crear silos de pruebas para sus granos, o puede usar un marco de trabajo de imitación como Moq para imitar partes del runtime Orleans con las que interactúa su grano.

Utilice el TestCluster

El paquete NuGet Microsoft.Orleans.TestingHost contiene TestCluster, que se puede usar para crear un clúster en memoria, compuesto por dos silos de manera predeterminada, que se puede usar para probar los 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 que supone iniciar un clúster en memoria, es posible que quiera crear un TestCluster y reutilizarlo en varios casos de prueba. Por ejemplo, esto se puede hacer mediante clases de xUnit o accesorios de prueba de colección.

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

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, crear un accesorio de prueba de colección:

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

Ahora puede volver a usar TestCluster en los 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 han completado todas las pruebas, xUnit llama al método Dispose() del tipo ClusterFixture y se detienen los silos del clúster en memoria. TestCluster también tiene un constructor que acepta TestClusterOptions y que se puede usar para configurar los silos del clúster.

Si usa la inserción de dependencias en el silo para que los servicios estén a disposición de 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>(/* ... */);
        });
    }
}

Uso de simulaciones

Orleans también permite simular muchas partes del sistema, lo que en muchos escenarios es la manera más sencilla de realizar pruebas unitarias de los granos. Este enfoque tiene limitaciones (por ejemplo, en torno a la programación de reentrada y la serialización), y puede exigir que los granos incluyan código que solo sea usado por las pruebas unitarias. La utilidad TestKit de Orleans proporciona un enfoque alternativo que evita muchas de estas limitaciones.

Por ejemplo, imagine que el grano que estamos probando interactúa con otros granos. Para poder simular esos otros granos también necesita simular el miembro GrainFactory del grano sometido a prueba. De manera predeterminada, GrainFactory es una propiedad protected normal, pero la mayoría de los marcos de simulación exigen que las propiedades sean public y virtual para poder simularlas. Por lo tanto, lo primero que hay que hacer es convertir a GrainFactory en una propiedad public y virtual:

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

Ahora puede crear el grano fuera del runtime de Orleans y usar la simulación 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í crea el grano sometido a prueba, WorkerGrain, mediante Moq, lo que significa que se puede invalidar el comportamiento de GrainFactory de modo que devuelva un IJournalGrain simulado. Después podrá comprobar que WorkerGrain interactúa con IJournalGrain como esperaba.