这是一系列关于同步客户端集成与异步系统(第六柱1, 2, 3, 4, 5 )。 在这里,我们将看到如何使用不同的测试样式来测试Akka演员 。
单元与集成测试
如今,每个人都同意著名的测试金字塔 :
很难达成的协议是关于集成,单元,功能或接受的含义。 这是合理的,因为根据语言,体系结构和领域的不同,应用程序的结构也不同。 我将尽力提炼一些精华:
- 单位:这里的关键是隔离。 有些人谈论隔离的生产代码,例如,一个类中的单个函数或一堆方法(一个公共,另一个私有)。 其他一些人谈论隔离测试或独立测试,即由于无法访问任何共享资源而可以并行执行的测试。 第三种观点是,单元测试执行应该是同步的,没有任何并发问题。 那是Akka的观点 :
在不涉及参与者模型的情况下测试隔离的代码段,这意味着没有多个线程; 这意味着与事件的顺序有关的完全确定性的行为,而没有并发问题,在下文中将被称为单元测试。
- 集成:这种测试通常涉及行使多个类,模块或服务。 使用Akka,我们将测试多个参与者,但关键概念是,我们将使用多线程调度:
测试(多个)封装的参与者,包括多线程调度; 这意味着事件的顺序不确定,但是可以避免参与者模型对并发问题的关注,在下文中将其称为集成测试。
Akka单元测试
当我们对测试对象进行单元化时,我们会寻找:
- 检查返回的值。
- 验证对协作者的呼叫。
- 检查内部状态。 在某些情况下,这可能是一种气味。
在Akka中,返回值的概念略有不同。 Akka专注于消息而不是方法调用。 检查返回的值涉及两个参与者和两个消息。 如果被测演员的合作者也是演员,则验证呼叫将涉及两个演员和两个消息。 如果我们使用多线程调度程序进行调度,则此方案将超出我们的单元测试定义。 然后让我们集中精力测试内部状态。
Akka actor被完全封装,唯一的通信渠道是邮箱。 TestActorRef
由Akka提供,因此我们可以访问actor的内部并对其进行单元测试。 它的一种特殊形式是TestFSMRef
,它使我们能够测试有限状态机 。 让我们从平台上看一个例子:
"Item FSM" should {
"move into active state when it receives an item" in {
val fsm = TestFSMRef(new ItemFSM(itemReportedProducer, itemDeletedBus))
fsm.stateName shouldBe Idle
fsm.stateData shouldBe Uninitialized
fsm ! ItemReported(itemId)
within(200 millis){
fsm.stateName shouldBe Active
fsm.stateData.asInstanceOf[ItemsToBeDeleted].items shouldBe items
}
}
}
如您所见, TestFSMRef
包装了我们要测试的actor并公开了其内部状态。 该包装器还有其他有用的方法,例如以编程方式设置状态或操纵FSM计时器。
我想分享一些让我第一次有些困惑的东西,但了解这一点很重要。 我们需要回想一下,在Akka中进行单元测试意味着使用一个单一线程来实现确定的事件顺序。 默认情况下, TestActorRef
使用CallingThreadDispatcher
。 该调度程序仅在当前线程上运行调用,因此我们可以进行单元测试,以检查具有此样式的actor的返回值。
class EchoActor extends Actor {
override def receive = {
case message ⇒ sender() ! message
}
}
"send back messages unchanged" in {
import akka.pattern.ask
import scala.concurrent.duration._
implicit val timeout = Timeout(5 seconds)
val actorRef = TestActorRef(new EchoActor)
val future = actorRef ? "hello world"
val Success(result: String) = future.value.get
result should be("hello world")
}
让我们看看如何使用Akka中的集成测试样式来测试此场景和其他场景。
Akka集成测试
Akka提供用于集成测试的TestKit
类。 让我们看看使用该类编写的测试之一:
class ItemFSMSpec() extends TestKit(ActorSystem("ItemFSMSpec")) with ImplicitSender
"send a complete message to the original sender when one deleted item is received and there are no more messages pending" in {
val worker = TestFSMRef(new ItemFSM(itemReportedProducer, itemDeletedBus))
worker ! ItemReported(itemId)
itemDeletedBus.publish(MsgEnvelope(item.partitionKey, ItemDeleted(item)))
within(200 millis) {
expectMsg(Result(Right()))
}
}
在此特定测试中,我们对FSM正在分派给发送方的消息感兴趣。 让我们再看一下这一行:
worker ! ItemReported(itemId)
在这里,我们说的是:向val worker
分配的actor发送ItemReported
类型的消息。 但是谁在发送该消息? 扩展并混合TestKit
和ImplicitSender
创建一个testActor
,它将成为消息发送者。 TestKit
公开了一些方法,例如expectMsg
以允许检查该testActor
的邮箱。 within
行为eventually
像Scalatest一样,但功能更强大。 例如,如文档所述:
应当注意,如果该块的最后一个消息接收断言是ExpectNoMsg或receiveWhile,则跳过内部的最终检查,以避免由于唤醒延迟而导致的误报。 这意味着尽管单个包含的断言仍使用最大时间限制,但在这种情况下,整个块可能会花费任意更长的时间。
另一个有趣的类是TestProbe
。 如果我们在集成测试中有多个参与者,并且我们想验证不同参与者之间发送的不同消息,那么使用单个testActor
可能会造成混淆。 即使使用单个TestProbe
在某些情况下,使用TestProbe
提高可读性:
"flush a FSM when it receives a Failed message" in {
val fsmProbe = TestProbe()
val actorFactory: (ActorContext, ActorRef) => ActorRef = (context, self) => fsmProbe.ref
val coordinator = TestActorRef(ItemReportedCoordinator.props(actorFactory))
fsmProbe.send(coordinator, Result(Left(Exception("Some exception message"))))
fsmProbe.expectMsg(FlushItemFSM)
}
在上一篇文章中,我们介绍了actor工厂以创建actor池。 在这里, TestProbe
可以帮助您更清楚地了解谁在发送和期待消息。
摘要
可测试性是Akka的主要资产之一。 最大的挑战是了解我们要测试的内容:参与者的内部业务逻辑或不同参与者之间的消息异步交换。
第一部分 | 第2部分 | 第3部分 | 第4部分 | 第5部分
翻译自: https://www.javacodegeeks.com/2016/06/unit-vs-integration-akka-testing.html