我很想知道如何测试Akka Actor功能,通过在Actor中模拟一些方法(替换真实对象/ actor的方法实现,通过模拟一个)。
我使用akka.testkit.TestActorRef
;
另外:我尝试使用SpyingProducer
,但目前尚不清楚如何使用它。 (就像我,如果我在其实现中创建了actor,它将与我现在一样)。
谷歌搜索结果不是很verbose。
我使用powemockito
和java
。但是这没关系。我很想知道how to do it in principle
使用任何框架
(所以如果你不知道power / mockito如何工作只是提供你的 代码..(请)或完全了解如何使用您知道的工具。)
所以,假设我们有一个要测试的Actor:
package example.formock;
import akka.actor.UntypedActor;
public class ToBeTestedActor extends UntypedActor {
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof String) {
getSender().tell( getHelloMessage((String) message), getSelf());
}
}
String getHelloMessage(String initMessage) { // this was created for test purposes (for testing mocking/spy capabilities). Look at the test
return "Hello, " + initMessage;
}
}
在我们的测试中,我们希望用getHelloMessage()
替换其他内容。
这是我的尝试:
package example.formock;
import akka.testkit.TestActorRef;
...
@RunWith(PowerMockRunner.class)
@PrepareForTest(ToBeTestedActor.class)
public class ToBeTestedActorTest {
static final Timeout timeout = new Timeout(Duration.create(5, "seconds"));
@Test
public void getHelloMessage() {
final ActorSystem system = ActorSystem.create("system");
// given
final TestActorRef<ToBeTestedActor> actorRef = TestActorRef.create(
system,
Props.create(ToBeTestedActor.class),
"toBeTestedActor");
// First try:
ToBeTestedActor actorSpy = PowerMockito.spy(actorRef.underlyingActor());
// change functionality
PowerMockito.when(actorSpy.getHelloMessage (anyString())).thenReturn("nothing"); // <- expecting result
try {
// when
Future<Object> future = Patterns.ask(actorRef, "Bob", timeout);
// then
assertTrue(future.isCompleted());
// when
String resultMessage = (String) Await.result(future, Duration.Zero());
// then
assertEquals("nothing", resultMessage); // FAIL HERE
} catch (Exception e) {
fail("ops");
}
}
}
结果:
org.junit.ComparisonFailure:
Expected :nothing
Actual :Hello, Bob
答案 0 :(得分:10)
Akka有一个类AutoPilot
,它基本上是演员的一般模拟,具有响应消息和断言消息被发送的能力。
http://doc.akka.io/docs/akka/snapshot/java/testing.html
这是该页面的java示例。您创建一个探测器,设置一个可以响应消息的自动驾驶仪,并从中获取一个ActorRef
,您可以替换为您的真实演员。
new JavaTestKit(system) {{
final JavaTestKit probe = new JavaTestKit(system);
// install auto-pilot
probe.setAutoPilot(new TestActor.AutoPilot() {
public AutoPilot run(ActorRef sender, Object msg) {
sender.tell(msg, ActorRef.noSender());
return noAutoPilot();
}
});
// first one is replied to directly ...
probe.getRef().tell("hello", getRef());
expectMsgEquals("hello");
// ... but then the auto-pilot switched itself off
probe.getRef().tell("world", getRef());
expectNoMsg();
}};
答案 1 :(得分:2)
我没有使用Akka和Java的经验,但我想我在Scala中使用的解决方案也适用于Java。根本不需要嘲笑任何东西。 在Java中,模拟有时对测试有用,但我的个人经验/意见是,无论何时需要PowerMock,你都会做错事。
以下是我尝试使用Akka进行测试的方法:
在Scala中,我使用了一个trait(aka界面),其中定义了actor方法。
trait ToBeTested {
def getHelloMessage(msg: String, replyTarget: ActorRef): String =
replyTarget ! s"Hello $msg"
}
这样,这个功能可以非常容易地进行单元测试。对于真正的演员,我试图坚持只实现接收方法。
class ToBeTestedActor extends Actor with ToBeTested {
def receive: Receive = {
case msg: String => getHelloMessage(msg, sender())
}
}
然后在测试actor时,您可以覆盖getHelloMessage实现来执行任何操作。
class ToBeTestedActorTest extends TestKit(ActorSystem("toBeTested") with .... {
trait MyToBeTested extends ToBeTested {
// do something predictable for testing or defer to a TestProbe which you can
// either define globally in the test class or provide one in a constructor.
override def getHelloMessage(msg: String, replyTarget: ActorRef): String = ???
}
val toBeTestedActor = TestActorRef(Probe(new ToBeTestedActor with MyToBeTested))
// ... (test cases)
}
在Java中,你可以做同样的事情。从Java 8开始,您可以在接口中提供默认方法实现,您可以在子接口中覆盖它们以进行测试。另一种方法是在测试中将actor子类化为覆盖某些方法以提供可预测的行为。
// An easy unit testable interface
public interface ToBeTested {
public ActorRef self();
default public void getHelloMessage(String msg, ActorRef replyTarget) {
replyTarget.tell(String.format("Hello %s", msg), self());
}
}
public class ToBeTestedActor extends UntypedActor implements ToBeTested {
// self() already implemented by Actor class
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof String) {
getHelloMessage((String)message, getSender());
}
}
}
public class ToBeTestedActorTest {
@Test
public void test() throws Exception {
ActorSystem system = ActorSystem.create();
TestActorRef<Actor> testActorRef = TestActorRef.create(system, Props.create(TestActor.class));
Future<Object> response = Patterns.ask(testActorRef, "World", 1000);
assertThat(response.isCompleted(), is(true));
assertThat(Await.result(response, Duration.Zero()), is("Test"));
}
// Override interface when using Java 8
interface DummyToBeTested extends ToBeTested {
@Override
default void getHelloMessage(String msg, ActorRef replyTarget) {
assertThat(msg, is("World"));
replyTarget.tell("Test", self());
}
}
// extend ToBeTestedActor with dummy interface
static class TestActor extends ToBeTestedActor implements DummyToBeTested {}
// Or (pre Java 8) extend the ToBeTestedActor directly
// static class TestActor extends ToBeTestedActor {
// @Override
// public void getHelloMessage(String msg, ActorRef replyTarget) {
// replyTarget.tell("Test", self());
// }
// }
}
答案 2 :(得分:1)
所以我可能不理解这个问题,但你可能不想模仿一个演员,因为模拟的目的是用一个具有调用期望的测试副本替换像dao这样的东西 - 演员不是真的符合条件,因为它是你扩展而不是依赖的东西 - 模拟真的只适用于真正的依赖。
TestActorRef专门为您提供对底层actor的访问权限 - 在大多数正常情况下,您只能向actor发送消息,而不能直接在其上调用任何内容。 TestActoRef通过允许您访问Actor的真正真实扩展而不仅仅是您只能访问的ActorRef来消除此限制!要么 ?反对(发送或询问)。
我是一名scala开发人员,所以洞察力是有希望的。我不知道具体的java api但它应该没关系。
我的建议是通过actor ref获取真正的Actor对象,然后测试方法或找出通过真实消息获得测试覆盖率的方法。
答案 3 :(得分:0)
通过TestActorRef更容易模拟演员。您可以使用以下代码:
static ActorSystem system = ActorSystem.create();
static Props propsSome = Props.create(MockedResultActor.class);
TestActorRef<MockedResultActor> refMockedResultActor= TestActorRef.create(
system, propsSome, "testA");
// Mocking an actor class and returning our reference actor
PowerMockito.mockStatic(ClassToBeMocked.class);
Mockito.when(ClassToBeMocked.getToBeMockedMethod())
.thenReturn(refMockedResultActor);
注意:ClassToBeMocked - 它是一个你想要模拟的类。 MockedResultActor - 它是一个你想要在模拟后返回的类。 这可以在您的类中实现模拟的基本配置后使用JunitTest运行。此处给出的代码仅适用于java中的akka actor。