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
乱七八糟的问题,所以我在这里做错了什么?解决方法是什么?预先感谢!
答案 0 :(得分:2)
@Asier Aranbarri是正确的,说您不让演员完成其工作。
Actor具有异步性质,尽管它们未实现Runnable
,但它们是与用于发送消息的线程分开执行的。
您将消息发送给演员,然后立即断言该消息已更改。由于actor在异步上下文(即不同的线程)中运行,因此它仍未处理传入的消息。因此,放置Threed.sleep
允许参与者处理消息,并且只有在此之后才声明。
我可能会建议您对初始设计进行一些更改,以使其与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))
代替了断言。它具有内部阻止机制,可以在声明完成之前等待消息被接收。这就是testing actors example中发生的事情。
要使expectMsg
正确断言,必须在类equals
中覆盖SomeMessage
方法
编辑:
为什么Akka在更改SomeMessage的内部状态时会皱眉?
akka的功能之一是它不需要同步或等待/通知来控制对共享数据的访问。但这只能通过消息不变性来实现。想象一下,您发送了一个可变消息,您在actor处理该消息的确切时间进行了更改。这可能会导致比赛条件。阅读this了解更多详情。
并且(2)是否同样适用于更改Actor的内部状态? ActorRef拥有可以更改的属性是否“可行”,或者社区对此也不满意(如果可以,为什么!)?
否,这不适用于此处。如果任何状态都封装在一个actor中并且只有它可以更改它,那么具有可变性就可以了。
答案 1 :(得分:1)
我认为你不让演员做他的工作。也许AkkaActor
开始了自己的线程?我认为Actor
确实实现了Runnable,但是我并不是Akka的专家。 ->编辑Actor是一个界面,很高兴我说我不是专家。
我的猜测是,通过休眠主线程,您可以花时间给Actor的“线程”完成他的方法。
我知道这可能无济于事,但评论太久了。 :(