Akka单元测试
actor测试需了解scalatest
,在多节点测试时,还需要使用sbt进行。
scalatest
scalatest是一个特别针对scala语言设计的单元测试框架,除了提供必要的基类和断言系统外,scalatest
可以与IntelliJ IDEA
和maven
等IDE或构建工具集成。akka提供的测试框架就是在scalatest
的基础上构建的,所以有必要先了解scalatest
。
scalatest提供了多种测试代码的书写风格,由于akka的例子大多是WordSpec
风格的,所以建议优先研究和掌握WordSpec
风格。
只要是安装有scala语言插件的IntelliJ IDEA
,是默认可以继承scalatest的。这意味着你可以通过右键测试代码,就会弹出Run Test ...
,甚至可以Debug Test ...
。而且无需像JUnit那样为测试类或方法书写注解。这些注解在scalatest提供的trait中已经包含了。
在与maven
集成时,需要在pom
中引入插件,这样可以通过mvn test
来运行测试
<!-- disable surefire --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.7</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <!-- enable scalatest --> <plugin> <groupId>org.scalatest</groupId> <artifactId>scalatest-maven-plugin</artifactId> <version>1.0</version> <configuration> <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory> <junitxml>.</junitxml> <filereports>WDF TestSuite.txt</filereports> </configuration> <executions> <execution> <id>test</id> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin>
akka单节点测试
akka提供了一个特殊的测试框架akka-testkit
<dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-testkit_2.12</artifactId> <version>2.5.13</version> <scope>test</scope> </dependency>
官方例子:
import akka.actor.ActorSystem import akka.testkit.{ ImplicitSender, TestActors, TestKit } import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpecLike } class MySpec() extends TestKit(ActorSystem("MySpec")) with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { override def afterAll { TestKit.shutdownActorSystem(system) } "An Echo actor" must { "send back messages unchanged" in { val echo = system.actorOf(TestActors.echoActorProps) echo ! "hello world" expectMsg("hello world") } } }
- TestKit是akka提供的testkit类,每一个测试类需继承
- WordSpecLike主要用于编写WordSpec风格的测试用例代码
- BeforeAndAfterAll用于覆写
afterAll
- 测试程序启动时,会自动创建一个
actorsystem
,可以向上述代码那样,创建actor,发消息。通过expectXXX来断言收到消息。这里隐含一个叫testActor
的实例,这个实例是作为发送消息的源,当书写expectXXX时,实际是期望testActor
收到消息。 ImplicitSender
用于将testActor
映射到self
上
sbt构建
sbt是scala官方的构建系统。之所以要学习sbt,是因为想进行akka多节点测试
的话,目前必须采用sbt构建。因为akka官方只为sbt创建了多节点测试的测试框架。
sbt的内容这里不详述,从下面多节点测试中体会。
akka多节点测试
官方文档描述了如何进行akka的多节点测试。
首先,应该引入akka的多节点测试框架:
libraryDependencies += "com.typesafe.akka" %% "akka-multi-node-testkit" % "2.5.13"
然后引入插件,在project/plugins.sbt
中加入:
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0")
最终build.sbt
应该看起来是这样的:
ThisBuild / version := "0.1.0" ThisBuild / scalaVersion := "2.11.8" ThisBuild / organization := "com.eoi.dc" lazy val eoilib = (project in file(".")) .enablePlugins(MultiJvmPlugin) .configs(MultiJvm) .settings( name := "eoilib", libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-cluster" % "2.5.13", "org.scalactic" %% "scalactic" % "3.0.5", "org.scalatest" %% "scalatest" % "3.0.5", "com.typesafe.akka" %% "akka-multi-node-testkit" % "2.5.13", ) )
测试代码如下:
package com.eoi.dc.lib.test //#config import akka.remote.testkit.MultiNodeConfig object MultiNodeSampleConfig extends MultiNodeConfig { val node1 = role("node1") val node2 = role("node2") } //#config //#spec import akka.actor.{Actor, Props} import akka.remote.testkit.MultiNodeSpec class MultiNodeSampleSpecMultiJvmNode1 extends MultiNodeSample class MultiNodeSampleSpecMultiJvmNode2 extends MultiNodeSample object MultiNodeSample { class Ponger extends Actor { def receive = { case "ping" => sender() ! "pong" } } } class MultiNodeSample extends MultiNodeSpec(MultiNodeSampleConfig) with STMultiNodeSpec { import MultiNodeSample._ import MultiNodeSampleConfig._ def initialParticipants = roles.size "A MultiNodeSample" must { "wait for all nodes to enter a barrier" in { enterBarrier("startup") } "send to and receive from a remote node" in { runOn(node1) { enterBarrier("deployed") val ponger = system.actorSelection(node(node2) / "user" / "ponger") ponger ! "ping" import scala.concurrent.duration._ expectMsg(10.seconds, "pong") } runOn(node2) { system.actorOf(Props[Ponger], "ponger") enterBarrier("deployed") } enterBarrier("finished") } } } //#spec
运行时:
> multi-jvm:testOnly MultiNodeSampleSpec
注意到代码中MultiNodeSampleSpecMultiJvmNode1
和MultiNodeSampleSpecMultiJvmNode2
这两个类是有命名约定的,{TestName}MultiJvm{node},这里的TestName
对应测试名,而node对应MultiNodeConfig
的
val node1 = role("node1") val node2 = role("node2")
在具体的测试代码中,我们可以通过runOn
来控制代码在哪个节点上运行。通过enterBarrier
来同步多个节点的代码运行(简单的说就是,enterBarrier
将当前节点放入某种状态,状态名随便定义,只有当所有的节点都进入这个状态后,代码才能运行下去,否则就block。