簡體   English   中英

Akka Actors單位測試假人

[英]Akka Actors unit testing for dummies

我是Akka和Scala的新手,我來自一個非同時發生的世界。 可能我做了很多錯事,我會很感激反饋,即使它與問題無關。

我正在用Akka和Scala做一個簡單的聊天應用程序。 我通過“打字功能”開始(bc業務要求)...這是whatsapp或tellegram中的典型功能“John正在鍵入消息”。

我使用兩種演員類型對它進行了建模:Talkers和Conversation,我想對我的Conversation actor進行單元測試。 My Conversation演員看起來像這樣:

object Conversation {
  def props(conversationId: UUID, talkers: List[ActorRef])(out: ActorRef) = Props(new Conversation(conversationId, talkers))

  case class Typing(talkerId: TalkerId)
}

class Conversation(conversationId: UUID, talkers: List[ActorRef]) extends Actor with ActorLogging {
  def receive = LoggingReceive {
    case Typing(talkerId) =>
      // notify all talkers that a talker is typing
      // @TODO don't notify user which is typing
      talkers foreach {talker: ActorRef => talker ! InterlocutorTyping(talkerId)}
 }
}

我想,到現在為止很簡單。 所以,在Scala和Akka開始編碼之前,我測試過這個:

  • 我得到了我的對話演員
  • 我嘲笑說話者
  • 我發送一條消息給我的演員打字
  • 我希望通知說話者

我真的不知道它是否是Scala和Akka的正確方法。 我的測試(使用scalatest)看起來像這樣:

"Conversation" should {
"Notify interlocutors when a talker is typing" in {
  val talkerRef1 = system.actorOf(Props())
  val talkerRef2 = system.actorOf(Props())

  val talkerRef1Id = TalkerIdStub.random

  val conversationId = UUID.randomUUID()

  val conversationRef = system.actorOf(Props(classOf[Conversation], conversationId, List(talkerRef1, talkerRef2)))

  // should I use TestActorRef ?

  conversationRef ! InterlocutorTyping(talkerRef1Id)

  // assert that talker2 is notified when talker1 is typing
}
}
  1. 我應該使用TestActorRef嗎? 我應該使用TestProbe()(我讀過這是用於集成測試)

  2. 如何創建Talker模擬? 這種方法是否正確?

  3. 向我的對話演員注入一個談話者列表是正確的嗎?

我搜索了文檔,但我認為有很多太舊了,我不確定代碼示例是否仍然可用。

謝謝你的時間,並抱歉這個noob問題:=)

確實,Akka的測試情況至少可以說有點令人困惑。

在Akka中,通常有兩種測試,同步和異步,有些人稱之為“單元”和“集成”測試。

  • '單元測試'是同步的,你直接測試接收方法而不需要actor系統等。在你的情況下,你想要模擬List[Talkers] ,調用你的receive方法並驗證是否調用了send方法。 您可以使用new Conversation(mockTalkers)直接實例化您的actor,在這種情況下,不必使用TestActorRef 對於模擬 ,我推薦ScalaMock

  • “集成測試”是異步的,通常會測試多個演員一起工作。 這是您繼承TestKit ,實例化TestProbe以充當您的談話者,使用一個向Conversation actor發送消息,並驗證另一個接收InterlocutorTyping消息。

這取決於您認為哪種測試是合適的。 我的個人意見是,除非你的actor中有復雜的內部bevaviour,否則你應該跳過同步測試並直接進行異步('集成')測試,因為這將涵蓋你可能會錯過的更棘手的並發邊緣情況。 這些也更像是“黑盒子”,因此隨着您的設計發展變得不那么敏感。

doc頁面上的更多詳細信息和代碼示例。

最后我做了這個(有比問題更多的功能):

object Conversation {
  def props(conversationId: UUID)(out: ActorRef) = Props(new Conversation(conversationId))

  case class TalkerTyping(talkerId: TalkerId)
  case class TalkerStopTyping(talkerId: TalkerId)

  case class Join(talker: ActorRef)
  case class Leave(talker: ActorRef)
}

class Conversation(conversationId: UUID) extends Actor with ActorLogging {

  var talkers : ListBuffer[ActorRef] = ListBuffer.empty

  val senderFilter = { talker: ActorRef => talker != sender() }

  def receive = LoggingReceive {
    case Join =>
      talkers += sender()

    case Leave =>
      talkers -= sender()

    case TalkerTyping(talkerId) => // notify all talkers except sender that a talker is typing
      talkers filter senderFilter foreach { talker: ActorRef => talker ! InterlocutorTyping(talkerId) }

    case TalkerStopTyping(talkerId) => // notify all talkers except sender that a talker has stopped typing
      talkers filter senderFilter foreach { talker: ActorRef => talker ! InterlocutorStopTyping(talkerId) }
  }
}

我的測試:

class ConversationSpec extends ChatUnitTestCase("ConversationSpec") {

  trait ConversationTestHelper {
    val talker = TestProbe()
    val anotherTalker = TestProbe()
    val conversationRef = TestActorRef[Conversation](Props(new Conversation(UUID.randomUUID())))
    val conversationActor = conversationRef.underlyingActor
  }

  "Conversation" should {
    "let user join it" in new ConversationTestHelper {
      conversationActor.talkers should have size 0

      conversationRef ! Join

      conversationActor.talkers should have size 1
      conversationActor.talkers should contain(testActor)
    }
    "let joining user leave it" in new ConversationTestHelper {

      conversationActor.talkers should have size 0
      conversationRef ! Join
      conversationActor.talkers should have size 1
      conversationActor.talkers should contain(testActor)

      conversationRef ! Leave
      conversationActor.talkers should have size 0
      conversationActor.talkers should not contain testActor
    }
    "notify interlocutors when a talker is typing" in new ConversationTestHelper {

      val talker1 = TestProbe()
      val talker2 = TestProbe()

      talker1.send(conversationRef, Join)
      talker2.send(conversationRef, Join)

      val talker2Id = TalkerIdStub.random

      talker2.send(conversationRef, TalkerTyping(talker2Id))

      talker1.expectMsgPF() {
        case InterlocutorTyping(talkerIdWhoTyped) if talkerIdWhoTyped == talker2Id => true
      }
      talker2.expectNoMsg()
}
"notify interlocutors when a talker stop typing" in new ConversationTestHelper {

  val talker1 = TestProbe()
  val talker2 = TestProbe()

  talker1.send(conversationRef, Join)
  talker2.send(conversationRef, Join)

  val talker2Id = TalkerIdStub.random

  talker2.send(conversationRef, TalkerStopTyping(talker2Id))

  talker1.expectMsgPF() {
    case InterlocutorStopTyping(talkerIdWhoStopTyping) if talkerIdWhoStopTyping == talker2Id => true
  }
  talker2.expectNoMsg()
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM