繁体   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