Поделиться через


Модульное тестирование с помощью Orleans

В этом руководстве показано, как модульное тестирование зерна, чтобы убедиться, что они работают правильно. Существует два основных способа модульного тестирования зерна, и выбранный метод будет зависеть от типа функциональности, которую вы тестируете. Microsoft .Orleans. Пакет NuGet TestHost можно использовать для создания тестовых силосов для зерна или использовать макетную платформу, например Moq , чтобы макетировать части 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 также позволяет издеваться над многими частями системы, и во многих сценариях это самый простой способ модульных тестов. Этот подход имеет ограничения (например, вокруг планирования повторного входа и сериализации) и может потребоваться, чтобы зерна включали код, используемый только модульными тестами. 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());
    }
}

Здесь вы создаете тестируемое зерно, WorkerGrainиспользуя Moq, что означает, что вы можете переопределить поведение GrainFactory так, чтобы он возвращал IJournalGrainмакет. Затем можно убедиться, что WorkerGrain взаимодействие с IJournalGrain ожидаемым.