【问题标题】:How to unit test an Akka actor that sends a message to itself, without using Thread.sleep如何在不使用 Thread.sleep 的情况下对向自身发送消息的 Akka Actor 进行单元测试
【发布时间】:2013-08-21 17:46:04
【问题描述】:

我有一个针对 Akka 演员的 Scala 单元测试。 Actor 旨在轮询远程系统并更新本地缓存。 Actor 的部分设计是它不会在仍在处理或等待最后一次轮询的结果时尝试轮询,以避免在远程系统遇到减速时淹没远程系统。

我有一个测试用例(如下所示),它使用 Mockito 模拟慢速网络调用,并检查当参与者被告知更新时,它不会在当前调用完成之前进行另一个网络调用。它通过验证缺少与远程服务的交互来检查参与者没有再次调用。

我想取消对Thread.sleep 的调用。我想在每次测试运行中测试演员的功能而不依赖于等待硬编码的时间,这很脆弱,而且浪费时间。测试可以轮询或阻塞,等待条件,超时。这将更加健壮,并且不会在测试通过时浪费时间。我还有一个额外的约束,我想将用于防止额外轮询var allowPoll 的状态限制在PollingActor 的内部。

  1. 有没有办法强制等待,直到演员自己完成消息传递?如果有办法我可以等到那时再尝试断言。
  2. 是否有必要发送内部消息?我不能用线程安全的数据结构来维护内部状态,比如java.util.concurrent.AtomicBoolean。我已经这样做了,代码似乎可以工作,但我对 Akka 的了解不够,无法知道它是否不受欢迎——一位同事推荐了 self 消息样式。
  3. 是否有更好的、开箱即用的功能与相同的语义?然后我会选择集成测试而不是单元测试,但我不确定它是否能解决这个问题。

当前的演员看起来像这样:

class PollingActor(val remoteService: RemoteServiceThingy) extends ActWhenActiveActor {

  private var allowPoll: Boolean = true

  def receive = {
    case PreventFurtherPolling => {
      allowPoll = false
    }
    case AllowFurtherPolling => {
      allowPoll = true
    }
    case UpdateLocalCache => {
      if (allowPoll) {
        self ! PreventFurtherPolling

        remoteService.makeNetworkCall.onComplete {
          result => {
            self ! AllowFurtherPolling
            // process result
          }
        }
      }
    }
  }
}

trait RemoteServiceThingy {
  def makeNetworkCall: Future[String]
}

private case object PreventFurtherPolling
private case object AllowFurtherPolling

case object UpdateLocalCache

specs2 中的单元测试如下所示:

"when request has finished a new requests can be made" ! {
  val remoteService = mock[RemoteServiceThingy]
  val actor = TestActorRef(new PollingActor(remoteService))

  val slowRequest = new DefaultPromise[String]()

  remoteService.makeNetworkCall returns slowRequest

  actor.receive(UpdateLocalCache)
  actor.receive(UpdateLocalCache)
  slowRequest.complete(Left(new Exception))

  // Although the test calls the actor synchronously, the actor calls *itself* asynchronously, so we must wait.
  Thread.sleep(1000)

  actor.receive(UpdateLocalCache)

  there was two(remoteService).makeNetworkCall
}

【问题讨论】:

    标签: unit-testing scala concurrency akka


    【解决方案1】:

    我们现在选择解决此问题的方法是将等效的观察者注入参与者(搭载未包含在问题列表中的现有记录器)。然后,actor 可以告诉观察者它何时从各种状态转换。在测试代​​码中,我们执行一个操作,然后等待参与者的相关通知,然后继续进行断言。

    在测试中我们有这样的东西:

    actor.receive(UpdateLocalCache)
    
    observer.doActionThenWaitForEvent(
       { actor.receive(UpdateLocalCache) }, // run this action
       "IgnoredUpdateLocalCache" // then wait for the actor to emit an event
    }
    
    // assert on number of calls to remote service
    

    我不知道是否有更惯用的方式,这对我来说似乎是一个合理的建议。

    【讨论】:

      猜你喜欢
      • 2015-05-29
      • 1970-01-01
      • 2021-11-22
      • 1970-01-01
      • 2013-05-23
      • 2013-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多