简体   繁体   English

为什么添加Thread.sleep让我的Akka TestKit单元测试通过?

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

Java 8 and Akka ( Java API ) 2.12:2.5.16 here. Java 8和Akka( Java API )2.12:2.5.16。 I have the following message: 我有以下消息:

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;
    }
}

And the following actor: 以下演员:

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();
    }
}

And the following unit test: 以下单元测试:

@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());
    }
}

So all I'm trying to verify is that TestActor does in fact receive a SomeMessage and that it alters its internal state. 所以我试图验证的是, TestActor确实接收了SomeMessage并且它改变了它的内部状态。

When I run this unit test, it fails and is as if the actor never receives the message: 当我运行这个单元测试时,它失败了,好像演员从未收到过消息:

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'.

But when I modify the test method and introduce a Thread.sleep(5000) into it (after the tell(...) ) it passes with flying colors: 但是当我修改测试方法并将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());
}

What's going on here?! 这里发生了什么?! Obviously I don't want my actor tests littered with sleeps , so what am I doing wrong here and what is the fix? 显然我不希望我的演员测试充满了sleeps ,所以我在这里做错了什么,修复是什么? Thanks in advance! 提前致谢!

@Asier Aranbarri is correct by saying that you don't let you actor to finish its work. @Asier Aranbarri说你不让演员完成它的工作是正确的。

Actors have asynchronous nature and though they don't implement Runnable they are executed separately from the thread that is used to send a message from. Actor具有异步性质,虽然它们不实现Runnable ,但它们与用于发送消息的线程分开执行。

You send a message to an actor and then immediately assert that the message was changed. 您向actor发送消息,然后立即断言消息已更改。 As actor runs in asynchronous context, ie different thread, it still has not processed the incoming message. 由于actor在异步上下文中运行,即不同的线程,它仍然没有处理传入的消息。 Thus putting Threed.sleep allows actor process the message and only after this the assertion is done. 因此,使用Threed.sleep允许actor处理消息,并且仅在此之后断言完成。

I may suggest some changes to your initial design that would marry well with akka nature. 我可能会建议您对初始设计进行一些更改,以便与akka性质结合得很好。

First of all, akka does not suggest using messages with mutability. 首先,akka不建议使用具有可变性的消息。 They must be immutable. 它们必须是不变的。 In you case this is broken with method SomeMessage#setAnotherNum . 在你的情况下,这是通过方法SomeMessage#setAnotherNum Remove it: 去掉它:

public class SomeMessage {
    private int anotherNum;

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

    public int getAnotherNum() {
        return anotherNum;
    }
}

After this create a new instance of SomeMessage instead of changing your incoming message in TestActor and send it back to context.sender() . 在此之后创建SomeMessage的新实例,而不是在TestActor中更改传入消息并将其发送回context.sender() Like defined here 像这里定义的那样

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();
    }
}

Now, instead of changing inner state of a message, a new message is created with new state and the later message is returned back to sender() . 现在,不是更改消息的内部状态,而是使用新状态创建新消息,然后将后面的消息返回给sender() This is the appropriate usage of akka. 这是akka的适当用法。

This allows test to use a TestProbe and be redefined as follows 这允许测试使用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 emulates a sender and captures all incoming message/replies from TestActor . TestProbe模拟发件人并从TestActor捕获所有传入的消息/回复。 Note that expectMsg(new SomeMessage(15)) is used instead of an assert. 请注意,使用expectMsg(new SomeMessage(15))而不是断言。 It has an inner blocking mechanism that waits for message to be received before the assertion is done. 它有一个内部阻塞机制,在断言完成之前等待接收消息。 This is what happens in testing actors example . 这是测试演员示例中发生的事情。

To make expectMsg assert correctly, you must override equals method in your class SomeMessage 要使expectMsg正确断言,必须在类SomeMessage重写equals方法

Edit: 编辑:

Why does Akka frown upon changing SomeMessage's internal state? 为什么Akka在改变SomeMessage的内部状态时皱眉?

One of the powers of akka is that it does not require synchronisation or wait/notify to control access to shared data. akka的一个强大功能是它不需要同步或wait / notify来控制对共享数据的访问。 But this can be achieved only with message immutability. 但这只能通过消息不变性来实现。 Imagine you send a mutable message that you change at the exact time actor processes it. 想象一下,您发送了一条可变消息,您可以在actor处理它的确切时间更改它。 This can cause race conditions. 这可能会导致竞争条件。 Read this for more details. 阅读本文了解更多详情。

And (2) does the same apply to changing Actors' internal state? 并且(2)同样适用于改变演员的内部状态? Is it "OK" for ActorRefs to have properties that can be changed, or does the community frown upon that as well (and if so, why!)? 对于ActorRefs来说,“OK”是否具有可以更改的属性,或者社区是否也对此感到不满(如果是,为什么!)?

No, this does not apply here. 不,这不适用于此。 If any state is incapsulated within an actor and only it can change it , it's completely fine having mutability. 如果任何一个状态被封装在一个actor中, 只有它可以改变它 ,那么它具有可变性就完全没问题了。

I think you don't let the actor do his job. 我想你不要让演员做他的工作。 Maybe the AkkaActor starts his own thread? 也许AkkaActor开始自己的线程? I think Actor does implement Runnable, but I'm not really an expert on Akka. 我认为Actor确实实现了Runnable,但我并不是Akka的专家。 --> edit Actor is an interface, glad I said I was not an expert.. - >编辑Actor是一个界面,很高兴我说我不是专家..

My guess is that by sleeping your main thread, you give time to the Actor's "thread" to finish his method. 我的猜测是,通过睡眠主线程,你可以给Actor的“线程”留出时间来完成他的方法。

I know this may not be helpful, but was way too long to put it in comment. 我知道这可能没有帮助,但是太长时间没有评论。 : ( :(

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM