如何在不使用Thread.sleep的情况下对发送消息的Akka actor进行单元测试

时间:2013-08-21 17:46:04

标签: unit-testing scala concurrency akka

我有一个针对Akka演员的Scala单元测试。该actor旨在轮询远程系统并更新本地缓存。演员设计的一部分是它在处理或等待上次轮询的结果时不会尝试轮询,以避免在远程系统经历减速时泛滥。

我有一个测试用例(如下所示),它使用Mockito来模拟慢速网络呼叫,并检查当告知演员更新时,它将不再进行另一次网络呼叫,直到当前完成。它通过验证与远程服务缺乏交互来检查演员是否还没有进行另一次调用。

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

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

  4. 当前演员看起来像这样:

    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
    }
    

1 个答案:

答案 0 :(得分:4)

我们现在选择解决这个问题的方法是将一个观察者的等价物注入演员(捎带现有的记录器,这个记录器没有包含在问题列表中)。然后,演员可以告诉观察者它何时从各种状态转变。在测试代​​码中,我们执行一个操作,然后在继续并进行断言之前等待来自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

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