本教程演示如何对粒度进行单元测试,以确保它们的行为正确。 有两种主要方法可以对粒度进行单元测试,你选择的方法取决于要测试的功能类型。 使用 Microsoft.Orleans.TestingHost NuGet 包为 grain 创建测试仓,或使用模拟框架(如 Moq)模拟 grain 与之交互的 Orleans 运行时部分。
使用 TestCluster
Microsoft.Orleans.TestingHost
NuGet 包包含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 将调用 Dispose() 类型的 ClusterFixture
方法。
TestCluster
还具有一个构造函数,该构造函数接受 TestClusterOptions,可用于配置集群中的独立单元。
如果在 Silo 中使用依赖注入使服务可用于 Grains,您也可以使用这种模式:
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 还允许对系统的许多部分进行模拟。 对于许多方案,这是单元测试粒度的最简单方法。 此方法有其局限性(例如,涉及到计划的再进入和序列化),并且可能需要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
按预期交互。