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。