Akka中的单元测试
来源:互联网 发布:科比2000年西决数据 编辑:程序博客网 时间:2024/05/22 07:02
Akka中的单元测试 – TestKit
在任何一个系统中,测试代码都是至关重要的。当一个系统不断增大的时候,跟需要有每个关键功能的测试代码。在这片文章中,我将简要介绍一下Actor(Akka)模型中的测试方法和测试思想。
本文所有使用的代码来自于Akka的官方文档。
基本思想
ScalaTest
首先,Akka的测试是基于ScalaTest的,所以我们首先介绍一下Scala的测试方法。
Scala的测试使用的是WordSpecLike
模式,使用3个关键字组织测试–must
, should
, can
。这3个关键字可以创建一个测试,之后使用in
关键字可以进行多个子测试(单元测试)。下面给一个例子:
"An Echo actor" must{ "send back message unchanged" in { val echo = system.actorOf(TestActors.echoActorProps) echo ! "hello world" expectMsg("hello world") } "another result" in { // another case ... } }
must
关键字创建了一个测试—An Echo actor
,之后有一个单元测试send back message unchanged
,在之后还可以创建其他条件下的测试。使用WordSpecLike
的测试模式,测试的可读性非常好。
TestKit
进行Actor实验,最为关键就是要扩展TestKit
类。需要下面两个依赖
"com.typesafe.akka" %% "akka-testkit" % akkaVersion, "org.scalatest" %% "scalatest" % "3.0.1",
首先,我们需要知道TestKit
中,有一个system
变量 – ActorSystem
。我们创建新的Actor的时候,都需要使用system
。
我们会产生一个疑问,如果我们向一个测试Actor – toTestActor 发送不同类型消息,查看toTestActor的处理结果,我们使用哪个Actor发送消息给toTestActor,并使用这个Actor接收来自toTestActor呢?
在TestKit中保存着一个testActor变量,这个变量会发送消息给toTestActor。
同时,testActor接收到的消息,会保存到一个queue
中,我们调用expectMsg
方法,检查queue
中的是否有我们想要的消息。
举个例子:
"An Echo actor" must{ "send back message unchanged" in { val echo = system.actorOf(TestActors.echoActorProps) echo ! "hello world" expectMsg("hello world") } }
在这个例子中,我们使用system
创建了一个Actor – echo
进行测试。我们向echo
发送消息(使用的是testActor
),并使用expectMsg
等待消息回复。
测试方法
断言方法
在之前的例子中,我们使用了expectMsg
这个断言。在Akka中,有许多不同的断言方法,这里列出以下:
* expectMsg[T](d: Duration, msg:T)
:等待一段时间,检查消息
* expectNoMsg(d: Duration)
:无消息返回
等待时间
使用within([min, ]max{}
设定一段时间来执行代码段中的代码。例子:
"Time wait" should { "not fail" in { within(200 millis){ // expectNoMsg or receiveWhile, the final check for the within // is skipped in order to avoid false positive due to wake-up latency. expectNoMsg() Thread.sleep(300) } } }
在上面的例子中,我们等待200ms执行中间的代码。但是要注意如果代码段中有expectNoMsg
和receiveWhile
这样的代码,within
检查会被省略。也就是,程序会使用expectNoMsg
和receiveWhile
的等待时间而不是within
的等待时间。
Probe
当我们需要向多个Actor
发送请求的时候,如果单纯使用testActor
作为接受者的话,我们往往无法判断消息到来的顺序。所以我们需要又一个专门的接受者,准备接受指定的数据。这里我们介绍的TestProbe
方法得到的结果就是一个靶接收器,等待来自测试Actor
返回的结果。在之后我们将看到,这些probe
甚至可以充当消息传递的中间人,在“暗中观察”消息的传递过程。
class MyDoubleEcho extends Actor{ var dest1: ActorRef = _ var dest2: ActorRef = _ override def receive = { case (d1: ActorRef, d2: ActorRef) => dest1 = d1 dest2 = d2 case x => dest1 ! x dest2 ! x }} "Test probe" should { "return two echo" in { val probe1 = TestProbe() val probe2 = TestProbe() val actor = system.actorOf(Props[MyDoubleEcho]) actor ! (probe1.ref, probe2.ref) actor ! "Hello" probe1.expectMsg("Hello") probe2.expectMsg("Hello") } }
在上面的代码中,我们创建了两个probe
来监听消息的接受。它们可以被看作一个普通的Actor
,作为参数传递给我们测试的Actor
–MyDoubleEcho
。我们通过等待probe
接受的消息(不再是testActor
)的消息,来测试相应的业务逻辑。
在Actor的文档中提到,我们不要对TestActor
进行watch
原因是,TestProbe
使用的是CallingThreadDispatcher
(一个线程调度管理类)来发送消息。而这个类会导致dead-lock
出现。这个有一点疑问~
使用probe
来watch
一个其他的Actor
。
val probe = TestProbe()probe watch targettarget ! PoisonPillprobe.expectTerminated(target)
使用probe
来回复消息
val probe = TestProbe()val future = probe.ref ? "Hello"probe.expectMsg(0 millis, "hello")probe.reply("world")assert(future.isCompleted && future.value == Some(Success("world")))
在这个实验中需要注意,Success
需要是scala.util.Success
。
使用probe
作为消息中间人
class Source(target: ActorRef) extends Actor { override def receive = { case "start" => target ! "work" }}class Destination extends Actor { override def receive = { case x => println(x) }}class ForwardDemo extends TestKit(ActorSystem("testsystem"))with WordSpecLike with BeforeAndAfterAll{ override protected def afterAll(): Unit = { shutdown(system) } // test probe act as a mid-one "forward demo" should { "receive message" in { val probe = TestProbe() val source = system.actorOf(Props(classOf[Source], probe.ref)) val dest = system.actorOf(Props[Destination]) source ! "start" probe.expectMsg("work") probe.forward(dest) } }}
在上面的实例中,我们创建了一个probe
,接受来自source
的消息回复。在probe.ref
位置上应该是dest
,我们放置probe
用来监控消息。 probe
之后会将消息forward
到dest
,仿佛probe
没有接受过消息,source
直接将消息发送给dest
。
AutoPilot
在上面的实例中,我们使用probe
来实现对消息的监控,在这里我们将介绍一个更为方便的方法。
"test auto pilot" should { "forward message" in { val probe = TestProbe() probe.setAutoPilot(new testkit.TestActor.AutoPilot { override def run(sender: ActorRef, msg: Any): TestActor.AutoPilot = msg match{ case "stop" => TestActor.NoAutoPilot case x => println(s"receive message $x") testActor.tell(x, sender); TestActor.KeepRunning } }) probe.ref ! "Hello" expectMsg("Hello") probe.ref ! "stop" probe.ref ! "Hello2" expectNoMsg } }
我们对probe
设置auto-pilot
。这个AutoPilot
中,我们可以设置更为负责的传递逻辑。需要注意的是在run
方法的partial function
中, 必须返回probe
下一步的状态,NoAutoPilot
或者KeepRunning
。在本例中,如果接受到来自sender
的消息是”stop”,我们就停止运行Actor。否则就将消息返回给发送者testActor.tell(x, sender)
。
CallingThreadDispatcher
在进行单元测试的时候,我们将所有的测试Actor
都运行在CallingThreadDispatcher
之上。这带来的好处是,我们能够跟踪异常出现的堆栈。这回导致一个问题,就是如果一个Actor
在运行的时候,阻塞了当前的线程,那么整个测试环境都将会被阻塞。
val latch = new CountDownLatch(1)actor ! startWorkerAfter(latch) // actor will call latch.await() before proceedingdoSomeSetupStuff()latch.countDown
在上面的实例中,代码将会无限期的阻塞,而且代码永远不会运行到第4行。原因是我们创建的worker
需要等待latch
,所以将当前的线程阻塞了。在实际的环境中,当前代码会在其他的dispatcher
上执行,相当于在另一个线程中执行。而在测试当中,我们只有一个线程可以使用,所以一切都堵住了。
最后放上一段精彩的测试代码以供欣赏^ _ ^
package TestKitDemoimport akka.actor.{Actor, ActorRef, ActorSystem, Props}import akka.testkit.{DefaultTimeout, ImplicitSender, TestActors, TestKit}import com.typesafe.config.ConfigFactoryimport org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}import scala.collection.immutableimport scala.util.Randomimport scala.concurrent.duration._object TestKitUsageSpec { val config = """ |akka { | loglevel = "DEBUG" | actor { | debug { | receive = on | autoreceive = on | } | } |} """.stripMargin class ForwardingActor(next: ActorRef) extends Actor { override def receive: Receive = { case msg => next ! msg } } class FilteringActor(next: ActorRef) extends Actor { override def receive: Receive = { case msg: String => next ! msg case _ => None } } class SequencingActor(next: ActorRef, head: immutable.Seq[String], tail: immutable.Seq[String]) extends Actor{ override def receive: Receive = { case msg => { head foreach {next ! _} next ! msg tail foreach {next ! _} } } }}class TestKitUsageSpec extends TestKit(ActorSystem("TestKitUsageSpec", ConfigFactory.parseString(TestKitUsageSpec.config))) with DefaultTimeout with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { import TestKitUsageSpec._ val echoRef = system.actorOf(TestActors.echoActorProps) val forwardRef = system.actorOf(Props(classOf[ForwardingActor], testActor)) val filterRef = system.actorOf(Props(classOf[FilteringActor], testActor)) val randomHead = Random.nextInt(6) val randomTail = Random.nextInt(10) val headList = immutable.Seq().padTo(randomHead, "0") val tailList = immutable.Seq().padTo(randomTail, "1") val seqRef = system.actorOf(Props(classOf[SequencingActor], testActor, headList, tailList)) override protected def afterAll(): Unit = { shutdown() } "An EchoActor" should { "Respond with the same message" in { within(500 millis){ echoRef ! "Hello" expectMsg("Hello") } } } "A ForwardingActor" should { "Forward a message it receives" in { within(500 millis) { forwardRef ! "test" expectMsg("test") } } } "A FilteringActor" should { "Filter all messages, except expected messagetypes it receive" in { var messages = Seq[String]() within(500 millis) { filterRef ! "test" expectMsg("test") filterRef ! 1 expectNoMsg filterRef ! "Some" filterRef ! "more" filterRef ! 1 filterRef ! "text" filterRef ! 1 receiveWhile(500 millis){ case msg: String => messages = msg +: messages } } messages.length should be(3) messages.reverse should be(Seq("Some", "more", "text")) } } "A Sequencing Actor" should { "receive an interesting message at some point" in { within(500 millis) { ignoreMsg { case msg: String => msg != "something" } seqRef ! "something" expectMsg("something") ignoreMsg { case msg : String => msg == "1" } expectNoMsg ignoreNoMsg } } }}
- Akka中的单元测试
- Akka【八】 Vector Clock在AKKA中的实现
- Akka
- Akka
- Akka
- Akka
- 日志线程与MDC中的Akka源
- java中的Actor模式 Akka实例
- Akka在Flink中的使用剖析
- boost 中的 单元测试 库
- VS2005中的单元测试
- VS2005中的单元测试
- APR中的单元测试框架
- 单元测试中的Mock对象
- 单元测试中的Mock
- struts2中的action单元测试
- struts2中的action单元测试
- 单元测试中的Mock对象
- 10. Override和Overload的含义与区别。
- Freemarker 模板的语法实例
- 科学研究设计三:抽样
- Java生成二维码
- 颜值即正义,Linux 桌面个性化工具推荐
- Akka中的单元测试
- Oracle基础知识4 : 单表查询3
- 数据结构-栈和队列
- 第一章课后习题
- 语音识别关键技术公开,人机交互这么做就对了!
- 轻松定位硬件故障方法-日志分析
- NTSC和PAL的区别
- 第二章课后习题
- sass初次使用及奇淫技巧之使用谷歌调试编译sass