次の方法で共有


Orleans での単体テスト

このチュートリアルでは、グレインの単体テストを行って、正しく動作することを確認する方法について説明します。 グレインの単体テストを行うには主に 2 つの方法があり、テストする機能の種類に応じてどちらかの方法を選びます。 Microsoft.Orleans.TestingHost NuGet パッケージを使って、グレイン用のテスト サイロを作成したり、Moq などのモック フレームワークを使って、グレインがやり取りする Orleans ランタイムの部分のモックを作成したりすることができます。

TestClusterを使用します

Microsoft.Orleans.TestingHost NuGet パッケージに含まれる TestCluster を使うと、既定では 2 つのサイロで構成されるインメモリ クラスターを作成でき、それをグレインのテストに使用できます。

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

インメモリ クラスターを起動するオーバーヘッドのため、TestCluster を作成し、複数のテスト ケース間でそれを再利用する方が好まれるかもしれません。 たとえば、xUnit のクラスまたはコレクション フィクスチャを使って、これを行うことができます。

複数のテスト ケース間で TestCluster を共有するには、最初にフィクスチャ型を作成します。

using Orleans.TestingHost;

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

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

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

次に、コレクション フィクスチャを作成します。

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

これにより、テスト ケースで 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);
    }
}

すべてのテストが完了して、インメモリ クラスター サイロが停止されると、xUnit により ClusterFixture 型の Dispose() メソッドが呼び出されます。 TestCluster には、クラスター内のサイロの構成に使用できる TestClusterOptions を受け取るコンストラクターもあります。

サービスをグレインで使用できるようにするためにサイロで依存関係の挿入を使っている場合は、次のパターンも使用できます。

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

モックを使用する

また、Orleans ではシステムの多くの部分のモックを作成することもでき、多くのシナリオでは、これはグレインの単体テストを行う最も簡単な方法です。 このアプローチには制限があり (たとえば、再入可能性とシリアル化のスケジュール設定に関するもの)、単体テストでのみ使われるコードをグレインに組み込むことが必要になる場合があります。 Orleans TestKit は、これらの制限の多くを回避する代替アプローチを提供します。

たとえば、テストしているグレインが他のグレインとやり取りするとします。 それらの他のグレインのモックを作成できるようにするには、テスト対象のグレインの GrainFactory メンバーのモックも作成する必要があります。 既定では、GrainFactory は通常の protected プロパティですが、ほとんどのモック フレームワークでは、プロパティのモックを作成できるためには、プロパティが public および virtual である必要があります。 そのため、最初に行う必要があるのは、GrainFactorypublic かつ virtual プロパティにすることです。

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

これで、Orleans ランタイムの外部でグレインを作成し、モックを使って 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());
    }
}

ここでは、Moq を使ってテスト対象のグレイン WorkerGrain を作成します。これは、モック化された IJournalGrain を返すように、GrainFactory の動作をオーバーライドできることを意味します。 その後、WorkerGrainIJournalGrain と期待どおりにやり取りすることを確認できます。