SilentActor Examples
我们从 SilentActor 开始。这是我们的第一个测试,我们通过使用 ScalaTest 来简要看看:
Listing 3.2 First test for the silent actor type
class SilentActor01Test extends TestKit(ActorSystem("testsystem"))
with WordSpec
with MustMatchers
with StopSystemAfterAll {
"A Silent Actor" must {
"change state when it receives a message, single threaded" in {
//Write the test, first fail
fail("not implemented yet")
}
"change state when it receives a message, multi-threaded" in {
//Write the test, first fail
fail("not implemented yet")
}
}
}
- 从 TestKit 扩展,提供用来测试的 actor 系统,
- WordSpec 提供了易于阅读的 DSL 来以 BDD 方式测试,
- MustMatchers 提供了易于阅读的断言,
- 确保系统在所有测试完成后停止,
- 以文字说明的方式写测试,
- 每个 'in' 描述一个特定的测试
上述代码是我们用来启动静默的 actor 测试的基本框架。
我们使用 WordSpec 方式的测试,因为这允许以文本说明的方式来写测试,这也会在测试运行时看到。 在上述代码中,我们用一个声称“当收到消息时,改变内部状态”的测试,为静默 actor 类型创建了说明。 现在因为还没有实现,它总是失败,如在”红-绿-重构“风格中预期的一样。以”红-绿-重构“风格中,你先让 测试失败(红),然后实现代码,让它通过(绿),之后你再重构代码,令其更优雅。 首先我们在单线程环境中测试静默 actor。 既然我们想以后测试在多线程环境中是否工作正常,我们就包含了 TestKit。如果你只想使用 TestActorRef,这不是必需的。 下边我们定义了一个空的 Actor,会让测试失败:
Listing 3.3 第一个静默 actor 类型的会失败的实现
class SilentActor extends Actor {
def receive = {
case msg => ()
}
}
- 压制消息,不保存任何内部状态
现在首先来写一个测试,给静默 actor 发送一个消息,检查是不是改变了内部状态。 我们得写 SilentActor actor 来让测试能通过,还需要写叫做 SilentActorProtocol 的对象。 对象包含所有的 SilentActor 支持的消息,像后边所看到的一样,这是一个把相关的消息归类的很好的方法。
Listing 3.4 单线程测试内部状态
"change internal state when it receives a message, single" in {
import SilentActorProtocol._
val silentActor = TestActorRef[SilentActor]
silentActor ! SilentMessage("whisper")
silentActor.underlyingActor.state must (contain("whisper"))
}
- 引入消息
- 创建单线程测试用的 TestActorRef
- 拿到底层的 actor, 检查其状态
这是典型 TDD 场景的最简版:触发,然后检查状态改变。 现在来写 SilentActor actor:
Listing 3.5 SilentActor 实现
object SilentActorProtocol {
case class SilentMessage(data: String)
case class GetState(receiver: ActorRef)
}
class SilentActor extends Actor {
import SilentActorProtocol._
var internalState = Vector[String]()
def receive = {
case SilentMessage(data) =>
internalState = internalState :+ data
}
def state = internalState
}
}
- 'protocol' 把相关信息保存在一起
- SilentActor 可以处理的消息类型
- 状态保存在一个 Vector 中,每个消息都加到这个 Vector 中
- 状态方法返回创建的 Vector
因为返回的列表是不可变的,测试不能改变列表,检查预期结果的时候就会有问题。 设置/更新变量 internalState 完全安全,因为 Actor 防止被多线程访问。 一般优先选择 vars 配合不可变数据结构是好习惯,而不是 vals 配合可变数据结构。
现在我们来看看这个测试的多线程版,你会看到我们需要改变的代码很少。 就像单线程版中,我们添加状态方法来使得可以测试 actor,我们也需要增加一些代码 让多线程版本也能测试。
Listing 3.6 内部状态的多线程测试
"change internal state when it receives a message, multi" in {
import SilentActorProtocol._
val silentActor = system.actorOf(Props[SilentActor], "s3")
silentActor ! SilentMessage("whisper1")
silentActor ! SilentMessage("whisper2")
silentActor ! GetState(testActor)
expectMsg(Vector("whisper1", "whisper2"))
}
- 把相关消息保存在一起的 'protocol'
- 测试系统用来创建 actor
- 把消息加到协议中来获取状态
- 用来检查发送给了 testActor 什么消息
多线程测试使用名为 "testsystem" 的 ActorSystem 来创建 SilentActor actor。 ActorSystem 是 TestKit 的一部分。 因为当使用多线程 actor 系统时,我们不能访问 actor 实例,我们需要用另一种方法来查看状态改变。 因为这个原因,GetState 消息加了进来,它引用 ActorRef。 TestKit 有一个 testActor,你可以用来接收期望的消息。 我们添加的 GetState 方法如此(TODO:简单?还是什么),我们可以直接让 SilentActor 发送内部状态。 那样我们可以调用 expectMsg 方法,它期待一个消息会发送到 testActor,判断这个消息,在这里,消息是一个包含所有数据字段的 Vector。
侧边栏: 方法 expectMsg* 的时间设置
TestKit 有多个版本的 expectMsg 和其他方法来判断消息。 所有这些方法在一定时间内等待一个消息,否则就超时并抛出异常。 超时有默认值,可以通过配置中的 "akka.test.single-expect-default" 来修改。 有一个伸缩系数可以计算超时的实际时间(正常设为 1,表示超时没有伸缩)。 它的目的是为计算能力各不相同的计算机提供各种分级方法,使得在较慢的机器上, 我们需要等的时间长一点(开发者在速度很快的工作站上测试后,提交到较难的持续集成服务器上, 然后测试失败了,这很常见)。 每个机器可以配置需要达到的参数(配置的更多细节参见第4章)。 最大超时时间也可以通过函数方法直接设置,但是最好只使用配置的值,如果必要时,通过修改配置来 改变这个影响所有测试的值。
现在我们需要的是还能处理 GetState 消息的静默的 actor 的代码:
Listing 3.7 SilentActor 实现
object SilentActorProtocol {
case class SilentMessage(data: String)
case class GetState(receiver: ActorRef)
}
class SilentActor extends Actor {
import SilentActorProtocol._
var internalState = Vector[String]()
def receive = {
case SilentMessage(data) =>
internalState = internalState :+ data
case GetState(receiver) => receiver ! internalState
}
}
- 为了测试目的,加了 GetState 消息
- 内部状态发送给 GetStatef 中的 ActorRef
内部状态发送回 GetState 消息中的 ActorRef,本例中的 ActorRef 会是 testActor。 因为内部状态是个不变的 Vector,这是完全安全的。 这就是 SilentActor 类型:单线程和多线程变型。 使用这些方法,我们可以构建大多数程序员都非常熟悉的测试:可以检查状态变化, 变化时可以运用 TestKit 中的一些工具来判断。