Почему при добавлении Thread.sleep мои модульные тесты Akka TestKit проходят успешно? - PullRequest
0 голосов
/ 30 января 2019

Java 8 и Akka ( Java API ) 2.12: 2.5.16 здесь.У меня есть следующее сообщение:

public class SomeMessage {
    private int anotherNum;

    public SomeMessage(int anotherNum) {
        this.anotherNum = anotherNum;
    }

    public int getAnotherNum() {
        return anotherNum;
    }

    public void setAnotherNum(int anotherNum) {
        this.anotherNum = anotherNum;
    }
}

И следующий актер:

public class TestActor extends AbstractActor {
    private Integer number;

    public TestActor(Integer number) {
        this.number = number;
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .matchAny(message -> {
                if (message instanceof SomeMessage) {
                    SomeMessage someMessage = (SomeMessage) message;
                    System.out.println("someMessage contains = " + someMessage.getAnotherNum());
                    someMessage.setAnotherNum(number);
                }
            }).build();
    }
}

И следующий юнит-тест:

@RunWith(MockitoJUnitRunner.class)
public class TestActorTest {
    static ActorSystem actorSystem;

    @BeforeClass
    public static void setup() {
        actorSystem = ActorSystem.create();
    }

    @AfterClass
    public static void teardown() {
        TestKit.shutdownActorSystem(actorSystem, Duration.create("10 seconds"), true);
        actorSystem = null;
    }

    @Test
    public void should_alter_some_message() {
        // given
        ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class, 10), "test.actor");
        SomeMessage someMessage = new SomeMessage(5);

        // when
        testActor.tell(someMessage, ActorRef.noSender());

        // then
        assertEquals(10, someMessage.getAnotherNum());
    }
}

Так что все, что я пытаюсьнеобходимо убедиться, что TestActor действительно получает SomeMessage и что оно меняет свое внутреннее состояние.

Когда я запускаю этот модульный тест, он не проходит и создается впечатление, что субъект никогда не получает сообщение:

java.lang.AssertionError: 
Expected :10
Actual   :5
 <Click to see difference>

    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.junit.Assert.assertEquals(Assert.java:645)
  <rest of trace omitted for brevity>

[INFO] [01/30/2019 12:50:26.780] [default-akka.actor.default-dispatcher-2] [akka://default/user/test.actor] Message [myapp.actors.core.SomeMessage] without sender to Actor[akka://default/user/test.actor#2008219661] was not delivered. [1] dead letters encountered. If this is not an expected behavior, then [Actor[akka://default/user/test.actor#2008219661]] may have terminated unexpectedly, This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

Но когда я модифицирую метод испытания и ввожу в него Thread.sleep(5000) (после tell(...)), он проходит с летающими цветами:

@Test
public void should_alter_some_message() throws InterruptedException {
    // given
    ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class, null, 10), "test.actor");
    SomeMessage someMessage = new SomeMessage(5);

    // when
    testActor.tell(someMessage, ActorRef.noSender());

    Thread.sleep(5000);

    // then
    assertEquals(10, someMessage.getAnotherNum());
}

Что происходитздесь?! Очевидно, я не хочу, чтобы мои актерские тесты были засорены sleeps, так что я здесь не так делаю и в чём дело?Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 30 января 2019

@ Асьер Аранбарри прав, говоря, что вы не позволите актеру закончить его работу.

Актеры имеют асинхронную природу и, хотя они не реализуют Runnable, они выполняются отдельно от потока, из которого отправляется сообщение.

Вы отправляете сообщение актеру, а затем сразу утверждаете, что сообщение было изменено.Поскольку субъект работает в асинхронном контексте, то есть в другом потоке, он все еще не обработал входящее сообщение.Таким образом, установка Threed.sleep позволяет субъекту обрабатывать сообщение, и только после этого выполняется утверждение.

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

Прежде всего, akka не предлагает использовать сообщения с изменчивостью.Они должны быть неизменными.В вашем случае это нарушается методом SomeMessage#setAnotherNum.Удалите его:

public class SomeMessage {
    private int anotherNum;

    public SomeMessage(int anotherNum) {
        this.anotherNum = anotherNum;
    }

    public int getAnotherNum() {
        return anotherNum;
    }
}

После этого создайте новый экземпляр SomeMessage вместо изменения входящего сообщения в TestActor и отправьте его обратно на context.sender().Как определено здесь

static public class TestActor extends AbstractActor {
    private Integer number;

    public TestActor(Integer number) {
        this.number = number;
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .matchAny(message -> {
                    if (message instanceof SomeMessage) {
                        SomeMessage someMessage = (SomeMessage) message;
                        System.out.println("someMessage contains = " + someMessage.getAnotherNum());
                        context().sender().tell(new SomeMessage(number + someMessage.getAnotherNum()), context().self());
                    }
                }).build();
    }
}

Теперь вместо изменения внутреннего состояния сообщения создается новое сообщение с новым состоянием, а более позднее сообщение возвращается обратно к sender().Это правильное использование Акки.

Это позволяет тесту использовать TestProbe и быть переопределенным следующим образом:

@Test
public void should_alter_some_message() {
    // given
    ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class,10));
    TestJavaActor.SomeMessage someMessage = new SomeMessage(5);
    TestProbe testProbe = TestProbe.apply(actorSystem);

    // when
    testActor.tell(someMessage, testProbe.ref());

    // then
    testProbe.expectMsg(new SomeMessage(15));
}

TestProbe эмулирует отправителя и захватывает все входящие сообщения / ответы от TestActor.Обратите внимание, что expectMsg(new SomeMessage(15)) используется вместо утверждения.Он имеет механизм внутренней блокировки, который ожидает получения сообщения, прежде чем будет выполнено утверждение.Это то, что происходит в примере с актерами тестирования .

Чтобы сделать правильное утверждение expectMsg, вы должны переопределить equals метод в своем классе SomeMessage

Редактировать:

Почему Акка хмурится при изменении внутреннего состояния SomeMessage?

Одна из возможностей Акки заключается в том, что она не требует синхронизации или ожидания / уведомления для контроля доступа к общему ресурсу.данные.Но это может быть достигнуто только с неизменяемостью сообщения.Представьте, что вы отправляете изменяемое сообщение, которое вы изменяете в то время, когда субъект его обрабатывает.Это может вызвать условия гонки.Прочитайте это для более подробной информации.

И (2) относится ли это к изменению внутреннего состояния актеров?Разве это нормально для ActorRefs иметь свойства, которые можно изменять, или сообщество тоже не одобряет это (и если да, то почему!)?

Нет, здесь это не касается.Если какое-либо состояние инкапсулировано в субъекте и только оно может изменить его , вполне нормально иметь изменчивость.

0 голосов
/ 30 января 2019

Я думаю, вы не позволяете актеру выполнять свою работу.Может быть, AkkaActor запускает собственную темуЯ думаю, что Actor реализует Runnable, но я не совсем эксперт по Akka.-> edit Actor - это интерфейс, рад, что я сказал, что я не эксперт.

Я предполагаю, что, спя ваш основной поток, вы даете время "потоку" Актера, чтобы закончить его метод.

Я знаю, что это может быть бесполезно, но было слишком долго, чтобы оставить это в комментарии.: (

...