テストしないで大丈夫? AkkaのActorを単体テストする


## はじめに

この記事では、AkkaのActorを用いたプログラムの単体テスト方法について簡単に説明します。

Actorをテストするのは通常のオブジェクトをテストするよりも下記の理由により困難です。

1. タイミング
メッセージは非同期に送信されるので、単体テストでいつ検査を実行したらよいかを知ることは難しいことです。

2. 非同期
Actorは並列のスレッドで実行されます。これはシングルスレッドで実行されるプログラムよりも検査するのが難しいです。

3. ステートレス
Actorは内部の状態を隠蔽しています。通常この内部状態に直接アクセスすることはできないので、単体テストが困難になります。

4. 強調と結合
複数のActorと結合して実行されます。これをテストするにはActor間のメッセージのやりとりを盗み聞きする必要があります。

この面倒なテストを、Akka-TestKitを使うことによって軽減します。

## Akka-TestKit

Akka-TestKitによって下記のテストが可能になります。

1. シングルスレッド単体テスト
Actorインスタンスは通常直接アクセスできません。testkitはTestActorRefを提供していて、Actorインスタンスへのアクセスができるようになります。これによって、Actorに定義されているメソッドを呼びだすことができます。シングルスレッドで実行することにより、通常のオブジェクトと同様にテストを記述できます。

2. マルチスレッド単体テスト
testkitは同様に、TestKitと、TestProbeクラスを提供しています。TestProbeを使うと、Actorからの返信をインスペクトすることができます。TestKitクラスは、期待しているメッセージかどうかを確かめることができます。Actorは通常のマルチスレッド環境で実行されます。

3. 複数JVMテスト
Akkaは同様に複数のJVMのテストも可能にします。これは、リモートのアクターシステムをより簡単にテストできるようにする仕組みです。


## TestActorRef

TestActorRefは同期テストを可能にします、通常Actorのインスタンスは取得できませんが、TestActorRefを用いるとインスタンスが取得できます。

```scala
import akka.testkit.TestActorRef
 
val actorRef = TestActorRef[MyActor]
val actor = actorRef.underlyingActor
```

## サンプルコード

### 必要インポート
```scala
import org.scalatest.{ Suite, BeforeAndAfterAll, WordSpecLike, MustMatchers }
import akka.testkit.{ TestKit, TestActorRef }
import akka.actor.{ Props, ActorSystem, Actor, ActorRef }
```

### アクターシステムをテスト後に停止するトレイト

SBTなどでテストを実行すると、アクターは動作しつづけてしまうので、テスト後に停止するようにします。

```scala
trait StopSystemAfterAll extends BeforeAndAfterAll {
  this: TestKit with Suite =>
  override protected def afterAll() {
    super.afterAll()
    system.shutdown()
  }
}
```

### プロトコル

ActorではActorとのメッセージのやりとりをプロトコルとしてグループ化しておく習慣があるようです。

```scala
object ActorProtocol {
  case class Message(data: String)
  case class GetState(receiver: ActorRef)
}
```

### アクター

varを使っていますが、アクターは同時に動作することはないのでロックなどは気にしなくても問題ありません。

```scala
class MessageActor extends Actor {
  import ActorProtocol._

  var internalState = Vector[String]()

  def receive = {
    case Message(data) => internalState = internalState :+ data
    case GetState(receiver)  => receiver ! internalState
  }

  def state = internalState
}

```

### シングルスレッドのスペック

TestKitをextendsすることにより、testActorやexpectMsgアサーションなどの機能が使えるようになります。

```scala
class ActorSpec extends TestKit(ActorSystem("test"))
    with WordSpecLike
    with MustMatchers
    with StopSystemAfterAll {

  "A actor" must {
    "change state when it receives a message" in {
      import ActorProtocol._

      val actor = TestActorRef[MessageActor]

      actor ! Message("ハロー")
      actor.underlyingActor.state must (contain("ハロー"))
    }
  }
}

```

### マルチスレッドのスペック

testActorをアクターに渡すことにより、testActorで受け取ったメッセージを、expectMsgで確認することができるようになります。

```scala
class ActorSpec extends TestKit(ActorSystem("test"))
    with WordSpecLike
    with MustMatchers
    with StopSystemAfterAll {

  "A actor" must {
    "change state when it receives a message, multi threaded" in {
      import ActorProtocol._

      val actor = system.actorOf(Props[MessageActor], "s3")

      actor ! Message("ハロー")
      actor ! Message("はろ〜")

      actor ! GetState(testActor)

      expectMsg(Vector("ハロー", "はろ〜"))
    }
  }
}

```

## 送信されたメッセージを取得する

```scala
val message = receiveOne(1 second)
```

## リスナーでメッセージを受け取る
```scala
class Sender(listener: Option[ActorRef] = None) extends Actor {
  def receive = {
    case Send(msg) => 
      listener.foreach { _ ! msg }
  }
}
```

```scala
val sender = system.actorOf(Props(Sender(testActor)))
sender ! Message("test")
receiveOne(1 second)
```


### おわりに

TestKitでは、expectMsgの様なアクターをテストするための便利なメソッドが多数用意されていますので詳しいことはScalaDocを参照して下さい。http://doc.akka.io/api/akka/2.0/akka/testkit/TestKit.html