簡體   English   中英

為什么添加Thread.sleep讓我的Akka TestKit單元測試通過?

[英]Why does adding Thread.sleep make my Akka TestKit unit tests pass?

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 ,所以我在這里做錯了什么,修復是什么? 提前致謝!

@Asier Aranbarri說你不讓演員完成它的工作是正確的。

Actor具有異步性質,雖然它們不實現Runnable ,但它們與用於發送消息的線程分開執行。

您向actor發送消息,然后立即斷言消息已更改。 由於actor在異步上下文中運行,即不同的線程,它仍然沒有處理傳入的消息。 因此,使用Threed.sleep允許actor處理消息,並且僅在此之后斷言完成。

我可能會建議您對初始設計進行一些更改,以便與akka性質結合得很好。

首先,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() 這是akka的適當用法。

這允許測試使用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正確斷言,必須在類SomeMessage重寫equals方法

編輯:

為什么Akka在改變SomeMessage的內部狀態時皺眉?

akka的一個強大功能是它不需要同步或wait / notify來控制對共享數據的訪問。 但這只能通過消息不變性來實現。 想象一下,您發送了一條可變消息,您可以在actor處理它的確切時間更改它。 這可能會導致競爭條件。 閱讀本文了解更多詳情。

並且(2)同樣適用於改變演員的內部狀態? 對於ActorRefs來說,“OK”是否具有可以更改的屬性,或者社區是否也對此感到不滿(如果是,為什么!)?

不,這不適用於此。 如果任何一個狀態被封裝在一個actor中, 只有它可以改變它 ,那么它具有可變性就完全沒問題了。

我想你不要讓演員做他的工作。 也許AkkaActor開始自己的線程? 我認為Actor確實實現了Runnable,但我並不是Akka的專家。 - >編輯Actor是一個界面,很高興我說我不是專家..

我的猜測是,通過睡眠主線程,你可以給Actor的“線程”留出時間來完成他的方法。

我知道這可能沒有幫助,但是太長時間沒有評論。 :(

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM