本教學課程將示範如何對您的 grain 進行單元測試,以確保它們能正常運作。 對元件進行單元測試有兩個主要方法,而選擇哪一種方法取決於您要測試的功能類型。 使用 Microsoft。Orleans。TestingHost NuGet 套件可為您的粒子建立測試沙洛,或使用 Moq 之類的模擬框架來模擬您粒子互動的運行時環境部分。
使用 InProcessTestCluster(推薦)
InProcessTestCluster 是推薦的 Orleans 測試基礎設施。 它提供一個簡化、以委托為基礎的 API,用於配置測試叢集,讓測試與叢集間的服務共享更為方便。
主要優勢
相較於 TestCluster,InProcessTestCluster 的主要優點是人體工學。
- 基於委託式配置:使用內嵌代理配置孤島與用戶端,而非獨立的組態類別
- 共享服務實例:輕鬆在測試程式碼與孤島主機間共享模擬服務、測試雙重實例及其他實例
-
減少樣板程式碼:不需要建立獨立的
ISiloConfigurator或IClientConfigurator類別。 - 更簡單的依賴注入:直接在 builder fluent API 中註冊服務
兩者InProcessTestClusterTestCluster預設使用相同的底層進程孤島主機,因此記憶體使用量和啟動時間是等效的。 API TestCluster 也被設計用於支援多程序情境(用於類似生產環境的模擬),這需要基於類別的配置方法,但預設情況下它像 InProcessTestCluster 一樣內部運行。
基本用法
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);
}
}
配置測試叢集
用於 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
| Option | 類型 | 預設 | Description |
|---|---|---|---|
| ClusterId | string |
自動生成 | 叢集識別碼。 |
| ServiceId | string |
自動生成 | 服務識別碼。 |
InitialSilosCount |
int |
1 | 一開始要設置多少個發射井。 |
InitializeClientOnDeploy |
bool |
true |
部署時是否要自動初始化客戶端。 |
ConfigureFileLogging |
bool |
true |
啟用檔案日誌以進行除錯。 |
UseRealEnvironmentStatistics |
bool |
false |
使用真實記憶體/CPU 統計數據,而非模擬值。 |
GatewayPerSilo |
bool |
true |
每個孤島是否都有一個用於用戶端連線的閘道器。 |
在測試間共享測試群組
為了提升測試效能,請使用 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);
}
}
測試時增減筒倉
InProcessTestCluster支援用於測試叢集行為的動態孤島管理:
// 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();
使用 TestCluster
該系統 TestCluster 採用類別為基礎的配置方法,需要實作 ISiloConfigurator 與 IClientConfigurator 介面。 此設計支援多程序測試情境,讓獨立的程序分別運行,對類生產環境的模擬測試非常有幫助。 然而,預設 TestCluster 情況下,也會以與 InProcessTestCluster相當的效能在進程中執行。
當選擇TestCluster而不是InProcessTestCluster時
- 生產模擬需要多流程測試
- 你已經有使用
TestClusterAPI 進行的測試 - 你需要與 7.x 或 8.x 相容Orleans
對於新測試 InProcessTestCluster ,因其代理式配置較簡單而被推薦。
Microsoft.Orleans.TestingHost NuGet 套件包含 TestCluster,可用來建立記憶體中的叢集(預設由兩個 silo 組成),以測試粒子。
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 會呼叫 Dispose() 類型的方法 ClusterFixture 。
TestCluster 也有一個可以接受 TestClusterOptions 的建構函式,您可以用來配置叢集中的節點。
如果您在Silo中使用相依性插入來讓服務可供穀粒使用,您也可以使用這種模式:
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>(/* ... */);
}
}
使用模擬物件
Orleans 也允許模擬許多系統部分。 在許多情況下,這是測試顆粒的單元的最簡單方式。 這種方法有其限制(例如,在排程重入和序列化方面),可能需要讓 grains 包含僅供單元測試使用的程式碼。 Orleans TestKit 提供可避開許多這些限制的替代方法。
例如,假設您測試的顆粒與其他顆粒互動。 若要模擬這些其他穀物,您也需要模擬 GrainFactory 受測穀物的成員。 根據預設, GrainFactory 是一般 protected 屬性,但大部分的模擬架構都需要屬性為 public 和 virtual 才能啟用模擬。 因此,第一個步驟是使 GrainFactory 既是 public 又是 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。 這允許覆寫 GrainFactory 的行為,使其返回一個模擬 IJournalGrain 物件的行為。 然後,您可以確認 WorkerGrain 是否如預期般與 IJournalGrain 互動。