在 Akka 中对私有方法进行单元测试

Unit testing private methods in Akka

我是 akka 的新手,我正在 java 上试用 akka。我想了解演员内业务逻辑的单元测试。我读过 documentation 并且 actor 中孤立的业务逻辑的唯一示例是:

static class MyActor extends UntypedActor {
  public void onReceive(Object o) throws Exception {
    if (o.equals("say42")) {
      getSender().tell(42, getSelf());
    } else if (o instanceof Exception) {
      throw (Exception) o;
    }
  }
  public boolean testMe() { return true; }
}

@Test
public void demonstrateTestActorRef() {
  final Props props = Props.create(MyActor.class);
  final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "testA");
  final MyActor actor = ref.underlyingActor();
  assertTrue(actor.testMe());
}

虽然这很简单,但它暗示我要测试的方法是public。然而,考虑到演员应该只通过消息进行交流,我的理解是没有理由拥有 public 方法,所以我将我的方法设为私有。就像下面的例子:

public class LogRowParser extends AbstractActor {
    private final Logger logger = LoggerFactory.getLogger(LogRowParser.class);

    public LogRowParser() {
        receive(ReceiveBuilder.
                        match(LogRow.class, lr -> {                                
                            ParsedLog log = parse(lr.rowText);
                            final ActorRef logWriter = getContext().actorOf(Props.create(LogWriter.class));
                            logWriter.tell(log, self());
                        }).
                        matchAny(o -> logger.info("Unknown message")).build()
        );
    }

    private ParsedLog parse(String rowText) {
        // Log parsing logic
    }
}

所以要测试方法 parse 我要么:

  1. 需要它来使包私有
  2. 或者测试 actor 的 public 接口,即下一个 actor LogWriter 从我的 actor LogRowParser
  3. 收到正确的解析消息

我的问题:

  1. 选项 #1 有什么缺点吗?假设参与者仅通过消息进行通信,那么封装和干净的开放接口就不那么重要了?
  2. 万一我尝试使用选项 #2,有没有办法在测试下游(测试 LogRowParser 并在 LogWriter 中捕获从 actor 发送的消息?我查看了 JavaTestKit 上的各种示例,但所有示例都在捕获响应发件人的消息,none 将显示如何拦截发送给新参与者的消息。
  3. 我还缺少其他选项吗?

谢谢!

更新: 忘了说我也考虑过像这样的选项:

3 年前,我在与演员打交道时遇到了同样的问题:我发现最好的方法是对演员的信息传递责任承担最低责任。 参与者将接收消息并选择要调用的对象方法或要发送的消息或要抛出的异常,仅此而已。 这样,模拟参与者调用的服务和这些服务的输入将非常简单。

确实没有充分的理由将该方法设为私有。人们通常将 class 上的方法设为私有,以防止直接引用该 class 实例的人调用该方法。对于 actor 实例,没有人会直接引用该 actor 的实例 class。您可以与该 actor class 的实例进行通信的是一个 ActorRef,它是一个轻量级代理,只允许您通过发送由 onReceive 处理的消息来进行通信邮箱。 ActorRef 不会公开该参与者 class 的任何内部状态或方法。这是演员系统的一大卖点。一个 actor 实例完全封装了它的内部状态和方法,保护它们不受外界影响,并且只允许这些内部事物响应接收消息而改变。这就是为什么似乎没有必要将该方法标记为私有的原因。

编辑

演员的单元测试,IMO,应该始终通过 receive 功能。如果您有一些内部方法随后由 receive 中的处理程序调用,您不应该专注于孤立地测试这些方法,而是确保通过您发送的消息正确执行导致它们调用的路径在测试场景中通过。

在您的特定示例中,parse 正在生成 ParsedLog 消息,然后将其发送给 logWriter 儿童演员。对我来说,知道 parse 按预期工作意味着断言 logWriter 收到了正确的消息。为了做到这一点,我将允许覆盖子 logWriter 的创建,然后在测试代码中这样做,并用 TestProbe 替换 actor 创建。然后,您可以在该探测器上使用 expectMsg 以确保它收到预期的 ParsedLog 消息,从而也测试 parse.

中的功能

就您关于将 actor 的实际业务转移到一个单独的、更可测试的 class 然后从 actor 中调用它的其他评论而言,有些人这样做,所以这并非闻所未闻.我个人不喜欢,但这就是我。如果这种方法适合您,我看不出有任何重大问题。