Akka單元測試

actor測試需瞭解scalatest,在多節點測試時,還需要使用sbt進行。

scalatest

scalatest是一個特別針對scala語言設計的單元測試框架,除了提供必要的基類和斷言系統外,scalatest可以與IntelliJ IDEAmaven等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

注意到代碼中MultiNodeSampleSpecMultiJvmNode1MultiNodeSampleSpecMultiJvmNode2這兩個類是有命名約定的,{TestName}MultiJvm{node},這裏的TestName對應測試名,而node對應MultiNodeConfig

val node1 = role("node1")
val node2 = role("node2")

在具體的測試代碼中,我們可以通過runOn來控制代碼在哪個節點上運行。通過enterBarrier來同步多個節點的代碼運行(簡單的說就是,enterBarrier將當前節點放入某種狀態,狀態名隨便定義,只有當所有的節點都進入這個狀態後,代碼才能運行下去,否則就block。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章