Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

AkkaのActor生成時の注意点やテスト方法のナレッジ共有

こんにちは、広幡です。

現在携わっているプロジェクトでは、バッチアプリケーションをAkkaで開発しています。

開発が進んできてActorの生成方法やテスト方法などのナレッジが少しずつたまってきたのでシェアしようと思います。

バージョンは、Akka 2.3.14, Scala 2.11.7です。

Actorの生成方法

ActorはPropsを使って生成するのですが、classOfを使用して作成するのがいいみたいです。 公式ドキュメントにも書いてありますね。

import akka.actor.Props

val props1 = Props[MyActor]
val props2 = Props(new ActorWithArgs("arg")) // careful, see below
val props3 = Props(classOf[ActorWithArgs], "arg")

2つめでnewして作成する方法が上げられていますが、こちらの方法ではメモリリークが発生する可能性があるとのことです。それについては以下の記事で詳しく述べられています。

d.hatena.ne.jp

じゃあclassOfの方を採用しよう!って軽い気持ちで決めたあなた、痛い目を見ます。 僕のように。

なんなのかというと、引数の指定が間違っている場合に実行時まで気付けないという問題が発生します。コンパイルで気付けないんです。(コードレビューでも気付けなかった…)

簡単な例で説明します。

/**
 * 引数が2つあるActor
 */
class SampleActor(
  hoge: Hoge,
  fuga: Fuga
) extends Actor {

  def receive = {
    case Foo => sender() ! hoge.value
  }
}

object ErrorSample extends App {

  val system = ActorSystem("Sample")
  system.actorOf(
    // 引数を2つ渡さなければいけないのに、1つしか渡していないパターン
    Props(
      classOf[SampleActor],
      Hoge
    )
  )
}

この場合、Propsにはクラスと2つの引数を渡さなければいけませんが、1つしか引数を渡していないため、実行時にエラーになってしまいます。新機能開発などで新しく引数を追加した際にこのような問題が起きてしまうかもしれないです。。

コンパイル通った!テスト通った!デプロイするわ!…バッチ落ちているんですが!?

となった僕が言えた義理ではありませんが、こうならないためにActor生成時のチェックをした方がいいと思ったわけです。

Actor生成時のテスト方法

前述したミスを犯さないためにもまずはActorのファクトリメソッドを定義しましょう。

object SampleActorFactory {

  def props(
    hoge: Hoge,
    fuga: Fuga
  ): Props = Props(
    classOf[SampleActor],
    hoge,
    fuga
  )
}

このファクトリメソッドを呼び出すことで引数チェックが行われるので、その点においてはコンパイルで気付けます(そもそもIDEで気付ける)。 しかしProps内の引数が間違っているかどうかはまだこの時点では気付けませんね。

この問題はファクトリメソッドのテストコードを書くことで担保することができます。 まずはテストする準備として、以下のコードを記述します。

/**
 * Akkaのテストサポート
 */
abstract class AkkaTestkitSupport
  extends TestKit(ActorSystem("TestSample"))
  with After
  with ImplicitSender {

  def after = system.shutdown
}

テスト時にこれを用いることで、それぞれのテストコード毎にActorSystemを起動・終了させます。 これについては他のブログ等で紹介されていますので、そちらを参考にしてください。

さて、ではファクトリメソッドのテストコードを書いていきましょう。

/**
 * SampleActorFactoryのテストコードサンプル
 */
class SampleActorFactorySpec extends Specification {

  "インスタンス生成" should {
    "SampleActorを生成できる" in new AkkaTestkitSupport {
      system.actorOf(
        SampleActorFactory.props(new Hoge, new Fuga)
      ) must not(throwA[IllegalArgumentException])
    }
  }
}

これだけです。 何をやっているのかというと、Actorの生成にミスった時はIllegalArgumentExceptionがthrowされるので、これがthrowされないことをチェックしています。 (もしほかの例外がtrhowされるとテストが通っちゃうので、ここはThrowableにするべきかもしれない…)

このテストコードのおかげで、Props内の引数が間違っていた場合はテストで落としてくれます。 新しくチームにジョインしてきた人やこの部分に詳しくない人などが触った時など、この落とし穴に引っかかることはなくなりました。

おわりに

以上になります。とりあえず一言だけ

Akkaむずい…