Akka ActorのテストはTestKitをなるべく使おう


背景

アクターを使った部分のテストを書いていて、非同期処理でされることを意識していなかったせいでドハマリしたので、そのメモ

はまったパターン

ちょっとしたRestAPIを実装して、つぎのようなことをテストしたかったので、

  1. APIを呼び出し
  2. HogeActorにメッセージして、レスポンスを返す
  3. HogeActorでなんらかの処理がされる

下記のようなテストコードを書きました。

SampleTest.java

  @Test
  public void test() {
      // 1. リクエスト実行

      // 2. レスポンスが返ってくることを確認
      assertThat(res).isNotEmpty();

      // 3. HogeActorでされる処理の結果を取得
      T actual = ...

      // 4. 上記が期待通りか確認
      assertThat(actual).isEqualTo(expected);   
  }

なにがまずいかというと、3 のHogeActorの処理は別スレッドで行われているので、このコードでは処理が終わったことが保障できないのです。(言われてみれば超あたりまえですが...)

短時間で終わることがわかりきっているなら、 TimeUnit.SECONDS.#sleep で数秒待つのを差し込めば解消されますが、あまりよろしくはないです。

Actorはもともとマルチスレッドでやるためのようなものなので、このテストの方針がいけていないです。

対策

いわゆる、困難の分割です。テストの対象を、

  1. APIをたたいたら、期待したメッセージをHogeActorで受け取る
  2. HogeActorでメッセージを受信したら、期待する処理がされる

の2つに分けます。

1はJavaTestKitを使うのが向いています。

2はTestActorRefを使うのが向いています。これを使うと、通常は生成できないActorのインスタンスを取得できるので、onReceiveのメソッドをテストで直接使えるようになります。

あるいはメッセージの送信やメッセージの部分をモックにおきかえられるなら、

  1. APIを叩いたら、HogeActorにメッセージを送るメソッドがよばれる
  2. HogeActorにメッセージをわたしたら、期待する処理をするメソッドがよばれる
  3. 実際の処理を実行して、期待する結果かを確認

という風に、途中まではverifyでしのいで、やりたい処理のところだけを別途やるという作戦もあります。

マルチスレッドになっているときのテストは要注意ですね...

参考サイト