3.2.3 SideEffectingActor 例子

还记得 HelloWorld 的例子吗?它只做了一件事:它的迎宾收到消息,然后输出到终端。 SideEffectingActor 使得我们可以测试这样的场景:不能直接查看动作产生的效果。 很多用例符合这个描述,下边这个充分展示了测试预期结果的终极方法:

Listing 3.13 测试 HelloWorld

import Greeter01Test._

class Greeter01Test extends TestKit(testSystem)
  with WordSpec
  with MustMatchers
  with StopSystemAfterAll {

  "The Greeter" must {
    "say Hello World! when a Greeting("World") is sent to it" in {
      val dispatcherId = CallingThreadDispatcher.Id
      val props = Props[Greeter].withDispatcher(dispatcherId)
      val greeter = system.actorOf(props)
      EventFilter.info(message = "Hello World!",
        occurrences = 1).intercept {
          greeter ! Greeting("World")
        }
      }
  }
}

object Greeter01Test {
  val testSystem = {
    val config = ConfigFactory.parseString(
      """akka.event-handlers = ["akka.testkit.TestEventListener"]""")
    ActorSystem("testsystem", config)
  }
}
  1. 根据附有测试事件监听器的配置创建系统
  2. 在 Greeter01Test 中使用 testSystem
  3. 单线程环境
  4. 拦截记录的日志信息

通过使用 ActorLogging 特质,检查写出的日志信息,来测试迎宾。 测试工具箱模块提供了 TestEventListener, 你可以配置地处理记录的所有事件。 ConfigFactory 可以从字符串中解析配置文件,这样可以重载事件处理器列表。

测试运行在单线程环境中,因为哦我们想检查,当迎宾发出 ”World“ 问候时,TestEventListener 记录了日志事件。 我们使用 EventFilter 对象,它可以用来过滤日志信息。 在本例中,我们滤出期望的信息,这些信息应该只出现一次。 当执行拦截代码块的时候,应用过滤器,也就是发送消息的时候。

上述测试 SideEffectingActor 的例子显示,断言一些交互可以很快变得很复杂。 在大量情况中,可以很容易地通过稍微调整代码来方便测试。 显然,如果我们把监听器传递给测试中的类,我们不必做任何配置或过滤, 我们会得到测试中的 actor 产生的每一个消息。 下边的例子展示了调整后的迎宾 actor,它可以配置地只要有问候被记录,就发送消息给监听器 actor:

Listing 3.14 简化有监听器的问候 actor 测试

class Greeter02(listener: Option[ActorRef] = None)
  extends Actor with ActorLogging {
  def receive = {
    case Greeting(who) =>
      val message = "Hello " + who + "!"
      log.info(message)
      listener.foreach(_ ! message)
  }
}
  1. 构造函数接收一个额外的参数,默认为空
  2. 可以发送给监听器

名为 Greeter02 的 actor 被修改地可以接受一个额外的 Option[ActorRef],默认是 None 当其成功地记录了一个消息后,如果 Option 不为空,就发送消息给监听器。 当正常使用 actor,没有指定监听器,它像平常一样运行。 下边是更新后的 Greeter02 actor 的测试:

Listing 3.15 更简单的问候 Actor 测试

class Greeter02Test extends TestKit(ActorSystem("testsystem"))
  with WordSpec
  with MustMatchers
  with StopSystemAfterAll {

  "The Greeter" must {
    "say Hello World! when a Greeting("World") is sent to it" in {
      val props = Props(new Greeter02(Some(testActor)))
      val greeter = system.actorOf(props, "greeter02-1")
      greeter ! Greeting("World")
      expectMsg("Hello World!")
    }        
  "say something else and see what happens" in {
    val props = Props(new Greeter02(Some(testActor)))
    val greeter = system.actorOf(props, "greeter02-2")
    system.eventStream.subscribe(testActor, classOf[UnhandledMessage])
    greeter ! "World"
    expectMsg(UnhandledMessage("World", system.deadLetters, greeter))
  }
}
}
  1. 把监听器设为 testActor
  2. 像平常一样断言消息

如你所见,测试被大大简化了。 我们简单地传给 Greeter02 构造函数 Some(testActor),然后像平常一样断言发送给 testActor 的消息。

下一节中,我们将要看看两路消息,以及如何测试。