Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
当akka actor在测试线程之外抛出异常时,ScalateTest失败_Scala_Akka_Scalatest - Fatal编程技术网

当akka actor在测试线程之外抛出异常时,ScalateTest失败

当akka actor在测试线程之外抛出异常时,ScalateTest失败,scala,akka,scalatest,Scala,Akka,Scalatest,我遇到过这样的情况,有几次我在测试一个演员,演员意外地抛出了一个异常(由于一个bug),但测试仍然通过。现在大多数情况下,Actor中的异常意味着无论测试验证的是什么,都不会正确出来,因此测试失败,但在极少数情况下,这是不正确的。异常发生在与测试运行程序不同的线程中,因此测试运行程序对此一无所知 一个例子是,当我使用一个mock来验证调用了某个依赖项时,由于Actor代码中的错误,我在mock中调用了一个意外的方法。这会导致模拟抛出一个异常,该异常会炸毁参与者,但不会炸毁测试。有时,这甚至会导致

我遇到过这样的情况,有几次我在测试一个演员,演员意外地抛出了一个异常(由于一个bug),但测试仍然通过。现在大多数情况下,Actor中的异常意味着无论测试验证的是什么,都不会正确出来,因此测试失败,但在极少数情况下,这是不正确的。异常发生在与测试运行程序不同的线程中,因此测试运行程序对此一无所知

一个例子是,当我使用一个mock来验证调用了某个依赖项时,由于Actor代码中的错误,我在mock中调用了一个意外的方法。这会导致模拟抛出一个异常,该异常会炸毁参与者,但不会炸毁测试。有时,这甚至会导致下游测试神秘地失败,因为参与者是如何爆炸的。例如:

// using scala 2.10, akka 2.1.1, scalatest 1.9.1, easymock 3.1
// (FunSpec and TestKit)
class SomeAPI {
  def foo(x: String) = println(x)
  def bar(y: String) = println(y)
}

class SomeActor(someApi: SomeAPI) extends Actor {
  def receive = {
    case x:String  =>
      someApi.foo(x)
      someApi.bar(x)
  }
}

describe("problem example") {
  it("calls foo only when it receives a message") {
    val mockAPI = mock[SomeAPI]
    val ref = TestActorRef(new SomeActor(mockAPI))

    expecting {
      mockAPI.foo("Hi").once()
    }

    whenExecuting(mockAPI) {
      ref.tell("Hi", testActor)
    }
  }

  it("ok actor") {
    val ref = TestActorRef(new Actor {
      def receive = {
        case "Hi"  => sender ! "Hello"
      }
    })
    ref.tell("Hi", testActor)
    expectMsg("Hello")
  }
}
“problemExample”通过了,但下游的“ok actor”失败了,原因我真的不明白。。。除此之外:

cannot reserve actor name '$$b': already terminated
java.lang.IllegalStateException: cannot reserve actor name '$$b': already terminated
at       akka.actor.dungeon.ChildrenContainer$TerminatedChildrenContainer$.reserve(ChildrenContainer.scala:86)
at akka.actor.dungeon.Children$class.reserveChild(Children.scala:78)
at akka.actor.ActorCell.reserveChild(ActorCell.scala:306)
at akka.testkit.TestActorRef.<init>(TestActorRef.scala:29)
无法保留参与者名称“$$b”:已终止
java.lang.IllegalStateException:无法保留参与者名称“$$b”:已终止
在akka.actor.dungeon.ChildrenContainer$TerminatedChildrenContainer$.reserve(ChildrenContainer.scala:86)
在阿克卡。演员。地下城。儿童$class。保留儿童(儿童。斯卡拉:78)
在akka.actor.ActorCell.reserveChild(ActorCell.scala:306)
在akka.testkit.TestActorRef.(TestActorRef.scala:29)
因此,我可以通过检查afterEach处理程序中的记录器输出来查看捕获此类内容的方法。当然是可行的,尽管在我实际期望出现异常的情况下有点复杂,这正是我试图测试的。但是有没有更直接的方法来处理这个问题并使测试失败呢


附录:我已经看过TestEventListener,并怀疑其中可能有一些东西会有所帮助,但我看不到。我能找到的唯一文档是关于使用它来检查预期的异常,而不是意外的异常

除了检查日志之外,我还可以想出两种在参与者崩溃时使测试失败的方法:

  • 确保没有收到终止的消息
  • 检查TestActorRef.isTerminated属性
后一个选项已被弃用,因此我将忽略它

描述如何设置。在这种情况下,它可能看起来像:

val probe = TestProbe()
probe watch ref

// Actual test goes here ...

probe.expectNoMessage()

如果参与者因异常而死亡,它将生成终止消息。如果在测试过程中发生了这种情况,并且您期望其他情况,那么测试将失败。如果它发生在您最后一条消息期望之后,那么当接收到终止消息时,expectNoMessage()应该会失败。

好的,我有一点时间来处理这个问题。我有一个很好的解决方案,它使用事件侦听器和过滤器来捕获错误。(在更集中的情况下,检查或使用TestProbe可能很好,但在尝试将某些东西混入任何旧的测试中时似乎很尴尬。)

您只需在特定点使用
withErrorChecking
内联,或将其混合到一个套件中,然后使用
withFixture
在所有测试中全局执行,如下所示:

trait AkkaErrorCheckingSuite extends AkkaErrorChecking with FunSpec {
  override protected def withFixture(test: NoArgTest) {
    withErrorChecking(test())
  }
}
如果您在我最初的示例中使用这个,那么您将得到第一个测试“仅当它收到消息时调用foo”失败,这很好,因为这是真正失败的地方。但由于系统爆炸,下游测试仍将失败。为了解决这个问题,我进一步使用了一个
fixture.Suite
为每个测试实例一个单独的
TestKit
。这就解决了很多其他潜在的测试隔离问题,当您有嘈杂的参与者时。每次测试都需要一点仪式,但我认为这是值得的。在我最初的例子中使用这个特性,我第一次测试失败,第二次通过,这正是我想要的

trait IsolatedTestKit extends ShouldMatchers { this: fixture.Suite =>
  type FixtureParam = TestKit
  // override this if you want to pass a Config to the actor system instead of using default reference configuration
  val actorSystemConfig: Option[Config] = None

  private val systemNameRegex = "[^a-zA-Z0-9]".r

  override protected def withFixture(test: OneArgTest) {
    val fixtureSystem = actorSystemConfig.map(config => ActorSystem(systemNameRegex.replaceAllIn(test.name, "-"), config))
                                         .getOrElse    (ActorSystem (systemNameRegex.replaceAllIn(test.name, "-")))
    try {
      val errorCheck = new AkkaErrorChecking {
        val system = fixtureSystem
      }
      errorCheck.withErrorChecking {
        test(new TestKit(fixtureSystem))
      }
    }
    finally {
      fixtureSystem.shutdown()
    }
  }
}

从参与者的角度考虑,还有另一种解决方案:故障会传递给主管,因此这是捕捉故障并将其输入测试程序的最佳场所:

val failures = TestProbe()
val props = ... // description for the actor under test
val failureParent = system.actorOf(Props(new Actor {
  val child = context.actorOf(props, "child")
  override val supervisorStrategy = OneForOneStrategy() {
    case f => failures.ref ! f; Stop // or whichever directive is appropriate
  }
  def receive = {
    case msg => child forward msg
  }
}))

您可以通过发送到
failureParent
发送到受测参与者,所有预期或未预期的故障都可以发送到
failures
探测器进行检查。

谢谢您的建议。我真的想要一些可以折叠成可重用测试套件的东西。当我有机会的时候,我会考虑如何采纳其中的一个想法并将其付诸实施。在我只验证异常行为的情况下,使用testActor(带有
ImplicitSender
)而不是
TestProbe
实例,效果非常好。酷!显然看起来更像“演员”。有机会的时候我会试试的。
val failures = TestProbe()
val props = ... // description for the actor under test
val failureParent = system.actorOf(Props(new Actor {
  val child = context.actorOf(props, "child")
  override val supervisorStrategy = OneForOneStrategy() {
    case f => failures.ref ! f; Stop // or whichever directive is appropriate
  }
  def receive = {
    case msg => child forward msg
  }
}))