Akka 使用系列之二: 测试

通过上一篇文章,我们已经大致了解怎么使用 Akka,期待细致用法。这篇文章将介绍如何用 Akka-testkit 对 Akka 程序进行测试。

并行程序是最难调试的程序类型之一,因此做好测试是相当重要的事情。为了减轻 Akka 的程序的测试难度, Akka 官方专门开发了一个测试工具包 Akka-testkit。

1 Actor 的测试需求

对于一个 Actor, 我们要测什么呢?不同的文章有不同的说法,比如 http://rerun.me/2014/09/29/akka-notes-logging-and-testing/ 就把 Actor 测试需求分为:1)发送消息给 Actors, 2)测试内部状态,3)测试日志和 4)带参数 Actor 的测试。我个人认为,对于一个 Actor, 我们要测的有三个方面:1)Actor 接收消息之后,是否返回正确的消息,2)Actor 接收消息之后,是否正确地改变内部状态和执行内部行为,3)Actor 接收消息之后,是否正确地发消息给后续 Actor。当然这是我的一家之言,有什么不完善的地方,欢迎大家讨论。下面是一个简单的示例图。

下面是 studentActor 的一段代码,反应了 studentActor 接受到早上时间消息之后的动作,包括:1)给环境或者闹钟回应“关闭闹钟”,2)内部变量 DayInSchool 加 1,3)向老师发送问题消息。这段代码将包含所有要测试的元素,后面我们将示例怎么用 Akka-testkit 测试这段代码。

  def receive = {
      case time:Long => {

        val originalSender = sender;
        sender ! "关闭闹钟" 

        DayInSchool += 1;
        log.info("DayInSchool is %d".format(DayInSchool))

        remoteTeacherRef ! "历史上规模最大的众筹行动是什么?";
      }
    }

2 不适用的 Scalatest

Scalatest 是 Scala 开发者们最常见的测试工具,其用法非常简便。下面是一个 Scalatest 的简单示例。

@RunWith(classOf[JUnitRunner])
class TeacherServiceTest1 extends FunSuite with BeforeAndAfter{
  test("countAnswer"){
     assert(1==1)
  }
}

但是我们无法使用 scalatest 测试 Actor。原因在于:1)Scalatest 无法捕捉被测 Actor 回应的消息,因此无法测试被测 Actor 是否正确回应消息; 2)Scalatest 无法获取被测 Actor 的内部状态,因此无法测试被测 Actor 内部状态的改变是否正确; 3) Scalatest 无法捕捉被测 Actor 对外发送的消息,因此无法测试被测 Actor 对外发送的消息是否正确。因此有必要针对 Akka 开发一套测试工具, Akka-testkit 测试包应运而生。

3 Akka-testkit 的使用

Maven 项目要使用 Akka-testkit,需要在 pom.xml 文件中加入 akka-testkit 包,如下所示。

<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-testkit_2.10</artifactId>
    <version>2.2.5</version>
</dependency>

然后编写单元测试的代码,其基本范例如下。

@RunWith(classOf[JUnitRunner]) //
class StudentActorTest
  extends TestKit(ActorSystem("SummerSchool",
    ConfigFactory.parseString("""一些配置""")))
  with WordSpecLike
  with BeforeAndAfterAll{
   
     "A actor " must {
       "acts in this way" in {
          测试代码写在这里。
        }
     }

  }

Akka-testkit 的主要工具包括, 1) testProbe 用于测试 Actor 回应和发送消息,testActor 用于简便情况下测试 Actor 回应消息,和 2) testActorRef 用于测试 Actor 内部状态的改变。

3.1 回应消息的测试

对于被测 Actor 是否正确地回应消息,可以用 testProbe 测试。首先将 testProbe 给被测 Actor 发送消息,再看 testProbe 是否接受到期望的回应消息。下面是一个示例。

 //test its responses
  "The countAnswer " must {
    "response a correct answer order" in {
      val studentActor 
          = system.actorOf(Props(new StudentActor(teacherActor )))
      val testProb   = new TestProbe(system);
      testProb.send(studentActor, 7.toLong);
      //testProbe 给 studentActor 发送 “早上 7 点啦” 的消息
      testProb.send(studentActor, 7.toLong)

      testProb.expectMsg("关闭闹钟")
      //测试 testProbe 是否收到预期回应消息
      testProb.expectMsg("关闭闹钟")
    }
  }

除了使用 testProbe 之外,Akka-testkit 还提供一种简便方法: 使用 testActor。 如果测试类实现特质 ImplicitSender, studentActorRef ! 7.toLong 发送给 studentActor 的消息 7.toLong 就是从 testActor 来的。然后在调用 expectMsg(“关闭闹钟”) 就可以测试 testActor 是否收到 studentActor 回应消息 “关闭闹钟” 了。具体代码如下所示。

class StudentActorTest
  extends TestKit(ActorSystem("SummerSchool",
    ConfigFactory.parseString("""一些配置""")))
  with ImplicitSender //加这句,把testActor 设置为消息发出 Actor
  with WordSpecLike
  with BeforeAndAfterAll{

  "StudentActor" must {
    "response correctly" in {
      val studentActorRef 
      = system.actorOf(Props(new StudentActor(teacherActor )))
      studentActorRef ! 7.toLong; 
      //testActor 发出 7.toLong 消息给 studentActor
      expectMsg("关闭闹钟") 
      //testActor 应该收到 studentActor 回应消息 "关闭闹钟"
    }
  }

}

我们可以看出,使用 testActor 的代码比使用 testProbe 的简便。但是,一个东西的用法越是简便,功能便越缺失。testActor 最大的缺失是只能接受被测 Actor 发来的一个回应消息。比如下面的代码就会报错。

