本教程介绍如何对grain进行单元测试,以确保它们的行为正确。有两种主要方法可以对grain进行单元测试,您选择的方法取决于您正在测试的功能类型。该Microsoft.Orleans.TestingHost
NuGet包可以用来为您的grain创建测试silo,也可以使用模拟框架(例如Moq),来模拟与你的grain交互的Orleans运行时部分。
Microsoft.Orleans.TestingHost
NuGet包包含TestCluster
,可用于创建一个内存中的集群,默认情况下由两个silo组成,可用于测试grain。
using System;
using System.Threading.Tasks;
using Orleans;
using Orleans.TestingHost;
using Xunit;
namespace Tests
{
public class HelloGrainTests
{
[Fact]
public async Task SaysHelloCorrectly()
{
var cluster = new TestCluster();
cluster.Deploy();
var hello = cluster.GrainFactory.GetGrain(Guid.NewGuid());
var greeting = await hello.SayHello();
cluster.StopAllSilos();
Assert.Equal("Hello, World", greeting);
}
}
}
由于启动内存中集群的开销,您可能希望创建一个TestCluster
,并在多个测试用例中重用它。例如,这可以使用xUnit的类或fixtures集合来完成(有关更多详细信息,请参阅https://xunit.github.io/docs/shared-context.html)。
为了在多个测试用例之间共享一个TestCluster
,首先创建一个fixture类型:
public class ClusterFixture : IDisposable
{
public ClusterFixture()
{
this.Cluster = new TestCluster();
this.Cluster.Deploy();
}
public void Dispose()
{
this.Cluster.StopAllSilos();
}
public TestCluster Cluster { get; private set; }
}
接下来创建一个fixture集合:
[CollectionDefinition(ClusterCollection.Name)]
public class ClusterCollection : ICollectionFixture<ClusterFixture>
{
public const string Name = "ClusterCollection";
}
您现在可以在测试用例中重用一个TestCluster
:
using System;
using System.Threading.Tasks;
using Orleans;
using Xunit;
namespace Tests
{
[Collection(ClusterCollection.Name)]
public class HelloGrainTests
{
private readonly TestCluster _cluster;
public HelloGrainTests(ClusterFixture fixture)
{
_cluster = fixture.Cluster;
}
[Fact]
public async Task SaysHelloCorrectly()
{
var hello = _cluster.GrainFactory.GetGrain(Guid.NewGuid());
var greeting = await hello.SayHell();
Assert.Equal("Hello, World", greeting);
}
}
}
当所有测试都已完成,并且内存中的集群的silo将被停止时,xUnit将调用ClusterFixture
类型的Dispose
方法。TestCluster
还有一个接受TestClusterOptions
的构造函数,可用于配置集群中的silo。
如果您在Silo中使用依赖注入为Grains提供服务,您也可以使用此模式:
public class ClusterFixture : IDisposable
{
public ClusterFixture()
{
var builder = new TestClusterBuilder();
builder.AddSiloBuilderConfigurator();
this.Cluster = builder.Build();
this.Cluster.Deploy();
}
public void Dispose()
{
this.Cluster.StopAllSilos();
}
public TestCluster Cluster { get; private set; }
}
public class TestSiloConfigurations : ISiloBuilderConfigurator {
public void Configure(ISiloHostBuilder hostBuilder) {
hostBuilder.ConfigureServices(services => {
services.AddSingleton(...);
});
}
}
Orleans还可以模拟系统的许多部分,对于许多场景,这是单元测试grain的最简单方法。这种方法确实存在局限性(例如,在调度可重入性和序列化方面),并且可能要求grain包括仅由单元测试使用的代码。Orleans TestKit提供了一种替代方法,它克服了这些限制。
例如,让我们想象一下,我们正在测试的grain与其他grain相互作用。为了能够模拟那些其他grain,我们还需要模拟被测grain的GrainFactory
成员。默认情况下,GrainFactory
是一个普通的protected
属性,但大多数模拟框架都要求属性是public
和virtual
,以便能够模拟它们。因此,我们要做的第一件事,就是让GrainFactory
同时成为public
和virtual
:
public new virtual IGrainFactory GrainFactory
{
get { return base.GrainFactory; }
}
现在我们可以在Orleans运行时之外,创建我们的grain,并使用mocking来控制GrainFactory
的行为:
using System;
using System.Threading.Tasks;
using Orleans;
using Xunit;
using Moq;
namespace Tests
{
public class WorkerGrainTests
{
[Fact]
public async Task RecordsMessageInJournal()
{
var data = "Hello, World";
var journal = new Mock();
var worker = new Mock();
worker
.Setup(x => x.GrainFactory.GetGrain(It.IsAny()))
.Returns(journal.Object);
await worker.DoWork(data)
journal.Verify(x => x.Record(data), Times.Once());
}
}
}
在这里,我们使用Moq,创建我们的测试grain WorkerGrain
,这意味着我们可以覆盖GrainFactory
的行为,以便它返回一个模拟的IJournalGrain
。然后,我们可以验证我们的WorkerGrain
与IJournalGrain
的交互,是否如我们期望的那样。