使用 JUnit 对 akka actors 进行单元测试
Unit testing akka actors with JUnit
最近我尝试为 akka actors 编写一些单元测试来测试 actors 消息流。我在测试中观察到一些奇怪的行为:
字段:
private TestActorRef<Actor> sut;
private ActorSystem system;
JavaTestKit AnotherActor;
JavaTestKit YetAnotherActor;
系统和actor在@Before注解方法中创建:
@Before
public void setup() throws ClassNotFoundException {
system = ActorSystem.apply();
AnotherActor = new JavaTestKit(system);
YetAnotherActor = new JavaTestKit(system);
Props props = MyActor.props(someReference);
this.sut = system.of(props, "MyActor"); }
下一个
@Test
public void shouldDoSth() throws Exception {
// given actor
MyActor actor = (MyActor) sut.underlyingActor();
// when
SomeMessage message = new SomeMessage(Collections.emptyList());
sut.tell(message, AnotherActor.getRef());
// then
YetAnotherActor.expectMsgClass(
FiniteDuration.apply(1, TimeUnit.SECONDS),
YetSomeMessage.class);
}
在我的代码中我有:
private void processMessage(SomeMessage message) {
final List<Entity> entities = message.getEntities();
if(entities.isEmpty()) {
YetAnotherActor.tell(new YetSomeMessage(), getSelf());
// return;
}
if (entities > workers.size()) {
throw new IllegalStateException("too many tasks to be started !");
}
}
基本上,有时(很少)这样的测试会失败(在另一个 OS 上),并且会抛出 processMessage 方法的异常(由于业务逻辑导致的 IllegalStateException)。
大部分测试通过,因为 YetAnotherActor 收到了 YetSomeMessage 消息,尽管 IllegateStateException 错误也被抛出并记录在堆栈跟踪中。
正如我从 akka TestActorRef 文档中假设的那样:
This special ActorRef is exclusively for use during unit testing in a single-threaded environment. Therefore, it
overrides the dispatcher to CallingThreadDispatcher and sets the receiveTimeout to None. Otherwise,
it acts just like a normal ActorRef. You may retrieve a reference to the underlying actor to test internal logic.
我的系统只使用单线程来处理 actor 收到的消息。有人可以解释我为什么尽管断言正确,但测试失败了吗?
当然可以在使用适当的代码发送 YetSomeMessage 后返回,但我不明白另一个线程处理如何导致测试失败。
由于您使用的是TestActorRef,所以您基本上是在进行同步测试。作为一般经验法则,除非确实需要,否则不要使用 TestActorRef。那东西使用 CallingThreadDispatcher,即它会窃取调用者线程来执行 actor。因此,您的谜团的解决方案是演员在与您的测试相同的线程上运行,因此异常最终出现在测试线程上。
幸运的是,您的这个测试用例根本不需要 TestActorRef。您可以将演员创建为普通演员,一切都应该正常(即演员将在适当的单独线程上)。请尽量使用异步测试支持http://doc.akka.io/docs/akka/2.4.0/scala/testing.html#Asynchronous_Integration_Testing_with_TestKit
最近我尝试为 akka actors 编写一些单元测试来测试 actors 消息流。我在测试中观察到一些奇怪的行为:
字段:
private TestActorRef<Actor> sut;
private ActorSystem system;
JavaTestKit AnotherActor;
JavaTestKit YetAnotherActor;
系统和actor在@Before注解方法中创建:
@Before
public void setup() throws ClassNotFoundException {
system = ActorSystem.apply();
AnotherActor = new JavaTestKit(system);
YetAnotherActor = new JavaTestKit(system);
Props props = MyActor.props(someReference);
this.sut = system.of(props, "MyActor"); }
下一个
@Test
public void shouldDoSth() throws Exception {
// given actor
MyActor actor = (MyActor) sut.underlyingActor();
// when
SomeMessage message = new SomeMessage(Collections.emptyList());
sut.tell(message, AnotherActor.getRef());
// then
YetAnotherActor.expectMsgClass(
FiniteDuration.apply(1, TimeUnit.SECONDS),
YetSomeMessage.class);
}
在我的代码中我有:
private void processMessage(SomeMessage message) {
final List<Entity> entities = message.getEntities();
if(entities.isEmpty()) {
YetAnotherActor.tell(new YetSomeMessage(), getSelf());
// return;
}
if (entities > workers.size()) {
throw new IllegalStateException("too many tasks to be started !");
}
}
基本上,有时(很少)这样的测试会失败(在另一个 OS 上),并且会抛出 processMessage 方法的异常(由于业务逻辑导致的 IllegalStateException)。
大部分测试通过,因为 YetAnotherActor 收到了 YetSomeMessage 消息,尽管 IllegateStateException 错误也被抛出并记录在堆栈跟踪中。
正如我从 akka TestActorRef 文档中假设的那样:
This special ActorRef is exclusively for use during unit testing in a single-threaded environment. Therefore, it overrides the dispatcher to CallingThreadDispatcher and sets the receiveTimeout to None. Otherwise, it acts just like a normal ActorRef. You may retrieve a reference to the underlying actor to test internal logic.
我的系统只使用单线程来处理 actor 收到的消息。有人可以解释我为什么尽管断言正确,但测试失败了吗?
当然可以在使用适当的代码发送 YetSomeMessage 后返回,但我不明白另一个线程处理如何导致测试失败。
由于您使用的是TestActorRef,所以您基本上是在进行同步测试。作为一般经验法则,除非确实需要,否则不要使用 TestActorRef。那东西使用 CallingThreadDispatcher,即它会窃取调用者线程来执行 actor。因此,您的谜团的解决方案是演员在与您的测试相同的线程上运行,因此异常最终出现在测试线程上。
幸运的是,您的这个测试用例根本不需要 TestActorRef。您可以将演员创建为普通演员,一切都应该正常(即演员将在适当的单独线程上)。请尽量使用异步测试支持http://doc.akka.io/docs/akka/2.4.0/scala/testing.html#Asynchronous_Integration_Testing_with_TestKit