Share via


使用 Orleans 的單元測試

本教學課程說明如何對粒紋進行單元測試,以確保運作正常。 有兩種主要方式可對粒紋進行單元測試,您選擇的方法將取決於您正在測試的功能型別。 Microsoft.Orleans.TestingHost NuGet 套件可用來為粒紋建立測試定址接收器,或者您可以使用 Moq 之類的模擬架構,來模擬粒紋與之互動的 Orleans 執行階段部分。

使用 TestCluster

Microsoft.Orleans.TestingHostNuGet 封裝包含的 TestCluster,可用來建立記憶體內部叢集,其預設組成為可用來測試粒紋的兩個定址接收器。

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 屬性,但大部分的模擬架構都要求屬性為 publicvirtual,以便能夠進行模擬。 因此,我們需要做的第一件事,是將 GrainFactory 設為 publicvirtual 屬性:

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,這表示您可以接著覆寫 GrainFactory 的行為,讓其傳回模擬的 IJournalGrain。 然後,您可以驗證 WorkerGrain 如預期與 IJournalGrain 互動。