5

我是 Akka 和 Scala 的新手,我来自一个非并发的世界。可能我做错了很多事情,即使与问题无关,我也会感谢反馈。

我正在用 Akka 和 Scala 做一个简单的聊天应用程序。我从“输入功能”开始(bc 业务需求)......这是whatsapp 或tellegram 中的典型功能“约翰正在输入消息”。

我使用两种演员类型对其进行了建模:Talkers 和 Conversation,并且我想对我的 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. 将谈话者列表注入我的谈话演员是正确的吗?

我搜索了文档,但我认为有很多太旧了,我不确定代码示例是否仍然有效。

谢谢你们的时间,对这个菜鸟问题感到抱歉:=)

4

2 回答 2

3

确实,Akka 中的测试情况至少可以说有点令人困惑。

在 Akka 中,通常你有两种测试,同步和异步,有些人称之为“单元”和“集成”测试。

  • “单元测试”是同步的,您可以直接测试接收方法而不需要演员系统等。在您的情况下,您可能想要模拟List[Talkers],调用您的receive方法并验证是否调用了发送方法。您可以使用 直接实例化您的演员new Conversation(mockTalkers),在这种情况下无需使用TestActorRef. 对于模拟,我推荐ScalaMock

  • “集成测试”是异步的,通常测试多个参与者一起工作。这是您继承的地方TestKit,实例化TestProbes 以充当您的谈话者,使用一个向参与者发送消息Conversation,并验证另一个是否接收到InterlocutorTyping消息。

这取决于您认为哪种测试是合适的。我个人的观点是,除非你的 actor 有复杂的内部行为,否则你应该跳过同步测试并直接进行异步(“集成”)测试,因为这将涵盖更棘手的并发边缘情况,否则你可能会错过。这些也更“黑盒”,因此随着您的设计发展而对变化不那么敏感。

文档页面上的更多详细信息和代码示例。

于 2015-11-18T17:01:33.643 回答
3

最后我做到了(有比问题更多的功能):

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()
    }
  }
}
于 2015-11-20T08:32:48.300 回答