单元与集成Akka测试

这是一系列关于同步客户端集成与异步系统(第六柱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类型的消息。 但是谁在发送该消息? 扩展并混合TestKitImplicitSender创建一个testActor ,它将成为消息发送者。 TestKit公开了一些方法,例如expectMsg以允许检查该testActor的邮箱。 within行为eventuallyScalatest一样,但功能更强大。 例如,如文档所述:

应当注意,如果该块的最后一个消息接收断言是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值