Время для создания TestProbe в Akka TestKit

У меня есть черта для переопределения actOf в тестах:

trait ActorRefFactory {
  this: Actor =>

  def actorOf(props: Props) = context.actorOf(props)
}

И у меня есть рабочий актер, который останавливается при получении любого сообщения:

class WorkerActor extends Actor {
  override def receive: Actor.Receive = {
    case _ => { context.stop(self) }
  }
}

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

class MasterActor extends Actor with ActorRefFactory {
  var workers = Set.empty[ActorRef]

  override val supervisorStrategy = SupervisorStrategy.stoppingStrategy

  def createWorker() = {
    val worker = context watch actorOf(Props(classOf[WorkerActor]))
    workers += worker
    worker
  }

  override def receive: Receive = {
    case m: String =>
      createWorker()
    case Terminated(ref) =>
      workers -= ref
      createWorker()
  }
}

И этот тест, который провален:

class ActorTest(val _system: ActorSystem) extends akka.testkit.TestKit(_system)
  with ImplicitSender
  with Matchers
  with FlatSpecLike {

  def this() = this(ActorSystem("test"))

  def fixture = new {
    val master = TestActorRef(new MasterActor() {
      override def actorOf(props: Props) = TestProbe().ref
    })
  }

  it should "NOT FAILED" in {
    val f = fixture

    f.master ! "create"
    f.master ! "create"

    f.master.underlyingActor.workers.size shouldBe 2

    val worker = f.master.underlyingActor.workers.head
    system.stop(worker)
    Thread.sleep(100)

    f.master.underlyingActor.workers.size shouldBe 2
  }

}

После Thread.sleep в тесте я выдаю ошибку «1 не был равен 2». Я понятия не имею, что происходит. Но, если предположить, я могу предположить, что TestProbe() не может создать за это время. Что я могу сделать?


person Leonard    schedule 22.10.2014    source источник
comment
Если я отправлю воркеру PoisonPill вместо system.stop(worker), он сработает. Но я не знаю, почему.   -  person Leonard    schedule 23.10.2014


Ответы (1)


Это в основном сводится к проблеме асинхронности, которую вы хотите попытаться избежать в модульных тестах для Akka. Вы правильно используете TestActorRef, чтобы зацепиться за CallingThreadDispatcher для актера master. Но когда вы вызываете system.stop(worker), system по-прежнему использует асинхронный диспетчер по умолчанию, который вводит это состояние гонки при остановке, а затем повторном создании работника. Самый простой способ, который я вижу для последовательного решения этой проблемы, — это остановить работника следующим образом:

master.underlyingActor.context.stop(worker)

Поскольку вы используете context из master, а этот актер использует CallingThreadDispatcher, я считаю, что это устранит проблему asnyc, которую вы видите. Это сработало для меня, когда я попробовал это.

person cmbaxter    schedule 22.10.2014
comment
К сожалению, это не работает. Например, если я добавлю оператор println в случае Terminated, я его получу. Это означает, что мастер-актор получил сообщение о завершении. - person Leonard; 23.10.2014
comment
@ Леонард, почему ты снова утверждаешь после остановки, что размер должен быть равен 2? Если завершено получено правильно, то размер списка должен измениться с 2 до 1. Ваш отказ говорит вам, что ваш размер равен 1, и это не соответствует вашему ожиданию 2, но ваше ожидание на самом деле должно быть 1. - person cmbaxter; 23.10.2014
comment
это не так просто. Посмотрите на метод createWorker(). Когда рабочий процесс завершается, мастер создает нового рабочего и помещает его в набор рабочих. - person Leonard; 23.10.2014
comment
@ Леонард, я добавил еще один ответ. Это сработало для меня, когда я попробовал это. - person cmbaxter; 23.10.2014