이 자습서에서는 곡물이 올바르게 작동하는지 확인하기 위해 단위 테스트하는 방법을 보여 줍니다. 곡물을 단위 테스트하는 두 가지 주요 방법이 있으며 선택하는 방법은 테스트하는 기능 유형에 따라 달라집니다. MicrosoftOrleans.TestingHost NuGet 패키지를 사용하여 그레인에 대한 테스트 슬로를 만들거나 Moq와 같은 모킹 프레임워크를 사용하여 그레인이 상호 작용하는 런타임의 일부를 모킹합니다.
사용( InProcessTestCluster 권장)
InProcessTestCluster는 Orleans에 권장되는 테스트 인프라입니다. 테스트 클러스터를 구성하기 위한 간소화된 대리자 기반 API를 제공하므로 테스트와 클러스터 간에 서비스를 보다 쉽게 공유할 수 있습니다.
주요 이점
InProcessTestCluster보다 TestCluster의 주요 이점은 인체 공학입니다.
- 대리자 기반 구성: 별도의 구성 클래스 대신 인라인 대리자를 사용하여 사일로 및 클라이언트 구성
- 공유 서비스 인스턴스: 테스트 코드와 사일로 호스트 간에 모의 서비스, 테스트 더블 및 기타 인스턴스를 쉽게 공유
-
상용구 더 적음: 별도의
ISiloConfigurator클래스 또는IClientConfigurator클래스를 만들 필요가 없습니다. - 더 간단한 종속성 주입: 작성기 흐름 API에서 직접 서비스 등록
InProcessTestCluster 둘 다 TestCluster 기본적으로 동일한 기본 내부 프로세스 사일로 호스트를 사용하므로 메모리 사용량과 시작 시간은 동일합니다.
TestCluster API는 클래스 기반 구성 방식을 필요로 하는 다중 프로세스 시나리오(실제와 유사한 시뮬레이션)를 지원하도록 설계되었지만, 기본적으로 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 | 유형 | Default | 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와 동일한 성능으로 In-Process에서도 실행됩니다.
TestCluster을/를 InProcessTestCluster보다 선택할 때:
- 프로덕션 시뮬레이션을 위한 다중 프로세스 테스트가 필요합니다.
- API를 사용하는 기존 테스트가 있습니다
TestCluster. - 7.x 또는 8.x와의 Orleans 호환성이 필요합니다.
새 테스트의 경우 더 간단한 대리자 InProcessTestCluster 기반 구성으로 인해 권장됩니다.
NuGet 패키지에는 Microsoft.Orleans.TestingHost 곡물을 테스트하기 위해 메모리 내 클러스터(기본적으로 두 개의 사일로로 구성됨)를 만드는 데 사용할 수 있는 이 패키지가 포함되어 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을(를) 공유하려면, 먼저 fixture 유형을 만드세요.
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 을(를) 받아들이는 생성자도 있습니다.
사일로에서 종속성 주입을 사용하여 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)
{
// TODO: Call required service registrations here.
// siloBuilder.Services.AddSingleton<T, Impl>(/* ... */);
}
}
모의 개체 사용
Orleans 또한 시스템의 많은 부분을 모의할 수 있습니다. 많은 시나리오에서 이는 단위 테스트 곡물을 수행하는 가장 쉬운 방법입니다. 이 접근 방식에는 한계(예: 재진입성 및 직렬화에 대한 일정 관리)가 있으며, 단위 테스트에서만 사용되는 코드를 포함해야 할 수도 있습니다. TestKit는Orleans 이러한 많은 제한 사항을 회피하는 대체 방법을 제공합니다.
예를 들어 테스트하는 곡물이 다른 곡물과 상호 작용하고 있다고 상상해 보십시오. 다른 그레인과 같은 방법으로, 테스트 중인 그레인의 GrainFactory 멤버를 모의해야 합니다. 기본적으로 GrainFactory는 일반적인 protected 속성이지만, 대부분의 모의 프레임워크에서는 모의 기능을 활성화하기 위해 속성을 public 및 virtual로 설정해야 합니다. 따라서 첫 번째 단계는 GrainFactory를 만들고, public와 virtual를 모두 포함하도록 하는 것입니다.
public new virtual IGrainFactory GrainFactory
{
get => base.GrainFactory;
}
이제 Orleans 런타임 외부에서 그레인(Grain)을 생성하고 모킹을 사용하여 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가 예상대로 상호 작용하는지 확인할 수 있습니다.
.NET