Egységtesztelés a következővel: Orleans

Ez az oktatóanyag bemutatja, hogyan lehet egyesíteni a szemcsék tesztelését annak érdekében, hogy helyesen viselkedjenek. A szemcsék tesztelésének két fő módja van, és a választott módszer a tesztelt funkciók típusától függ. Használja a Microsoft.Orleans.TestingHost NuGet-csomagot tesztsilók létrehozásához a komponenseihez, vagy használjon egy szimuláló keretrendszert, mint például a Moq, hogy a futtatókörnyezet azon részeit modellezze, amelyekkel a komponensei kölcsönhatásba lépnek.

Ez az InProcessTestCluster az ajánlott tesztelési infrastruktúra a Orleans számára. Egyszerűsített, delegáltalapú API-t biztosít a tesztfürtök konfigurálásához, így egyszerűbbé válik a szolgáltatások megosztása a tesztek és a fürt között.

Főbb előnyök

Az InProcessTestClusterTestCluster elsődleges előnye az ergonómia:

  • Delegáltalapú konfiguráció: Silók és ügyfelek konfigurálása beágyazott meghatalmazottak használatával külön konfigurációs osztályok helyett
  • Megosztott szolgáltatáspéldányok: Könnyen megoszthat szimulált szolgáltatásokat, tesztmásolatokat és más példányokat a tesztkód és a siló gazdagépek között
  • Kevesebb sablon: Nincs szükség külön ISiloConfigurator vagy IClientConfigurator osztályok létrehozására
  • Egyszerűbb függőséginjektálás: Szolgáltatások regisztrálása közvetlenül a builder fluent API-ban

InProcessTestCluster és TestCluster alapértelmezés szerint ugyanazt a folyamaton belüli siló gazdagépet használja, így a memóriahasználat és az indítási idő megegyezik. Az TestCluster API úgy lett kialakítva, hogy többfolyamatos forgatókönyveket is támogatjon (éles szimulációhoz), amelyhez az osztályalapú konfigurációs megközelítés szükséges, de alapértelmezés szerint a folyamaton belül fut, ugyanúgy.InProcessTestCluster

Alapszintű használat

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

A tesztfürt konfigurálása

Silók, ügyfelek és szolgáltatások konfigurálására használható InProcessTestClusterBuilder :

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

Lehetőség Típus Alapértelmezett Description
ClusterId string Automatikusan létrehozott Fürtazonosító.
ServiceId string Automatikusan létrehozott Szolgáltatásazonosító.
InitialSilosCount int 1 Kezdetben elindítandó silók száma.
InitializeClientOnDeploy bool true Azt határozza meg, hogy az ügyfél automatikusan inicializálható-e az üzembe helyezéskor.
ConfigureFileLogging bool true Engedélyezze a fájlnaplózást a hibakereséshez.
UseRealEnvironmentStatistics bool false Szimulált értékek helyett valós memória-/CPU-statisztikákat használjon.
GatewayPerSilo bool true Azt jelzi, hogy mindegyik siló rendelkezik-e átjáróval az ügyfélkapcsolatokhoz.

Tesztfürt megosztása tesztek között

A tesztelési teljesítmény javítása érdekében az xUnit-szerelvényekkel egyetlen fürtöt oszthat meg több tesztesetben:

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

Silók hozzáadása és eltávolítása tesztek során

A InProcessTestCluster támogatja a dinamikus silókezelést a fürt viselkedésének tesztelésére.

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

A TestCluster használata

A TestCluster egy osztályalapú konfigurációs megközelítést használ, amely megköveteli a ISiloConfigurator és IClientConfigurator interfészek implementálását. Ez a kialakítás támogatja a többfolyamatos tesztelési forgatókönyveket, ahol a silók külön folyamatokban futnak, ami az éles környezethez hasonló szimulációs teszteléshez hasznos. Alapértelmezés szerint azonban a TestCluster a folyamaton belül is fut, amelynek teljesítménye megegyezik a InProcessTestCluster-ével.

Válassza a TestCluster lehetőséget a InProcessTestCluster helyett, amikor:

  • Többfolyamatú tesztelésre van szükség a gyártási szimulációhoz
  • Meglévő tesztjei vannak, amelyek az TestCluster API-t használják.
  • Kompatibilitásra van szüksége a 7.x vagy a 8.x verzióval Orleans

Az új tesztekhez InProcessTestCluster az egyszerűbb delegáltalapú konfiguráció miatt ajánlott.

A Microsoft.Orleans.TestingHost NuGet-csomag tartalmaz TestClusteregy memórián belüli fürtöt (alapértelmezés szerint két silóból áll) a szemcsék teszteléséhez.

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

Érdemes lehet létrehozni egy memóriabeli fürtöt és TestCluster újra felhasználni több teszteset között, a memóriabeli fürtök elindításának többletterhelése miatt. Ezt például xUnit osztály- vagy gyűjteményszerelvényekkel érheti el.

Ha több teszteset között szeretne megosztani egy TestCluster elemet, először hozzon létre egy fixture típust:

using Orleans.TestingHost;

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

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

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

Ezután hozzon létre egy gyűjtemény készletet.

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

Most már újra felhasználhatja a TestCluster elemet a tesztesetekben.

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

Ha minden teszt befejeződött, és a memóriában lévő fürt silók leállnak, az xUnit meghívja a Dispose()ClusterFixture típusú metódust. TestCluster konstruktor TestClusterOptions is rendelkezik, amely a fürt silóinak konfigurálásához használható.

Ha a Grains számára szolgáltatások biztosítása érdekében függőséginjektálást használ a silóban, akkor ezt a mintát is alkalmazhatja.

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

Mockok használata

Orleans lehetővé teszi a rendszer számos részének imitálását is. Sok esetben ez a legegyszerűbb módszer a szemcsék tesztelésére. Ez a megközelítés korlátozásokkal rendelkezik (például az újrabelépés és a szerializálás ütemezése körül), és előfordulhat, hogy a szemcséknek csak az egységtesztek által használt kódot kell tartalmazniuk. A Orleans TestKit egy alternatív megközelítést kínál, amely számos korlátozást átvesz.

Tegyük fel például, hogy a tesztelt gabona más szemekkel is kommunikál. Ahhoz, hogy a többi objektumot imitálhassa, szimulálni kell a vizsgált objektum GrainFactory tagját. Alapértelmezés szerint a GrainFactory egy normál protected tulajdonság, de a legtöbb mocking keretrendszerben a tulajdonságoknak public és virtual kell lenniük a mockolás engedélyezéséhez. Az első lépés tehát az, hogy GrainFactory mindkettőt public és virtual:

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

Most már a futtatókörnyezeten kívül Orleans is létrehozhatja a grain objektumot, és a GrainFactory viselkedésének szabályozására használhatja a mockolást.

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

Itt, a Moq használatával hozza létre a vizsgált szemcsét, WorkerGrain. Ez lehetővé teszi a GrainFactory viselkedésének felülbírálását, így egy hasonmás IJournalGrain-t ad vissza. Ezután ellenőrizheti, hogy a WorkerGrain várt módon IJournalGrain működik-e.