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
来运行测试
org.apache.maven.plugins
maven-surefire-plugin
2.7
true
org.scalatest
scalatest-maven-plugin
1.0
${project.build.directory}/surefire-reports
.
WDF TestSuite.txt
test
test
akka单节点测试
akka提供了一个特殊的测试框架akka-testkit
com.typesafe.akka
akka-testkit_2.12
2.5.13
test
官方例子:
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。