Aracılığıyla paylaş


Birim testi Orleans ile

Bu öğreticide, doğru davrandığından emin olmak için taneciklerinizin birim testini nasıl sağlayacağınız gösterilmektedir. Tahıllarınızı birim test etmenin iki ana yolu vardır ve seçeceğiniz yöntem, test ettiğiniz işlevselliğin türüne bağlıdır. Orleans NuGet paketini kullanarak tanecikleriniz için test siloları oluşturabilir veya taneciğinizin etkileşimde bulunduğu çalışma zamanının parçalarını taklit etmek için Moq gibi bir sahte çerçeve kullanabilirsiniz.

InProcessTestCluster önerilen test altyapısıdır Orleans için. Test kümelerini yapılandırmak için kolaylaştırılmış, temsilci tabanlı bir API sağlayarak testlerinizle küme arasında hizmet paylaşımını kolaylaştırır.

Önemli avantajlar

InProcessTestCluster TestCluster karşısındaki başlıca avantajı ergonomidir:

  • Temsilci tabanlı yapılandırma: Siloları ve istemcileri ayrı yapılandırma sınıfları yerine satır içi temsilciler kullanarak yapılandırma
  • Paylaşılan hizmet örnekleri: Test kodunuz ve silo konakları arasında sahte hizmetleri, test çiftlerini ve diğer örnekleri kolayca paylaşın
  • Daha az gereksiz kod: Ayrı ISiloConfigurator veya IClientConfigurator sınıflar oluşturmanıza gerek yoktur
  • Daha basit bağımlılık ekleme: Hizmetleri doğrudan oluşturucu akıcı API'sine kaydetme

Hem InProcessTestCluster hem de TestCluster, varsayılan olarak aynı işlem içi silo konağını kullandıklarından bellek kullanımı ve başlangıç süresi eşdeğerdir. TestCluster API, sınıf tabanlı yapılandırma yaklaşımı gerektiren ancak varsayılan olarak aynı gibi InProcessTestClusterişlem içinde çalışan çok işlemli senaryoları (üretim benzeri simülasyon için) destekleyecek şekilde tasarlanmıştır.

Temel kullanım

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

Test kümesini yapılandırma

Siloları, istemcileri ve hizmetleri yapılandırmak için kullanın 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();

İşlemİçiTestKümeSeçenekleri

Seçenek Türü Varsayılan Description
ClusterId string Otomatik oluşturulan Küme tanımlayıcısı.
ServiceId string Otomatik oluşturulan Hizmet tanımlayıcısı.
InitialSilosCount int 1 Başlangıçta başlatılacak silo sayısı.
InitializeClientOnDeploy bool true Dağıtımda istemcinin otomatik olarak başlatılıp başlatılmayacağı.
ConfigureFileLogging bool true Hata ayıklama için dosya günlüğünü etkinleştirin.
UseRealEnvironmentStatistics bool false Simülasyon değerleri yerine gerçek bellek/CPU istatistikleri kullanın.
GatewayPerSilo bool true Her silo, istemci bağlantıları için bir ağ geçidi barındırıp barındırmadığı.

Testler arasında test kümesi paylaşma

Test performansını geliştirmek için xUnit fikstürlerini kullanarak birden çok test çalışması arasında tek bir küme paylaşın:

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

Testler sırasında silo ekleme ve kaldırma

, InProcessTestCluster küme davranışını test etme için dinamik silo yönetimini destekler:

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

Kullanın TestCluster

, TestCluster uygulama ISiloConfigurator ve IClientConfigurator arabirimler gerektiren sınıf tabanlı bir yapılandırma yaklaşımı kullanır. Bu tasarım, siloların ayrı işlemlerde çalıştığı çok işlemli test senaryolarını destekler ve bu durum üretim benzeri benzetim testi için kullanışlıdır. Ancak, varsayılan olarak TestCluster ile eşdeğer performansa sahip olarak aynı işlemde de çalışır.

Şu durumlarda InProcessTestCluster yerine TestCluster seçin:

  • Üretim simülasyonu için çok işlemli teste ihtiyacınız var
  • API'yi TestCluster kullanan mevcut testleriniz var
  • 7.x veya 8.x ile Orleans uyumlu olmanız gerekir

Yeni testler için, InProcessTestCluster daha basit temsilci tabanlı yapılandırması nedeniyle önerilir.

Microsoft.Orleans.TestingHost NuGet paketi, taneleri test etmek için kullanabileceğiniz, bellek içi bir küme (varsayılan olarak iki silodan oluşan) oluşturmanızı sağlayan TestCluster öğesini içerir.

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

Bellek içi küme başlatmanın ek yükünden dolayı, bir TestCluster oluşturup birden çok test çalışması arasında yeniden kullanmak isteyebilirsiniz. Örneğin, xUnit'in sınıfını veya koleksiyon fikstürlerini kullanarak bunu gerçekleştirin.

Birden fazla test durumu arasında bir TestCluster paylaşmak için öncelikle bir fikstür türü oluşturun.

using Orleans.TestingHost;

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

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

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

Ardından, bir koleksiyon düzenek oluşturun.

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

Test senaryolarınızda artık bir TestCluster yeniden kullanabilirsiniz.

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

Tüm testler tamamlandığında ve bellek içi küme siloları durduğunda, xUnit Dispose() yöntemini ClusterFixture türün çağırır. TestCluster ayrıca, kümedeki siloları yapılandırmak için kullanabileceğinizi kabul eden TestClusterOptions bir oluşturucuya sahiptir.

Hizmetleri Grain'ler tarafından kullanılabilir hale getirmek için Silo'nuzda Bağımlılık Enjeksiyonu'nu kullanıyorsanız, bu yöntemi de kullanabilirsiniz.

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

Mockları kullan

Orleans ayrıca sistemin birçok parçasının taklit edilmesini sağlar. Birçok senaryoda bu, test tanelerini birleştirmenin en kolay yoludur. Bu yaklaşımın sınırlamaları vardır (örneğin, yeniden giriş ve serileştirme ile ilgili zamanlama) ve yalnızca birim testleriniz tarafından kullanılan kodu eklemek için grain'ler gerekebilir. Orleans TestKit, bu sınırlamaların çoğunu engelleyen alternatif bir yaklaşım sağlar.

Örneğin, test ettiğiniz tahılın diğer tahıllarla etkileşime geçtiğini düşünün. Diğer tahılları taklit etmek için, test edilen tahılın GrainFactory üyesini de taklit etmeniz gerekir. Varsayılan olarak, GrainFactory normal protected bir özelliktir, ancak çoğu sahteçilik çerçevesinde sahte işlemi etkinleştirmek için özelliklerin public ve virtual olması gerekir. Bu nedenle, ilk adım GrainFactory hem public hem de virtual yapmaktır:

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

Artık tahılınızı çalışma zamanının Orleans dışında oluşturabilir ve davranışını GrainFactorydenetlemek için sahtesini kullanabilirsiniz:

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

Burada, Moq kullanarak testin WorkerGrainaltındaki dilimi oluşturun. Bu, GrainFactory'nin davranışını geçersiz kılarak, taklit edilmiş bir IJournalGrain döndürmesine izin verir. Ardından, WorkerGrain'ın IJournalGrain ile beklendiği gibi etkileşimde bulunduğunu doğrulayabilirsiniz.