Как использовать TestKit в Akka.NET

Я пытаюсь протестировать своих аккордов Akka.NET, но испытываю определенные проблемы с TestKit и понимая, как это работает.

Поскольку в Akka.NET пока нет официальной документации для модульного тестирования, я изучил репозиторий Akka.NET, например, код, но используемые здесь примеры не работают.

Те тесты, которые я использовал для справки, ReceiveActorTests.cs и ReceiveActorTests_Become.cs, поскольку эти близки к сценарию, который я пытаюсь проверить в своем приложении.

Вот какой фиктивный код:

Учитывая этот актер

public class Greeter : ReceiveActor
{
    public Greeter()
    {
        NotGreeted();
    }

    private void NotGreeted()
    {
        Receive<Greeting>(msg => Handle(msg));
    }

    private void Greeted()
    {
        Receive<Farewell>(msg => Handle(msg));
    }

    private void Handle(Greeting msg)
    {
        if (msg.Message == "hello")
        {
            Become(Greeted);
        }
    }

    private void Handle(Farewell msg)
    {
        if (msg.Message == "bye bye")
        {
            Become(NotGreeted);
        }
    }
}

Я хочу проверить, что он правильно получает приветствие и прощание, и правильно вводит состояния Become. Глядя на тесты ReceiveActorTests_Become.cs, актер создается

var system = ActorSystem.Create("test");
var actor = system.ActorOf<BecomeActor>("become");

и сообщение отправлено и подтверждено

actor.Tell(message, TestActor);
ExpectMsg(message);

Однако, когда я пытаюсь использовать этот подход для создания экземпляра актера и многих других, основанных на методах TestKit (см. ниже), я продолжаю получать ошибку теста samme:

Xunit.Sdk.TrueExceptionFailed: Timeout 00:00:03 while waiting for a message of type ConsoleApplication1.Greeting 
Expected: True
Actual:   False

Это мой тест:

public class XUnit_GreeterTests : TestKit
{
    [Fact]
    public void BecomesGreeted()
    {
        //var system = ActorSystem.Create("test-system"); // Timeout error
        //var actor = system.ActorOf<Greeter>("greeter"); // Timeout error
        //var actor = ActorOfAsTestActorRef<Greeter>("greeter"); // Timeout error
        //var actor = ActorOf(() => new Greeter(), "greeter"); // Timeout error
        //var actor = Sys.ActorOf<Greeter>("greeter"); // Timeout error
        //var actor = Sys.ActorOf(Props.Create<Greeter>(), "greeter"); // Timeout error
        var actor = CreateTestActor("greeter"); // Works, but doesn't test my Greeter actor, but rather creates a generic TestActor (as I understand it)

        var message = new Greeting("hello");

        actor.Tell(message, TestActor);

        ExpectMsg(message);
    }
}

Я также попытался переместить строку ExpectMsg над линией actor.Tell(поскольку для вас было больше смысла ожидать чего-то до того, как вы начнете действовать, и скорее проверите ожидание после), но это также приводит к ошибке Timeout.

Я пробовал как с NUnit, так и с XUnit TestKits.

Возможно, что-то действительно основное, что я забыл.

Ответ 1

TestKit используется для более поведенческого тестирования, чтобы проверить, работают ли ваши актеры как ожидаемые в контексте всей системы актеров. Это больше похоже на тестирование черного ящика - вы не достигаете внутренних аспектов актера напрямую. Вместо этого лучше сосредоточиться на поведении, таком как данный сигнал A и поведение актера B, он должен передать сообщение C другому игроку D.

В вашем примере проблема с актером Greeter заключается в том, что он отключен - пока он может получать некоторые входы, он ничего не делает в результате. С точки зрения всей системы это могло быть мертвым, и никто не заботился бы.

Используя другой пример - данный следующий актер:

public class Greeter : ReceiveActor
{
    public Greeter()
    {
        Receive<Greet>(greet =>
        {
            // when message arrives, we publish it on the event stream 
            // and send response back to sender
            Context.System.EventStream.Publish(greet.Who + " sends greetings");
            Sender.Tell(new GreetBack(Self.Path.Name));
        });
    }
}

Позвольте создать пример тестовой спецификации:

public class GreeterSpec : TestKit
{
    private IActorRef greeter;

    public GreeterSpec() : base()
    {
        greeter = Sys.ActorOf<Greeter>("TestGreeter");
    }

    [Fact]
    public void Greeter_should_GreetBack_when_Greeted()
    {
        // set test actor as message sender
        greeter.Tell(new Greet("John Snow"), TestActor);
        // ExpectMsg tracks messages received by TestActors
        ExpectMsg<GreetBack>(msg => msg.Who == "TestGreeter");
    }

    [Fact]
    public void Greeter_should_broadcast_incoming_greetings()
    {
        // create test probe and subscribe it to the event bus
        var subscriber = CreateTestProbe();
        Sys.EventStream.Subscribe(subscriber.Ref, typeof (string));

        greeter.Tell(new Greet("John Snow"), TestActor);

        // check if subscriber received a message
        subscriber.ExpectMsg<string>("John Snow sends greetings");
    }
}

Как вы можете видеть, здесь я не проверяю внутреннее состояние актера. Вместо этого я наблюдаю, как он реагирует на сигналы, которые я отправляю на него, и проверяю, является ли это ожидаемым результатом.

Ответ 2

Вы не должны и не должны создавать свой собственный ActorSystem как часть любого теста Akka.TestKit. Вы можете использовать встроенное свойство Sys в любом из ваших тестов, и вы получите доступ к тому же ActorSystem, который используется самим TestKit.

Итак, вы должны сделать что-то вроде этого:

var actor = Sys.ActorOf<BecomeActor>("become");

Причина, по которой это важно: наличие TestActor и вашего BecomeActor в том же ActorSystem необходимо, чтобы они могли отправлять сообщения друг другу, если только вы не используя Akka.Remote. В противном случае TestActor не может получать сообщения, а ваши вызовы ExpectMsg будут отсутствовать.

EDIT: вся тестовая актерская система теперь разрывается между модульными тестами.

ИЗМЕНИТЬ 2: Подробное руководство которое мы написали в TestKit Akka.NET для более подробного объяснения.