12

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

我用akka.testkit.TestActorRef;

另外:我尝试使用SpyingProducer但不清楚如何使用它。(就像我在它的实现中创建actor一样,我现在拥有的是一样的)。关于那个的谷歌搜索结果不是很冗长

我使用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

4 回答 4

10

Akka has a class AutoPilot that is basically a general mock for actors, with the ability to respond to messages and assert that messages were sent. http://doc.akka.io/docs/akka/snapshot/java/testing.html

Here's the java example for that page. You create a probe, set an auto-pilot that can respond to messages, and get an ActorRef from it that you can substitute in for your real actor.

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();
}};
于 2013-10-29T16:04:54.800 回答
2

我没有在 Java 中使用 Akka 的经验,但我想我在 Scala 中使用的解决方案也适用于 Java。根本不需要嘲笑任何东西。在 Java 中,模拟有时对测试很有用,但我个人的经验/意见是,每当你需要 PowerMock 时,你就做错了。

以下是我尝试使用 Akka 进行测试的方法:

在 Scala 中,我使用了一个 trait(又名接口),其中定义了 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())
  }
}

然后在测试参与者时,您可以覆盖 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 开始,您可以在接口中提供默认方法实现,您可以在子接口中覆盖这些实现以进行测试。另一种方法是在您的测试中对参与者进行子类化以覆盖某些方法以提供可预测的行为。

// 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());
  //        }
  //    }
}
于 2015-04-02T06:29:29.583 回答
1

所以我可能不理解这个问题,但你可能不想嘲笑一个演员,因为嘲笑的目的是用一个期望调用的测试副本替换像 dao 这样的东西——演员并不真正符合要求因为它是你扩展而不是依赖的东西——模拟真的只适用于真正的依赖。

TestActorRef 专门为您提供了对底层参与者的访问权限——在大多数正常情况下,您只能向参与者发送消息,而不能直接在其上调用任何内容。TestActoRef 允许您访问 Actor 的真正扩展,而不仅仅是您只能访问的 ActorRef ,从而消除了这个限制!或者 ?反对(发送或询问)。

我是一个 scala 开发人员,所以这种洞察力希望是不可知的。我不知道具体的java api,但这没关系。

我的建议是通过 actor ref 获取真正的 Actor 对象,然后测试该方法或找出某种方法来通过真实消息获得测试覆盖率。

于 2013-05-24T02:24:25.410 回答
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。

于 2015-10-12T06:00:35.027 回答