简体   繁体   English

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

[英]How to test Akka Actor functionality by mocking one or more methods in it

I'm interested to know about how to test Akka Actor functionality, by mocking some methods ( substitute real object's/actor's method implementation by mocked one ) in Actor. 我很想知道如何通过在Actor中模拟一些方法用模拟的 替换真实对象/ actor的方法实现 )来测试Akka Actor功能。

I use akka.testkit.TestActorRef ; 我用akka.testkit.TestActorRef ;

Also: I tried to use SpyingProducer but it is not clear how to use it. 另外:我试图使用SpyingProducer但目前尚不清楚如何使用它。 (like I if I created actor inside its implementation it would be the same I have now). (就像我,如果我在其实现中创建了actor,它将与我现在一样)。 The google search result about that is not very verbose . 谷歌搜索结果不是很详细

I use powemockito and java . 我使用powemockitojava But It does not matter. 但是这没关系。 I would be interested to know how to do it in principle with any language with any framework 我很想知道how to do it in principle 任何语言框架下使用任何语言

(so if you do not know how power/mockito works just provide your code.. (please) or complete idea about how you would do it with your tools you know.) (所以如果你不知道power / mockito如何工作只是提供你的代码..(请)或完全了解你将如何使用你知道的工具。)

So, let's say we have an Actor to test: 所以,假设我们有一个要测试的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;
    }

}

And in our test we want to substitute getHelloMessage() returning something else. 在我们的测试中,我们想要替换getHelloMessage()返回其他内容。

This is my try: 这是我的尝试:

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");
        }
    }
}

Result: 结果:

org.junit.ComparisonFailure: 
Expected :nothing
Actual   :Hello, Bob

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. Akka有一个类AutoPilot ,它基本上是演员的一般模拟,具有响应消息和断言消息被发送的能力。 http://doc.akka.io/docs/akka/snapshot/java/testing.html http://doc.akka.io/docs/akka/snapshot/java/testing.html

Here's the java example for that page. 这是该页面的java示例。 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. 您可以创建一个探针,设置一个可以响应消息的自动导航,并从中获取一个ActorRef ,您可以替换为真正的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();
}};

I have no experience in using Akka with Java, but I guess the solution for this I use in Scala can also apply to Java. 我没有使用Akka和Java的经验,但我想我在Scala中使用的解决方案也适用于Java。 There is no need at all to mock anything. 根本不需要嘲笑任何东西。 In Java mocking is sometimes useful for testing, but my personal experience/opinion is that whenever you need PowerMock you're doing something wrong. 在Java中,模拟有时对测试有用,但我个人的经验/意见是,无论何时需要PowerMock,你都会做错事。

Here's how I try to test using Akka: 以下是我尝试使用Akka进行测试的方法:

In Scala I use a trait (aka interface) in which the actor methods are defined. 在Scala中,我使用了一个trait(aka接口),其中定义了actor方法。

trait ToBeTested {
  def getHelloMessage(msg: String, replyTarget: ActorRef): String = 
      replyTarget ! s"Hello $msg"
}

This way, this functionality can be unit tested very easy. 这样,此功能可以非常容易地进行单元测试。 For the real actor I try to stick to implement the receive method only. 对于真正的演员,我试图坚持只实现接收方法。

class ToBeTestedActor extends Actor with ToBeTested {
  def receive: Receive = {
    case msg: String => getHelloMessage(msg, sender())
  }
}

Then when testing the actor, you can override the getHelloMessage implementation to do whatever you want. 然后在测试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)
}

In Java you can do pretty much the same thing. 在Java中,你可以做同样的事情。 Since Java 8 you can provide default method implementations in interfaces, which you can override in a sub-interface for testing. 从Java 8开始,您可以在接口中提供默认方法实现,您可以在子接口中覆盖它们以进行测试。 Another way would be to subclass the actor in your test to override some methods to provide predictable behaviour. 另一种方法是在测试中将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());
  //        }
  //    }
}

So I'm probably not understanding the question but you probably don't want to mock an actor as the purpose of mocking is to replace something like a dao with a test copy that has expectations of invocation - actor doesn't really fit the bill as it's something you extend rather than a dependency - mocking really only applies to real dependencies. 所以我可能不理解这个问题,但是你可能不想模仿一个演员,因为模拟的目的是用一个具有调用期望的测试副本替换像dao这样的东西 - 演员并不真正适合这个法案因为它是你扩展而不是依赖的东西 - 模拟真的只适用于真正的依赖。

TestActorRef specifically gives you access to the underlying actor - in most normal circumstances you can only send messages to an actor and not invoke anything directly on it. TestActorRef专门为您提供对底层actor的访问权限 - 在大多数正常情况下,您只能向actor发送消息而不直接在其上调用任何内容。 TestActoRef removes this limitation by allowing you to access your real true extension of Actor instead of just the ActorRef that you can only ! TestActoRef通过允许您访问Actor的真正真实扩展而不仅仅是您只能访问的ActorRef来消除此限制! or ? 要么 ? against (send or ask). 反对(发送或询问)。

I'm a scala dev so the insight is hopefully agnostic. 我是一名scala开发者,所以洞察力是有希望的。 I don't know the java api specifically but it shouldn't matter. 我不知道具体的java api但它应该没关系。

My recommendation is to get the real Actor object via actor ref and just test the method or figure out some way to get test coverage through real messages. 我的建议是通过actor ref获取真正的Actor对象,然后测试方法或找出通过真实消息获得测试覆盖率的方法。

To mock an actor is easier through the TestActorRef. 通过TestActorRef可以更轻松地模拟actor。 You can use this code : 您可以使用此代码:

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);

Note: ClassToBeMocked--Its a class you want to mock. 注意:ClassToBeMocked - 它是一个你想要模拟的类。 MockedResultActor -- Its a class you want to return after mocking. MockedResultActor - 它是一个你想要在模拟后返回的类。 This can be run using JunitTest after implementing basic configuration of mocking in your class. 这可以在您的类中实现模拟的基本配置后使用JunitTest运行。 Code given here is specific to akka actor in java only. 此处给出的代码仅适用于java中的akka​​ actor。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM