【问题标题】:How do I unit test akka actors in Play Framework 2.2.0 Scala (spec2, Mockito)如何在 Play Framework 2.2.0 Scala(specs2,Mockito)中对 akka 演员进行单元测试
【发布时间】:2013-10-02 23:08:39
【问题描述】:

我正在尝试使用 Play 框架设置一些单元测试。我的很多逻辑都内置在预定的 akka 演员中,这些演员在后台开始并收集数据。我的问题是我不知道如何对它们进行单元测试。我真的不知道如何处理它。我正在尝试使用 akka-testkit,但我基本上是在四处游荡。有人对如何处理它有任何建议吗?示例将非常有用。这是我目前正在处理的可憎的例子:

package test

import org.specs2.mutable._
import controllers.Scanner
import java.util.UUID
import org.joda.time.DateTime
import akka.testkit.TestActorRef
import play.api.Logger
import play.api.test.{FakeApplication, TestServer}
import models.PSqlEnum

class ScannerTest extends Specification {
  val appId = UUID.randomUUID()
  val app = models.App(appId, "TestApp", "TestServer", "TestComponent", "Test Description", DateTime.now(),
                       DateTime.now(), true, 3, 60, PSqlEnum("scanType", "mandatory"), "http://localhost")
  val rules = <Rule name="DivisionDataIsAvailable" elementsToCheck="DivisionDataIsAvailable"
                    ruleType="is, true, yellow" />
              <Rule name="DivisionDataLoadIsHealthy" elementsToCheck="DivisionDataLoadIsHealthy"
                    ruleType="is, true, red" />;

  "Scanner" should {
    "test something" in {
      val fakeApp = TestServer(3333)
      fakeApp.start()
      implicit val actorSystem = play.api.libs.concurrent.Akka.system(fakeApp.application)
      val scanner = TestActorRef(new Scanner(app, rules)).underlyingActor
      Logger.warn(scanner.getResponseFromWebService.toString)
      fakeApp.stop()
      1 === 1
    }
  }
}

这显然不是真正测试任何东西。我基本上试图让它通过 1 === 1 在这一点上只是为了看看我是否可以让运行时错误停止。此代码生成的错误如下:

INFO  - Starting application default Akka system.
[info] ScannerTest
[info] Scanner should
[info] ! test something
[error]     ThrowableException: akka.actor.LocalActorRef.<init>(Lakka/actor/ActorSystemImpl;Lakka/actor/Props;Lakka/actor/InternalActorRef;Lakka/actor/ActorPath;)V (TestActorRef.scala:21)
[error] akka.testkit.TestActorRef.<init>(TestActorRef.scala:21)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:135)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:132)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:125)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:27)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:23)
[error] akka.actor.LocalActorRef.<init>(Lakka/actor/ActorSystemImpl;Lakka/actor/Props;Lakka/actor/InternalActorRef;Lakka/actor/ActorPath;)V
[error] akka.testkit.TestActorRef.<init>(TestActorRef.scala:21)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:135)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:132)
[error] akka.testkit.TestActorRef$.apply(TestActorRef.scala:125)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:27)
[error] test.ScannerTest$$anonfun$1$$anonfun$apply$3.apply(ScannerTest.scala:23)
[info] Total for specification ScannerTest
[info] Finished in 86 ms
[info] 1 example, 0 failure, 1 error
[info] test.ScannerTest

我认为我需要创建一个 FakeApplication 并使用该 FakeApplication 的 Akka.system;但是,我不知道该怎么做。老实说,我什至不确定这是否是正确的方法。如果我能生成一个通用的 Akka.system 并完成这项工作,我会欣喜若狂。如果有人对如何解决这个问题有任何想法,我将不胜感激。

【问题讨论】:

  • 抱歉听起来像个推销员,但 Manning 的 Akka in Action 有一章是关于 Akka 测试的。我读过这本书,但还没有读完。
  • 是的,我一直在阅读有关 Akka 测试的信息。我遇到的问题与 Play 的关系比我认为的 Akka 更多。我无法让单个示例起作用,而且 Play 的文档似乎不是最新的。我真的需要一个如何在 Play 2.2 中执行此操作的示例。
  • 我在 Akka in Action 中看到的示例具有扩展 TestKit(ActorSystem("testsystem")) 的测试类,他们还使用 override protected def afterAll() { super.afterAll(); system.shutdown(); } 创建了一个 trait 并将其混合。为此,他们必须注释 this: TestKit with Suite =&gt;。也许这会有所帮助。
  • 好的,我去看看。无论如何,我可能需要花费 40 美元。作为记录,I found a way 专门为单元测试创​​建一个 DefaultApplication。 val application = new DefaultApplication(new File("."), this.getClass.getClassLoader, None, Mode.Dev); implicit val actorSystem = play.api.libs.concurrent.Akka.system(application) 早上我会用头撞它,如果我弄清楚了,我会发布它。感谢您的建议。
  • 我买了它,不幸的是,它们根本不包括单元测试。我将切换到 scalaTest,看看我是否可以使用它。如果我想出一个解决方案,我会发布一个解决方案。再次感谢。

标签: unit-testing scala playframework-2.2


【解决方案1】:

好的,我想通了。确保您使用的是正确版本的 akka-testkit。在 Play 2.2.0 中,我尝试使用 akka 2.2.M3。显然,这是行不通的。我必须将正确的依赖项放在我的 Build.scala 中,结果是这样的:

"com.typesafe.akka" %% "akka-testkit" % "2.2.0" % "test"

我的实际测试代码如下所示:

package test

import org.specs2.mutable._
import controllers.Scanner
import java.util.UUID
import org.joda.time.DateTime
import akka.testkit.TestActorRef
import play.api.Logger
import models.PSqlEnum
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import scala.concurrent.ExecutionContext.Implicits.global

class ScannerTest extends Specification {
  val appId = UUID.randomUUID()
  val app = models.App(appId, "TestApp", "TestServer", "TestComponent", "Test Description", DateTime.now(),
                       DateTime.now(), true, 3, 60, PSqlEnum("scanType", "mandatory"), "http://localhost")
  val rules = <Rule name="DivisionDataIsAvailable" elementsToCheck="DivisionDataIsAvailable"
                    ruleType="is, true, yellow" />
              <Rule name="DivisionDataLoadIsHealthy" elementsToCheck="DivisionDataLoadIsHealthy"
                    ruleType="is, true, red" />;


  "Scanner" should {
    "test something" in {
      implicit val actorSystem = ActorSystem("testActorSystem", ConfigFactory.load())
      val scanner = TestActorRef(new Scanner(app, rules)).underlyingActor
      val response = scanner.getResponseFromWebService
      response onSuccess {
        case result => Logger.warn(result.toString)
      }
      response onFailure {
        case error => Logger.warn(error.toString)
      }
      1 === 1
    }
  }
}

很明显,这个测试并没有真正做任何事情。正在评估的实际测试是 1 === 1。它现在确实打印到日志中,但这意味着我可以返回并验证数据类型和响应的有效负载,然后构建一些实际的单元测试。我希望有人觉得这很有用。原始问题中的这些错误消息是由 akka-testkit 依赖项与 Akka 版本不同引起的,这可能对某人有用。

【讨论】:

  • 我不明白 play framework 中隐式 val actorSystem = ActorSystem("testActorSystem", ConfigFactory.load()) 构造的用法我通常使用 play.api.libs.concurrent.Akka.system 快捷方式作为主 akka.system 但在 2.2 中 Akka.system 总是创建它自己的 Actor 系统 play.api.libs.concurrent.Akka.system: lazy val applicationSystem: ActorSystem = { ... val system = ActorSystem("application", app .configuration.underlying, app.classloader) Play.logger.info("启动应用默认Akka系统。") system }
  • 你可以使用任何你想要的actor系统来进行这个测试。没关系。我可能把它放进去是因为它没有选择一个actorSystem。为一个测试启动一个actor系统是很昂贵的,所以最好已经设置一个你可以使用的,或者只使用默认值。不过,这可能并不重要。
【解决方案2】:

我没有使用 spec2 或 Mockito,但这是我在 Play 2.2.1 中对 Akka 演员进行单元测试的方法:

import org.specs2.mutable.Specification
import scala.concurrent.duration._
import org.scalatest.concurrent._
import akka.testkit._
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.{FlatSpec, BeforeAndAfterAll}
import akka.actor.{Props, ActorSystem}
import akka.pattern.ask
import akka.util.Timeout
import scala.util.{Failure, Success}
import model.BlacklistEntry
import scala.concurrent.{Future, Promise, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.test.FakeApplication
import model.BlacklistEntryImpl


class LicenceBlackListSpec(_system: ActorSystem) extends TestKit(_system) with ImplicitSender with ShouldMatchers with FlatSpec with BeforeAndAfterAll {
  play.api.Play.start(FakeApplication())

  import akka.testkit.TestKit._
  def this() = this( ActorSystem("LicenceBlackListSpec") )

  override def afterAll: Unit = {
    system.shutdown()
    system.awaitTermination(10.seconds)
  }

  implicit val timeout = Timeout(10 seconds)
  val blacklistRef = TestActorRef(Props[LicenceBlackList])

  "An LicenceBlackList Actor" should "be able to create a new blacklist entry" in {
    blacklistRef ! CreateEntry(BlacklistEntryImpl("NEW_KEY",1000,"Test creation"))

    val expected: BlacklistEntry = BlacklistEntryImpl("NEW_KEY", 1000 ,"Test creation")
    expectMsg( expected )
  }
}

您需要包含 scalatest lib 作为依赖项以及 akka 测试工具包:

"org.scalatest" % "scalatest_2.10" % "1.9.1"

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    我不使用 ActorSystem("testActorSystem" ) ,我试图让播放框架使用它通常的 akka 插件。这种方法有以下好处:我可以在所有代码中使用 play.api.libs.concurrent.Akka.system,我可以有额外的配置选项来理解展位播放和 akka 代码部分。

      class BaseActorTester (_app: Application) extends akka.testkit.TestKit(play.api.libs.concurrent.Akka.system(_app)) with FunSuiteLike with BeforeAndAfterAll {
    
       def this() = this(FakeApplication(additionalConfiguration=Map("currency.db"->"airando-test")))
       implicit val app: Application = _app
       implicit val ec = play.api.libs.concurrent.Akka.system.dispatcher
    
       override def beforeAll {
         Play.start(app)
       }
       // play plugin do it itself ??
       //override def afterAll {
       //  akka.testkit.TestKit.shutdownActorSystem(system)
       //}
       ...
      }
    

    【讨论】:

    • 我喜欢这个解决方案,因为它使用单个 actorSystem 进行测试,这使得它们更快,这对于单元测试来说非常有用;但是,如果你经常玩游戏,你会发现 FakeApplication 不值得它带来的麻烦。我更喜欢将配置解耦以进行单元测试,并使用单独的测试框架(黄瓜)进行功能测试。 FakeApplication 在大型应用程序中往往会变得难以预测。
    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 2012-07-12
    • 1970-01-01
    • 2014-04-12
    • 2020-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多