Тестирование актеров, создающих дочерних актеров

#scala #unit-testing #akka

#scala #модульное тестирование #akka

Вопрос:

Например, у меня есть два актера — родительский актер и дочерний актер. Когда родительский элемент получает сообщение, он порождает столько дочерних актеров, сколько указано в сообщении. Как я могу протестировать эту функциональность? Есть ли способ издеваться над контекстом или каким-либо другим способом проверить, что актеры созданы правильно и в нужном количестве?

 class ParentActor extends Actor {
  case class CreateChildren(count: Int)

  override def receive: Receive = {
    case CreateChildren(count) => for (_ <- 0 until count) context.actorOf(Props[ChildActor])
  }
}

class ChildActor extends Actor {
  override def receive: Receive = {
    case _ =>
  }
}
  

Обновлено: решение основано на ответе @Tim

Измененные классы:

 class ParentActor(childActorFactory: ChildActorFactory) extends Actor {
  override def receive: Receive = {
    case CreateChildren(count) => for (_ <- 0 until count) childActorFactory.create(context)
  }
}

object ParentActor {
  def props(childActorFactory: ChildActorFactory): Props = Props(new ParentActor(childActorFactory))
}

class ChildActorFactory {
  def create(context: ActorContext): ActorRef = context.actorOf(Props[ChildActor])
}
  

Тест:

 "ParentActor" should {
    "instantiate ten child actors" in {
      val childrenCount = 10
      val childActorFactory = mock[ChildActorFactory]
      val parentActor = TestActorRef[ParentActor](ParentActor.props(childActorFactory))

      parentActor ! CreateChildren(childrenCount)

      Mockito.verify(childActorFactory, Mockito.times(childrenCount))
        .create(parentActor.underlyingActor.context)
    }
  }
  

Комментарии:

1. Почему вы хотите создать так много актеров? Разве ты не хочешь поступить context.become с одним из них? Вы уверены, что просто хотите их создать?

2. @TomerShetah Отдельные участники выполняются асинхронно, что обеспечивает больший параллелизм, в то время как использование context.become является последовательным. Это также позволяет локализовать состояние в дочерних субъектах, а не централизовать его в родительском.

3. @Tim, насколько я знаю, пока вы не добавите их в контекст, они вообще не вызываются. например, если вы добавите в функцию ChildActor приема: case s => println(s) , и отправите строку ParentActor , она не будет разрешена.

4. @TomerShetah Это псевдокод, предоставленный для поддержки конкретного вопроса о создании и тестировании актеров, поэтому не придавайте слишком большого значения тому факту, что код неполный.

Ответ №1:

Вам нужно издеваться над дочерним актером, если вы хотите полностью протестировать родительского актера, поэтому вы не можете создать дочернего актера непосредственно в родительском актере. Вместо этого передайте фабричный метод родительскому актеру (внедрение зависимостей):

 class ParentActor(makeChild: () => ChildActor) extends Actor {
  case class CreateChildren(count: Int)

  override def receive: Receive = {
    case CreateChildren(count) => for (_ <- 0 until count) makeChild() 
  }
}
  

makeChild Функция может подсчитывать количество созданных актеров. Он также может возвращать макет версии ChildActor , которая реализует тестовое поведение, чтобы подчеркнуть ParentActor .

Комментарии:

1. Спасибо! Я расширил свой код на основе вашего ответа, и все работает просто отлично.

Ответ №2:

Самый простой способ просто убедиться, что дочерние актеры созданы, — переопределить метод preStart() в ChildActor

 class ChildActor extendsActor with ActorLogging {
  override def preStart(): Unit = log.info("Child created")
def receive(): Receive = // whatever
}
  

Таким образом, вы будете видеть сообщение каждый раз, когда дочерний актер добавляется в систему.

Комментарии:

1. Да, но это решение требует ручной проверки журналов. Мне нужен был модульный тест для автоматической проверки, поэтому это решение абсолютно неуместно.