Модульное тестирование частных методов в Акке

Я новичок в Акке, и я пытаюсь Акка на Java. Я хотел бы понять модульное тестирование бизнес-логики в актерах. Я читаю документацию, и единственный пример изолированной бизнес-логики внутри актера:

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 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. Или проверить открытый интерфейс актера, то есть следующего актера LogWriter получил правильное проанализированное сообщение от моего актера LogRowParser

Мои вопросы:

  1. Есть ли недостатки у варианта № 1? Предполагая, что субъекты, общающиеся только через сообщения, инкапсуляция и чистые открытые интерфейсы, менее важны?
  2. В случае, если я пытаюсь использовать опцию № 2, есть ли способ перехватить сообщения, отправленные от актера в тесте downstream (тестирование LogRowParser и ловить в LogWriter)? Я рассмотрел различные примеры на JavaTestKit но все они отлавливают сообщения, которые являются ответами назад отправителю, и ни одно из них не показывает, как перехватить сообщение, отправленное новому субъекту.
  3. Есть ли другой вариант, который мне не хватает?

Спасибо!

UPD: забыл упомянуть, что я также рассматривал варианты как:

  • Перемещение логики из актеров полностью в вспомогательные классы. Это обычная практика с аккой?
  • Powermock... но я пытаюсь избежать этого, если возможен редизайн

2 ответа

Решение

Там действительно нет веских причин, чтобы сделать этот метод частным. Как правило, метод класса становится закрытым, чтобы тот, кто имеет прямую ссылку на экземпляр этого класса, не вызывал этот метод. С экземпляром актера никто не будет иметь прямой ссылки на экземпляр этого класса актера. Все, что вы можете получить для связи с экземпляром этого актерского класса, это ActorRef это легкий прокси-сервер, который позволяет вам общаться только путем отправки сообщений для обработки onReceive через почтовый ящик. ActorRef не раскрывает никакого внутреннего состояния или методов этого класса актера. Это своего рода один из главных пунктов продажи системы актеров. Экземпляр субъекта полностью инкапсулирует свое внутреннее состояние и методы, защищая их от внешнего мира и позволяет только тем внутренним вещам изменяться в ответ на получение сообщений. Вот почему нет необходимости отмечать этот метод как закрытый.

редактировать

Модульное тестирование актера, IMO, всегда должно проходить через receive функциональность. Если у вас есть некоторые внутренние методы, которые затем вызываются обработкой в receiveВы не должны сосредоточиваться на тестировании этих методов в отдельности, а вместо этого убедиться, что пути, которые приводят к их вызову, правильно выполняются с помощью сообщений, которые вы передаете во время тестовых сценариев.

В вашем конкретном примере, parse производит ParsedLog сообщение, которое затем отправляется на logWriter детский актер. Для меня, зная, что parse работает, как ожидалось, означает, что logWriter получил правильное сообщение. Для этого я бы позволил создание ребенка logWriter переопределить, а затем сделать это в тестовом коде и заменить создание актера TestProbe, Затем вы можете использовать expectMsg на этом зонде, чтобы убедиться, что он получил ожидаемый ParsedLog Таким образом, сообщение также проверяет функциональность в parse,

Что касается вашего другого комментария о том, как перевести реальный бизнес для актера в отдельный и более тестируемый класс, а затем вызвать его из актера, некоторые люди делают это, так что это не случайно. Лично я нет, но это только я. Если такой подход работает для вас, я не вижу каких-либо серьезных проблем с ним.

У меня была такая же проблема 3 года назад, когда я имел дело с актерами: лучший подход, который я нашел, состоял в том, чтобы иметь минимальную ответственность за ответную реакцию актера. Актер получит сообщение и выберет метод объекта для вызова или сообщение для отправки или исключение, чтобы бросить и все. Таким образом, будет очень легко смоделировать либо сервисы, вызываемые субъектом, и входные данные для этих сервисов.

Другие вопросы по тегам