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)
}
}
- 根据附有测试事件监听器的配置创建系统
- 在 Greeter01Test 中使用 testSystem
- 单线程环境
- 拦截记录的日志信息
通过使用 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)
}
}
- 构造函数接收一个额外的参数,默认为空
- 可以发送给监听器
名为 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))
}
}
}
- 把监听器设为 testActor
- 像平常一样断言消息
如你所见,测试被大大简化了。 我们简单地传给 Greeter02 构造函数 Some(testActor),然后像平常一样断言发送给 testActor 的消息。
下一节中,我们将要看看两路消息,以及如何测试。