如何通过模拟其中的一个或多个方法来测试Akka Actor功能

时间:2013-05-23 14:00:14

标签: java scala mocking akka actor

我很想知道如何测试Akka Actor功能,通过在Actor中模拟一些方法替换真实对象/ actor的方法实现,通过模拟一个)。

我使用akka.testkit.TestActorRef;

另外:我尝试使用SpyingProducer,但目前尚不清楚如何使用它。 (就像我,如果我在其实现中创建了actor,它将与我现在一样)。 谷歌搜索结果不是很verbose

我使用powemockitojava。但是这没关系。我很想知道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

4 个答案:

答案 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。