Akka's context.become странное поведение

У меня странное поведение при использовании context.become в PersistentActor (не уверен, что постоянство имеет какое-либо отношение к причине проблемы). Мой код выглядит примерно так:

class MyActor extends PersistentActor {

    import context._

    // Messages
    case object Start
    case object Ready
    case object MessageX

    // Events
    case object Started

    def recieveRecover: Receive = { /* Not relevant, I think */}

    def receiveCommand: Receive = idle

    def idle: Receive = {
        case Start => 
            persist(Started) { _ =>
                sender() ! Ready
                become(ready)
            }
    }

    def ready: Receive = {
        case MessageX => doSomething()
    }
}

И у меня есть два теста в одном файле MyActorSpec. Первый просто проверяет состояние «ожидания», а второй проверяет состояние «готово»:

"Test A" in {
    val actorRef = system.actorOf( MyActor.props(), "test-A-actor" )

    actorRef ! Start

    expectMsg(Ready)
}

"Test B" in {
    val actorRef = system.actorOf( MyActor.props(), "test-B-actor" )

    actorRef ! Start

    expectMsg(Ready) /* It fails here because for some reason the actorRef 
    was created with its 'receiveCommand' block equal to 
    the 'ready' block and not equal to the 'idle' block as its suppossed to. 
    So it timeouts here waiting for the message which is not handled in
    the 'ready' block */

    actorRef ! MessageX

    testSomethingAboutTheMessageX() 
}

Если я запускаю оба теста, первый завершается успешно, а второй завершается ошибкой в ​​ожидании сообщения о готовности (как объяснено в комментарии ко второму тесту). Если я запускаю только второй тест, он проходит. Итак, я не уверен, что сделал что-то не так, когда определял актера.

ОБНОВЛЕНИЕ: я попытался удалить постоянство для события Started (часть persist(Started)), как было предложено, и тесты сработали, как и ожидалось (оба актера были созданы в состоянии idle). Итак, что происходит, так это то, что события в первом экземпляре актора сохраняются, а затем второй экземпляр актора воспроизводит их, и это потому, что экземпляры актора создаются с одним и тем же persistenceId (DOH!). Итак, способ сделать тесты независимыми состоял в том, чтобы просто создать экземпляр каждого актора с разными persistenceId.


person miguel    schedule 02.11.2014    source источник
comment
Тот факт, что результат меняется в зависимости от того, какие тесты выполняются, говорит о том, что ваши тесты не являются независимыми. Возможно, попробуйте создать новый system в каждом тесте, чтобы убедиться, что нет общего состояния?   -  person millhouse    schedule 03.11.2014


Ответы (2)


Происходит то, что события первого экземпляра актора сохраняются, а затем второй экземпляр актора воспроизводит их, и это потому, что экземпляры актора создаются с одним и тем же persistenceId (DOH!). Итак, чтобы сделать тесты независимыми, нужно просто создать экземпляр каждого актора с разными persistenceId.

person miguel    schedule 04.11.2014

Это может быть вызвано тем, что Specs2 запускает все тесты одновременно, потому что его «последовательный» аргумент имеет значение по умолчанию «false».

Чтобы заставить все тесты выполняться один за другим, укажите sequence в верхней части спецификации, примерно так:

class MyActorSpec extends Specification {

  // Run the tests sequentially
  sequential


  "MyActor" should {
    "Test A" in {
          // test goes here
    }   
    "Test B" in {
          // test goes here
    }   
  }
}

Взгляните на http://etorreborre.github.io/specs2/guide/org.specs2.guide.Runners.html для получения подробной информации.

person Denis Makarenko    schedule 03.11.2014
comment
хм, нет, я не думаю, что это он. Я использую scalatest и запускаю тесты последовательно, и у меня такое же поведение. Я думаю, что это не проблема, потому что, даже если они работают параллельно, они используют разные экземпляры акторов, поэтому не должно быть перекрытий. - person miguel; 04.11.2014
comment
Это правда, но оба теста используют одну и ту же систему акторов, и один и тот же неявный актор-отправитель используется для получения ответа от ваших акторов в expectMsg(). Таким образом, у вас может возникнуть состояние гонки, когда ответ Ready, отправленный субъектом B, получен в тесте A. - person Denis Makarenko; 04.11.2014
comment
Я протестировал добавление предложения case Start => println("Started in ready!") в блок readyrecive, и второй тест распечатывает его. Таким образом, проблема заключается не в том, что первый тест получает сообщение Ready и, таким образом, вызывает тайм-аут во втором тесте, а вместо этого второй тест создает актера, для которого его блок приема установлен на ready, а не на idle. - person miguel; 04.11.2014
comment
Хм, я сомневаюсь, что проблема в be(), но вы можете легко проверить, находится ли актор в состоянии «готов», когда он получает сообщение Start, регистрируя все нераспознанные сообщения следующим образом: def ready: Receive = { case MessageX => doSomething () case m : Any =› println(sПолучено сообщение в состоянии готовности: $m) } - person Denis Makarenko; 04.11.2014
comment
Кроме того, попробуйте временно закомментировать строку persist(Started) { _ =› (и закрывающую скобку тоже), чтобы исключить ее влияние на отправку сообщения Ready. - person Denis Makarenko; 04.11.2014
comment
Другая идея заключается в том, чтобы установить точку останова на становление (готово) и посмотреть, сработает ли она дважды. - person Denis Makarenko; 04.11.2014
comment
Я не использовал PersistentActor, но если обратный вызов, переданный в persist(), вызывается асинхронно, sender() может вернуть неверное значение. т.е. не отправитель исходного сообщения, а отправитель последнего обработанного сообщения, который не обязательно один и тот же. Таким образом, ответ может быть отправлен в неправильное место назначения. Я сомневаюсь, что это происходит в ваших тестах, но это следует учитывать при запуске в нетестовой среде. - person Denis Makarenko; 04.11.2014
comment
спасибо, я попытался удалить настойчивость, и я думаю, что у меня это получилось. Я думаю, что persist() работает синхронно (есть persistAsync(), но я его не использую), так что проблема не в этом. - person miguel; 04.11.2014