Delen via


Unittesten met Orleans

In deze tutorial wordt uitgelegd hoe u eenheids tests kunt uitvoeren op uw componenten om ervoor te zorgen dat ze correct functioneren. Er zijn twee belangrijkste manieren om uw korrels te testen en de methode die u kiest, is afhankelijk van het type functionaliteit dat u test. Gebruik Microsoft.Orleans. TestingHost NuGet-pakket om testsilo's voor uw korrels te maken, of gebruik een mockingframework zoals Moq om delen van de Orleans runtime te mocken waarmee uw graan communiceert.

Dit InProcessTestCluster is de aanbevolen testinfrastructuur voor Orleans. Het biedt een gestroomlijnde, gedelegeerde API voor het configureren van testclusters, waardoor het eenvoudiger is om services te delen tussen uw tests en het cluster.

Belangrijkste voordelen

Het belangrijkste voordeel van InProcessTestCluster over TestCluster is ergonomisch:

  • Configuratie op basis van gemachtigden: Silo's en clients configureren met inline gedelegeerden in plaats van afzonderlijke configuratieklassen
  • Gedeelde service-exemplaren: Deel eenvoudig mock-services, test doubles en andere exemplaren tussen uw testcode en de silohosts
  • Minder standaard: u hoeft geen afzonderlijke ISiloConfigurator klassen of IClientConfigurator klassen te maken
  • Eenvoudigere afhankelijkheidsinjectie: Services rechtstreeks registreren in de Builder Fluent API

Beide InProcessTestCluster en TestCluster gebruiken dezelfde onderliggende in-process silohost standaard, dus geheugengebruik en opstarttijd zijn equivalent. De TestCluster API is ontworpen om ook scenario's met meerdere processen te ondersteunen (voor productieachtige simulatie), waarvoor de op klassen gebaseerde configuratiebenadering is vereist, maar standaard wordt deze in het proces uitgevoerd, net zoals InProcessTestCluster.

Basaal gebruik

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

Het testcluster configureren

Gebruik InProcessTestClusterBuilder om silo's, clients en services te configureren.

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

Optie Typologie Verstek Description
ClusterId string Automatisch gegenereerd Cluster-id.
ServiceId string Automatisch gegenereerd Service-id.
InitialSilosCount int 1 Het aantal silo's dat in eerste instantie moet worden gestart.
InitializeClientOnDeploy bool true Of de client automatisch moet worden geïnitialiseerd bij de implementatie.
ConfigureFileLogging bool true Schakel logboekregistratie van bestanden in voor foutopsporing.
UseRealEnvironmentStatistics bool false Gebruik werkelijke geheugen-/CPU-statistieken in plaats van gesimuleerde waarden.
GatewayPerSilo bool true Of elke silo fungeert als host voor een gateway voor clientverbindingen.

Een testcluster delen tussen tests

Als u de testprestaties wilt verbeteren, deelt u één cluster over meerdere testcases met behulp van xUnit-armaturen:

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

Silo's toevoegen en verwijderen tijdens tests

Het InProcessTestCluster ondersteunt dynamisch silobeheer voor het testen van clustergedrag:

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

Gebruik de TestCluster

De TestCluster methode maakt gebruik van een op klassen gebaseerde configuratiebenadering waarvoor implementatie ISiloConfigurator en IClientConfigurator interfaces vereist zijn. Dit ontwerp ondersteunt scenario's voor het testen van meerdere processen waarbij silo's worden uitgevoerd in afzonderlijke processen, wat nuttig is voor productieachtige simulatietests. Standaard TestCluster wordt echter ook in het proces uitgevoerd met gelijkwaardige prestaties als InProcessTestCluster.

Kies TestCluster boven InProcessTestCluster wanneer:

  • U hebt tests met meerdere processen nodig voor productiesimulatie
  • U hebt bestaande tests met behulp van de TestCluster API
  • U hebt compatibiliteit nodig met Orleans 7.x of 8.x

Voor nieuwe tests wordt InProcessTestCluster aanbevolen vanwege de configuratie die eenvoudiger is dankzij het gebruik van gedelegeerden.

Het Microsoft.Orleans.TestingHost NuGet-pakket bevat TestCluster, dat u kunt gebruiken om een in-memory cluster (standaard uit twee silo's) te maken voor het testen van korrels.

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

Vanwege de overhead van het starten van een in-memory cluster, kunt u een TestCluster cluster maken en opnieuw gebruiken in meerdere testcases. U kunt dit bijvoorbeeld bereiken met behulp van de klasse- of verzamelingsinrichtingen van xUnit.

Als u een TestCluster tussen meerdere testcases wilt delen, maakt u eerst een fixtuurtype aan:

using Orleans.TestingHost;

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

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

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

Maak vervolgens een verzamelingsarmaturen:

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

U kunt nu een TestCluster in uw testcases opnieuw gebruiken:

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

Wanneer alle tests zijn voltooid en de clustersilo's in het geheugen stoppen, roept xUnit de Dispose() methode van het ClusterFixture type aan. TestCluster heeft ook een constructor die accepteert TestClusterOptions dat u kunt gebruiken om de silo's in het cluster te configureren.

Als u afhankelijkheidsinjectie in uw silo gebruikt om services beschikbaar te maken voor Grains, kunt u dit patroon ook gebruiken:

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

Mocks gebruiken

Orleans maakt het ook mogelijk om veel onderdelen van het systeem te mocken. Voor veel scenario's is dit de eenvoudigste manier om korrels te testen. Deze aanpak heeft beperkingen (bijvoorbeeld bij het plannen van reentrancy en serialisatie) en vereist mogelijk korrels om code op te nemen die alleen door uw eenheidstests wordt gebruikt. De Orleans TestKit biedt een alternatieve benadering die veel van deze beperkingen afstleden.

Stel dat het graan dat u test, communiceert met andere korrels. Als u die andere grains wilt nabootsen, moet u ook het GrainFactory lid van de grain die getest wordt nabootsen. Standaard is GrainFactory een normale protected eigenschap, maar voor de meeste mockingframeworks moeten eigenschappen public en virtual zijn om mocking mogelijk te maken. De eerste stap is dus om GrainFactory zowel public als virtual:

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

Nu kunt u uw korrel buiten de Orleans runtime maken en mocking gebruiken om het gedrag van GrainFactory te beheersen.

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

Maak hier het graan onder test, WorkerGrainmet behulp van Moq. Hierdoor kan het gedrag van de GrainFactory worden overschreven, zodat het een gesimuleerde IJournalGrain retourneert. Vervolgens kunt u controleren of WorkerGrain samenwerkt met IJournalGrain zoals verwacht.