Как вы обнаруживаете ошибки и исключения без выключения опекуна при использовании TestActorRef в Akka?

При выполнении синхронного тестирования с TestActorRef (в соответствии с рекомендациями Testing Actor Systems) иногда желательно проверить, не генерируется ли исключение, отдельно от любых других тестов, которые ожидают передачи сообщений и т. д.

Как это можно сделать надежно в модульном тесте?

Этот ответ показывает, как добавить обработчик для исключений с помощью EventFilter, Этот подход не работает для Throwable с которые Error с однако.

Вот пример кода:

import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.event.Logging
import akka.testkit.{EventFilter, ImplicitSender, TestKit, _}
import com.typesafe.config.{Config, ConfigFactory}
import org.scalatest.{FlatSpec, Matchers}

import scala.collection.mutable


object GreetingProtocol {
  case class Greet(msg: String)
  case object GreetWithNPE
  case object GreetWithError
}

class ErrorLogger() {
  val errors = mutable.ListBuffer[Logging.Error]()
  val filter = EventFilter.custom {
    case e: Logging.Error =>
      errors += e
      false
  }
}

class Greeter extends Actor with ActorLogging {
  import GreetingProtocol._
  def receive = {
    case Greet(who) => log.info(s"Hello $who")
    case GreetWithError => ???
    case GreetWithNPE => throw new NullPointerException("NPE")
  }
}

class GreetingSpec extends FlatSpec with Matchers {
  import GreetingProtocol._

  def testEventListener(theFilter: EventFilter) = Props(new TestEventListener { addFilter(theFilter) })
  def akkaConfiguration: Config =  ConfigFactory.parseString(
    """
      |akka {
      | loglevel = DEBUG
      | actor.debug {
      |   receive = on
      |   lifecycle = on
      |   event-stream = on
      | }
      | remote {
      |   log-sent-messages = on
      |   log-received-messages = on
      | }
      |}
      |
    """.stripMargin)

  "a normal greeting" should "cause the test to succeed" in
    new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {

      val errorLogger = new ErrorLogger()
      system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])

      filterEvents(errorLogger.filter)({
        val greeter = TestActorRef[Greeter]("Greeter")
        greeter ! Greet("Bob")
      })

      shutdown()

      withClue("errors logged") {
        errorLogger.errors should have length 0
      }
  }

  "a NullPointerException" should "be caught by the testing framework and not make the test succeed" in
    new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {
      val errorLogger = new ErrorLogger()
      system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])

      filterEvents(errorLogger.filter)({
        val greeter = TestActorRef[Greeter]("Greeter")
        greeter ! GreetWithNPE
      })

      shutdown()

      withClue("errors logged") {
        errorLogger.errors should have length 1
      }
    }

  "an Error" should "be caught by the testing framework and not make the test succeed" in
    new TestKit(ActorSystem("test", akkaConfiguration)) with ImplicitSender {
      val errorLogger = new ErrorLogger()
      system.eventStream.subscribe(system.actorOf(testEventListener(errorLogger.filter), "ErrorListener"), classOf[Logging.Error])

      filterEvents(errorLogger.filter)({
        val greeter = TestActorRef[Greeter]("Greeter")
        greeter ! GreetWithError
      })

      shutdown()

      withClue("errors logged") {
        // Fails because the user guardian is shut down and the ErrorListener is gone with it
        errorLogger.errors should have length 1
      }
    }

}

С этим кодом много проблем, но главная из них заключается в том, что третий тест всегда терпит неудачу из-за сбоя опекуна пользователя со следующим сообщением в выходных данных журнала:

[ERROR] [03/02/2015 11:34:51.260] [test-akka.actor.default-dispatcher-2] [LocalActorRefProvider(akka://test)] guardian failed, shutting down system

Что можно сделать с третьим случаем, чтобы проверка прошла успешно (например, ошибка заносится в список ошибок)?

Есть две другие небольшие проблемы:

  • Вызов shutdown() необходим, потому что нет очевидного способа дождаться, пока прослушиватель событий обработает событие, а установка произвольного тайм-аута еще сложнее
  • Вызов filterEvents() на самом деле не нужен, но, похоже, все примеры его используют, почему?

Версии:

  • Scala 2.11.4
  • Акка 2.3.7
  • Scalatest 2.2.2

0 ответов

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