"StudentActor" must {
    "response correctly" in {
      val studentActorRef 
      = system.actorOf(Props(new StudentActor(teacherActor )))
      studentActorRef ! 7.toLong; 
      studentActorRef ! 8.toLong; 
      expectMsg("关闭闹钟") 
      expectMsg("关闭闹钟") 
    }

3.2 内部状态的测试

对于被测 Actor 内部状态的改变,可以用 TestActorRef 进行测试。TestActorRef.underlyingActor 可以探测被测 Actor 的内部,用于测试被测 Actor 内部状态是否符合预期。 下面是一个示例。

"StudentActor" must {
    "increase the DayInSchool" in {
      val testActorRef 
       = TestActorRef(new StudentActor(teacherActor)) 
      // 建立 Actor 的TestActorRef
      testActorRef ! 7.toLong; 
      assert(testActorRef.underlyingActor.DayInSchool == 1); 
      // TestActorRef.underlyingActor 探测 DayInSchool 变量是否符合预期。
    }
}

3.3 发出消息的测试

对于被测 Actor 是否正确地发出消息,也可以用 testProbe 测试。首先将 testProbe 设置为被测 Actor 发出消息的目标,然后让被测 Actor 发出消息,再看 testProbe 是否接受到期望的消息。下面是一个示例。

"StudentActor " must{
    val questionReceiver 
      = TestProbe()
    val studentActorRef 
      = system.actorOf(Props(new StudentActor(questionReceiver.ref))) 
    // 设置为 studentActor 发送消息的目标

    "send a question after waking up" in {
        studentActorRef ! 7.toLong 
        studentActorRef ! 7.toLong
        /*给 studentActor 发送消息“7点啦” ,
         *studentActor 会给老师(这里的 questionReceiver.ref) 发送问题
         */

        questionReceiver.expectMsg("历史上规模最大的众筹行动是什么?")
        questionReceiver.expectMsg("历史上规模最大的众筹行动是什么?")
        //模拟老师的 testProbe 是否收到预期问题
    }
}

4 总结

Akka-testkit 是 Akka 官方推出的 Akka 测试工具包,用于减轻 Akka 程序的测试难度。Akka-testkit 的主要工具包括, 1) testProbe 用于测试被测 Actor 回应和发送消息,testActor 用于简便情况下测试被测 Actor 回应消息,和 2) testActorRef 用于测试被测 Actor 内部状态的改变。完整的项目代码已经上传到 Github 上了。被测 Actor 是 org.algorithmdog.akkalearning.StudentActor, 测试类是 org.algorithmdog.akkalearning.StudentActorTest。

这篇文章难产了很长一段时间,对不住支持我的读者们。对不起。Akka 和 Actor 模型对我来说是一个全新的东西,花了比较多的时间学习和熟悉。学习之后,觉得第一篇写得太不清楚了,准备重构第一篇。对于这篇文章质量,我个人比较满意的,甚至敢认为这篇文章应该是国内关于 Akka-testkit 最清楚的文章之一(ps:大牛们轻喷)。

Akka 系列系列文章

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么我们从Python切换到Go?

切换到新的编程语言向来是关键一步,尤其是当你的团队只有一位成员有该语言的使用经验时。今年年初,我们将 Stream 的主要编程语言从Python 切换到 Go。...

3032
来自专栏Android知识点总结

01--Java核心之JVM引入篇

虚拟机即:虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的 JVM只是一种用于计算设备的规范,广义来说相当于一个接口,而接口的实现可以不...

863
来自专栏ThoughtWorks

都100%代码覆盖了,还会有什么问题?

(图片来自:http://t.cn/R06rQHi) 引言 很多人看到这个标题时,都会想“你都100%代码覆盖了,怎么还会有问题呢?” 让我们看一下代码例子:...

3868
来自专栏Crossin的编程教室

【每周一坑】用代码给图片配上文字

我们的『每日一坑』栏目里都是一些练手的小题目,难度不高,适合新手用来熟悉编程。如果想要更复杂的大项目,可以上我们的实验室栏目 lab.crossincode.c...

3276
来自专栏机器之心

放弃Python转向Go语言:我们找到了以下9大理由

选自Stream 作者:Thierry Schellenbach 机器之心编译 参与:黄小天、李亚洲 转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有...

61511
来自专栏王清培的专栏

.NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)

阅读目录: 1.开篇介绍 2.迭代测试、重构(强制性面向接口编程,要求代码具有可测试性) 2.1.面向接口编程的两个设计误区 2.1.1.接口的依赖倒置 ...

2019
来自专栏企鹅号快讯

PHP的学习路线

1、PHP的学习路线 任何网站全都是由网页组成的,也就是说想完成一个网站,必须先学会做网页,掌握静态网页的制作技术是学习开发网站的先决条件。 因此我们要学习HT...

58610
来自专栏java思维导图

程序员,请优先提高代码的可读性

现在,当有人提及“优化”一词时,他们通常是指“优化执行时间”,除非他们明确表明要优化GPU的内存消耗,网络流量等等。

954
来自专栏狮乐园

【译】Understanding SOLID Principles - Single Responsibility

之前的第一篇文章阐述了依赖倒置原则(DIP)能够使我们编写的代码变得低耦合,同时具有很好的可测试性,接下来我们来简单了解下单一职责原则的基本概念:

731
来自专栏开源优测

[接口测试 - 基础篇] 02 你应该掌握的Python3接口测试内功

概述 本文主要介绍基于Python3进行接口测试时,应该掌握Python3哪些基本的能力,主要从以下几个方面进行说明。 Python3基本语法 ...

3476

扫码关注云+社区

领取腾讯云代金券