[英]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.