2015-11-18 117 views
3

我是Akka和Scala的新手,我來自非併發世界。也許我做了很多錯事,我會很感激反饋,即使它與問題無關。Akka Actors單元測試模型

我正在做一個簡單的聊天應用程序與阿卡和斯卡拉。我通過「輸入功能」開始了(bc業務需求)......這是whatsapp或tellegram中的典型特徵「John正在輸入消息」。

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

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和阿卡編碼我曾測試此類似:

  • 我得到我的對話演員
  • 我嘲笑健談
  • 我發送消息打字我的演員
  • 我希望健談應該被通知

我真的不知道它是否是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. 我該如何創建發言者嘲笑?這種方法是否正確?

  3. 向我的對話注入一個Talker列表是正確的Actor?

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

謝謝您的時間傢伙,而這個noob問題不好意思:=)

回答

2

這是真的,在阿卡檢測情況有點混亂,至少可以說。

在Akka一般你有兩種測試,一些人認爲是'單元'和'集成'測試的同步和異步測試。

  • 「單元測試」是同步的,你直接測試接收方法,而不需要一個演員系統等。在你的情況,你會想嘲笑List[Talkers],請致電receive方法和驗證發送方法叫做。你可以直接實例化你的演員new Conversation(mockTalkers),在這種情況下不需要使用TestActorRef。對於嘲笑,我推薦ScalaMock

  • 「集成測試」是異步的,通常測試多個參與者一起工作。這是您繼承TestKit,實例化TestProbe s作爲您的談話人,使用其中一個發送消息到Conversation actor,並驗證另一個收到InterlocutorTyping消息。

這取決於你認爲哪種測試是合適的。我個人的觀點是,除非你的演員中有內部複雜的事情,否則你應該跳過同步測試,直接進行異步('集成')測試,因爲這將覆蓋更多棘手的併發邊緣情況,否則你可能會錯過。這些也是「黑盒子」,隨着您的設計發展而變得更加敏感。

更多詳細信息和代碼示例在doc page

+0

嗨Jazmit,謝謝您的回答。當然,我知道我的測試是一個愚蠢的測試。當然,我也會做一些集成測試,但我從容易的東西開始:P因此,在你看來,爲了做這個虛擬測試,我應該嘲笑我的講話者,我應該使用一個模擬框架嗎?你推薦我一個嗎?有什麼方法可以直接使用Teskit來做到這一點?再次感謝您的時間:) – SergiGP

+0

我建議使用ScalaMock,添加一個鏈接到答案。 TestKit是用於異步測試的,你不需要它來進行簡單的同步測試 – jazmit

+0

你可能有些運氣使用TestProbe來嘲諷actor Refs,但遲早你需要嘲笑其他依賴的框架 – jazmit

1

最後我這樣做(有一些比這個問題更多的功能):

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() 
    } 
    } 
}