Freigeben über


Komponententests mit Orleans

In diesem Tutorial wird gezeigt, wie Sie Komponententests für Ihre Grains durchführen, um sicherzustellen, dass sie sich ordnungsgemäß verhalten. Komponententests für Ihre Grains können im Grunde auf zwei Arten durchgeführt werden. Die passende Methode hängt von der Art der Funktion ab, die Sie testen möchten. Das NuGet-Paket Microsoft.Orleans.TestingHost kann verwendet werden, um Testsilos für Ihre Grains zu erstellen. Alternativ können Sie ein Pseudoframework wie Moq verwenden, um Teile der Orleans-Runtime zu simulieren, mit denen Ihr Grain interagiert.

Verwenden des TestClusters

Das NuGet-Paket Microsoft.Orleans.TestingHost enthält TestCluster. Damit kann ein In-Memory-Cluster erstellt werden, das standardmäßig aus zwei Silos besteht, die zum Testen von Grains verwendet werden können.

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

Aufgrund des Mehraufwands beim Starten eines In-Memory-Clusters können Sie einen TestCluster erstellen und in mehreren Testfällen wiederverwenden. Dies kann z. B. mit der xUnit-Klasse oder Sammlungsfixtures erfolgen.

Um einen TestCluster für mehrere Testfälle zu verwenden, erstellen Sie zuerst einen Fixturetyp:

using Orleans.TestingHost;

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

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

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

Erstellen Sie als Nächstes eine Sammlungsfixture:

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

Sie können jetzt einen TestCluster in Ihren Testfällen wiederverwenden:

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

xUnit ruft die Dispose()-Methode des ClusterFixture-Typs auf, wenn alle Tests abgeschlossen sind und die In-Memory-Clustersilos beendet wurden. TestCluster verfügt außerdem über einen Konstruktor, der Optionen vom Typ TestClusterOptions akzeptiert, die zum Konfigurieren der Silos im Cluster verwendet werden können.

Wenn Sie die Abhängigkeitsinjektion in Ihrem Silo verwenden, um Dienste für Grains verfügbar zu machen, können Sie auch das folgende Muster verwenden:

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

Verwenden von Simulationen

Orleans ermöglicht es auch, viele Teile des Systems zu simulieren, und für viele Szenarien ist dies die einfachste Möglichkeit, Komponententests für Grains durchzuführen. Dieser Ansatz hat gewisse Einschränkungen (z. B. im Zusammenhang mit Termineintrittsvarianz und Terminserialisierung) und erfordert möglicherweise, dass Grains Code enthalten, der nur von Ihren Komponententests verwendet wird. Das Orleans-Testkit bietet einen alternativen Ansatz, der viele dieser Einschränkungen umgeht.

Stellen Sie sich beispielsweise vor, dass das getestete Grain mit anderen Grains interagiert. Um diese anderen Grains simulieren zu können, müssen Sie auch den GrainFactory-Member des zu testenden Grains simulieren. Standardmäßig ist GrainFactory eine normale geschützte Eigenschaft (protected). Bei den meisten Pseudoframeworks müssen Eigenschaften allerdings öffentlich (public) und virtuell (virtual) sein, damit sie simuliert werden können. Daher muss GrainFactory als Erstes sowohl zu einer public-Eigenschaft als auch zu einer virtual-Eigenschaft gemacht werden:

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

Nun kann das Grain außerhalb der Orleans-Runtime erstellt und das Verhalten von GrainFactory mithilfe einer Simulation gesteuert werden:

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

Hier wird das zu testende Grain WorkerGrain unter Verwendung von Moq erstellt. Das bedeutet, dass das Verhalten von GrainFactory außer Kraft gesetzt werden kann, sodass ein simuliertes IJournalGrain-Element zurückgegeben wird. Anschließend kann überprüft werden, ob WorkerGrain wie erwartet mit IJournalGrain interagiert.