Sdílet prostřednictvím


Testování jednotek s využitím Orleans

V tomto kurzu se dozvíte, jak zrna testovat, aby se zajistilo, že se chovají správně. Existují dva hlavní způsoby testování zrn a metoda, kterou zvolíte, závisí na typu funkce, kterou testujete. Použijte balíček NuGet Microsoft.Orleans.TestingHost k vytvoření testovacích sil pro vaše aktory nebo použijte napodobovací rámec, jako je Moq, k napodobení částí Orleans modulu runtime, se kterými váš aktor interaguje.

Jedná se InProcessTestCluster o doporučenou testovací infrastrukturu pro Orleans. Poskytuje zjednodušené rozhraní API založené na delegátech pro konfiguraci testovacích clusterů, což usnadňuje sdílení služeb mezi testy a clusterem.

Klíčové výhody

Hlavní výhodou InProcessTestCluster oproti TestClusterergonomii je:

  • Konfigurace založená na delegátech: Konfigurace sila a klientů využívajících vložené delegáty místo samostatných tříd konfigurace
  • Instance sdílených služeb: Snadné sdílení napodobených služeb, testovacích dvojníků a dalších instancí mezi vaším testovacím kódem a silo hostiteli
  • Méně šablonového kódu: Není nutné vytvářet samostatné ISiloConfigurator nebo IClientConfigurator třídy
  • Jednodušší injektáž závislostí: Registrace služeb přímo v rozhraní FLUENT API tvůrce

InProcessTestCluster a TestCluster ve výchozím nastavení používají stejného základního hostitele sila v procesu, takže využití paměti a čas spuštění jsou stejné. Rozhraní TestCluster API je navržené tak, aby podporovalo také scénáře s více procesy (pro simulaci podobné produkčnímu prostředí), které vyžadují přístup konfigurace založený na třídě, ale ve výchozím nastavení se spouští v procesu stejně jako InProcessTestCluster.

Základní použití

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

Konfigurace testovacího clusteru

Slouží InProcessTestClusterBuilder ke konfiguraci sila, klientů a služeb:

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

Možnost Typ Výchozí Description
ClusterId string Automaticky vygenerováno Identifikátor clusteru.
ServiceId string Automaticky vygenerováno Identifikátor služby.
InitialSilosCount int 1 Počet sil, která se mají zprovoznit nejprve.
InitializeClientOnDeploy bool true Určuje, jestli se má automaticky inicializovat klient při nasazení.
ConfigureFileLogging bool true Povolte protokolování souborů pro ladění.
UseRealEnvironmentStatistics bool false Místo simulovaných hodnot používejte statistiku skutečné paměti a procesoru.
GatewayPerSilo bool true Určuje, jestli každý silo hostuje bránu pro připojení klientů.

Sdílení testovacího clusteru mezi testy

Pokud chcete zvýšit výkon testů, sdílejte jeden cluster napříč několika testovacími případy pomocí příslušenství xUnit:

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

Přidat a odebrat úložiště během testů

Podpora InProcessTestCluster dynamického řízení úložných modulů pro testování fungování clusteru:

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

Použijte TestCluster

Používá TestCluster metodu konfigurace založenou na třídě, která vyžaduje implementaci ISiloConfigurator a IClientConfigurator rozhraní. Tento návrh podporuje testování víceprocesních scénářů, ve kterých sila běží v oddělených procesech, což je užitečné pro testování simulací podobných produkčnímu prostředí. Ve výchozím nastavení však TestCluster běží také v procesu s ekvivalentním výkonem jako InProcessTestCluster.

Zvolte TestCluster místo InProcessTestCluster kdy:

  • Potřebujete víceprocesové testování pro produkční simulaci.
  • Máte existující testy pomocí TestCluster rozhraní API.
  • Potřebujete kompatibilitu s Orleans verzí 7.x nebo 8.x.

Pro nové testy se doporučuje InProcessTestCluster kvůli jednodušší konfiguraci založené na delegátech.

Balíček Microsoft.Orleans.TestingHost NuGet obsahuje TestCluster, který můžete použít k vytvoření clusteru v paměti (který se ve výchozím nastavení skládá ze dvou sila) pro testování zrn.

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

Kvůli režii při spouštění clusteru v paměti můžete chtít vytvořit TestCluster a znovu ho použít v několika testovacích případech. Dosáhnete toho například pomocí třídy xUnit nebo sběrných zařízení.

Pokud chcete sdílet TestCluster více testovacích případů, nejprve vytvořte typ zařízení:

using Orleans.TestingHost;

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

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

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

Dále vytvořte zařízení kolekce:

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

V testovacích případech teď můžete znovu použít TestCluster :

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

Po dokončení všech testů a zastavení paměťových silo clusteru xUnit volá Dispose() metodu ClusterFixture typu. TestCluster má také konstruktor, který přijímá TestClusterOptions, který lze použít ke konfiguraci sil v clusteru.

Pokud v silu použijete injektáž závislostí k zpřístupnění služeb pro zrna, můžete použít i tento model:

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

Použít mocky

Orleans umožňuje také mockování mnoha částí systému. V mnoha scénářích je to nejjednodušší způsob, jak testovat zrnka. Tento přístup má omezení (např. ohledně plánování reentrancy a serializace) a může vyžadovat zahrnutí kódu do zrnka používaného pouze jednotkovými testy. TestKitOrleans nabízí alternativní přístup, který omezuje řadu těchto omezení.

Představte si například zrnko, které testujete, komunikuje s jinými zrnky. Chcete-li napodobit tato ostatní zrna, musíte také napodobit GrainFactory člena zrna, které testujete. Ve výchozím nastavení GrainFactory je normální protected vlastnost, ale většina nástrojů pro vytváření mocků vyžaduje, aby vlastnosti byly public a virtual, aby bylo možné provádět napodobování. Prvním krokem je tedy provést GrainFactory obojí, tedy public i virtual:

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

Teď můžete vytvořit svůj objekt mimo prostředí Orleans a pomocí mockování řídit chování 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());
    }
}

Vytvořte zde zkušební objekt WorkerGrain pomocí Moq. To umožňuje přebití chování GrainFactory, aby vracelo mockovaný IJournalGrain. Pak můžete ověřit, že WorkerGrain komunikuje IJournalGrain podle očekávání.