Akka Actors单位测试假人

时间:2015-11-18 16:24:21

标签: scala unit-testing akka scalatest akka-testkit

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

我正在与Akka和Scala一起做一个简单的聊天应用程序。我开始(bc业务要求)"输入功能" ...它是whatsapp或tellegram中的典型功能" John正在键入消息"。

我使用两种演员类型对其进行建模:谈话者和对话,我想对我的对话演员进行单元测试。 My Conversation演员看起来像这样:

EnronMail.java

我认为,到现在为止非常简单。所以,在Scala和Akka开始编码之前,我测试了这个:

  • 我得到了我的对话演员
  • 我嘲笑谈话者
  • 我发送一条消息给我的演员打字
  • 我希望通知谈话者

我不知道Scala和Akka是否采用了正确的方法。我的测试(使用scalatest)看起来像这样:

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)}
 }
}
  1. 我应该使用TestActorRef吗?我应该使用TestProbe()(我读过这是用于集成测试)

  2. 如何创建Talker模拟?这种方法是否正确?

  3. 向我的对话演员注入一个谈话者名单是对的吗?

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

    感谢您的时间,并对这个noob问题感到抱歉:=)

2 个答案:

答案 0 :(得分:2)

至少可以说阿卡的测试情况有点令人困惑。

在Akka,一般来说,你有两种测试,同步和异步,有些人称之为' unit'和'整合'测试

  • '单元测试'是同步的,你直接测试接收方法而不需要一个actor系统等。在你的情况下,你想要模拟List[Talkers],调用你的receive方法并验证是否调用了send方法。您可以使用new Conversation(mockTalkers)直接实例化您的演员,在这种情况下,使用TestActorRef不是必需的。对于模拟,我建议ScalaMock

  • '整合测试'是异步的,通常测试多个演员一起工作。这是您继承TestKit,实例化TestProbe以充当您的谈话者,使用一个向Conversation演员发送消息,并验证另一个接收InterlocutorTyping的地方消息。

您认为哪种测试适合您。我的个人意见是,除非你的演员有内心的复杂内容,否则你应该跳过同步测试并直接进行异步('集成')测试,因为这将涵盖更多棘手的并发边缘情况。否则错过。这些也是更多的黑盒子'因此,在改进设计时对变化不那么敏感。

doc page上的更多详细信息和代码示例。

答案 1 :(得分:2)

最后我做了这个(比问题还要多一些功能):

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