2015-11-18 1 views
3

Я новичок в Akka и Scala, и я родом из несовместимого мира. Вероятно, я делаю много чего не так, я буду признателен за отзывы, даже если это не связано с вопросом.Akka Единичное тестирование для манекенов

Я занимаюсь простым чатом с Аккой и Скалой. Я начал (бизнес-требования bc) с помощью «ввода функции» ... это типичная функция whatsapp или tellegram «Джон печатает сообщение».

Я смоделировал его, используя два типа актеров: Talkers and 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 и Акку я испытал это, как:

  • Я получаю мой разговор актер
  • Я издеваться болтунов
  • Я отправить сообщение Typing к моему актеру
  • я ожидаю, что болтуны должен быть извещен об этом:

Я действительно не знаю, правильно ли это в 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 mocks? Правильно ли этот подход?

  3. Правильно ли вводить список разговоров в мой разговор? Актер?

Я искал документацию, но я думаю, что слишком много старых, и я не уверен, что примеры кода по-прежнему функциональны.

Благодарим Вас за время, ребята, и жаль об этом нуб вопрос: =)

ответ

2

Это правда, что ситуация тестирования в Акку немного сбивает с толку, мягко говоря.

В Акке обычно у вас есть два типа испытаний, синхронных и асинхронных, которые некоторые люди называют «единицей» и «интеграцией».

  • «Юнит-тесты» являются синхронными, вы сразу испытать метод приема, не требуя системы актера и т.д. В вашем случае, вы хотели бы, чтобы дразнить List[Talkers], вызовите метод receive и проверить, что метод отправки называется. Вы можете напрямую создать экземпляр своего актера с помощью new Conversation(mockTalkers), в этом случае нет необходимости использовать TestActorRef. Для насмешек я рекомендую ScalaMock.

  • «Тесты интеграции» являются асинхронными и, как правило, тестируют более одного действующего лица.Здесь вы наследуете TestKit, создайте TestProbe s, чтобы действовать как ваши болтуны, используйте его для отправки сообщения игроку Conversation и убедитесь, что другой получает сообщение InterlocutorTyping.

Это зависит от вас, какой тип теста вы считаете уместным. Мое личное мнение состоит в том, что, если у вас нет сложного внутреннего участия в вашем акторе, вы должны пропустить синхронные тесты и пойти прямо на асинхронные тесты («интеграция»), поскольку это будет охватывать более сложные случаи краха параллелизма, которые вы могли бы пропустить. Это также более «черный ящик» и поэтому менее чувствителен к изменениям, когда вы меняете свой дизайн.

Подробнее и образцы кода на doc page.

+0

Привет Jazmit, спасибо за Ваш ответ. Конечно, я знаю, что мой тест глупый. И, конечно же, я сделаю некоторые интеграционные тесты, но я начинаю с простых вещей: P Итак, на ваш взгляд, для выполнения этого фиктивного теста я должен высмеивать своих болтунов, должен ли я использовать насмешливую структуру? Вы мне порекомендовали? Есть ли способ сделать это с Teskit напрямую? Еще раз спасибо за ваше время :) – SergiGP

+0

Я бы рекомендовал использовать ScalaMock, добавил ссылку на ответ. TestKit предназначен для асинхронных тестов, вам это не понадобится для простых тестов синхронизации. – jazmit

+0

Возможно, вам повезло, если вы издеваетесь над актером Refs, используя TestProbe, но рано или поздно вам понадобится фальшивая каркас для других зависимостей – 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() 
    } 
    } 
}