riak_test でインテグレーションテストの自動化

Erlangアベドントカレンダー16日目の記事。CROSS 2014で分散システムのテストがどうのという話になったときにぼくは riak_test をちょっとだけ話したが、それをもうちょっと詳しく説明しておこうと思う。ちなみに2年前もちょっとだけ紹介していた


is 何

BashoがRiakのインテグレーションテストを自動化するために開発したテストフレームワークだ。 eunit や ct では機能が不十分なので結局自分たちで作ってしまった。ペネトレーションテストとか負荷テストはまた別途。

なにができるのか

  • プロセスを立ち上げて何か叩いてチェックする、プロセスを落として環境を元に戻す、という手順を自動化できる
  • コマンド入力などマニュアル操作を自動化できる
  • 通常の eunit のアサーションを使って、ログの正規表現マッチなどさまざまなバリデーションができる
  • 複数のテストの結果を集計できる
  • 適当なところでFault Injectionして異常系の試験ができる
  • 失敗したテストのノードをそのまま残して、プロセスにアタッチして直接みてデバッグできる

というわけで、Riakそのもののテストにも使っているが、実はRiakを使ったアプリケーション(Riak CS)の結合テストも riak_test で書かれている

使い方

まずはコマンドを用意しよう。 17系のErlangで動くかどうかは知らない…

$ git clone git://github.com/basho/riak_test
$ cd riak_test && make
$ sudo cp riak_test /usr/local/bin

これで riak_test をコマンドとして実行できるようになる。

フレームワークそのものをいきなり使うのは大変なので、手っ取り早く動かしてみたいという人は(僕がいつもやっている) Riak CS のテストを走らせてみるとよいだろう。 README をもとに環境を用意して、ぜひ動かしてみてほしい。

作り方

ハーネス

rtdev.erlrt_cs_dev.erl を見るとわかる。分かりませんね、本当は behaviour で丁寧に用意すべきコールバックを用意すべきなのだが、まあそうなってないのはいつものBashoクオリティということでひとつ。このなかの setup_harness や stop_all (うろ覚え)がコールバックになっていて、これらを設定してやるとハーネスとしてなんとか動き始めるはず。はず…。

設定ファイル

以下は、ざっくりとした環境の構成を説明する。設定ファイルをつくる。設定ファイルはデフォルトでは ~/.riak_test.config だ。

{default, [
    {rt_max_wait_time, 180000},
    {rt_retry_delay, 1000}
]}.

{rtdev, [
    {rt_deps, ["/home/kuenishi/src/riak/deps"]},
    {rt_retry_delay, 500},
    {rt_harness, rtdev},
    {rtdev_path, [{root, "/tmp/rt"},
                  {current, "/tmp/rt/current"},
                  {"1.2.1", "/tmp/rt/riak-1.2.1"},
                  {"1.1.4", "/tmp/rt/riak-1.1.4"},
                  {"1.0.3", "/tmp/rt/riak-1.0.3"}]},
    {basho_bench, "/home/kuenishi/src/basho_bench"}
]}.

{rt_cs_dev, [
     {rt_project, "riak_cs"},
     {rt_deps, [
                "/home/kuenishi/cs-2.0/riak_cs/deps"
               ]},
     {rt_retry_delay, 500},
     {rt_harness, rt_cs_dev},
     {build_paths, [{root,              "/home/kuenishi/rt/riak"},
                    {current,           "/home/kuenishi/rt/riak/current"},
                    {previous,          "/home/kuenishi/rt/riak/riak-1.4.10"},
                    {ee_root,           "/home/kuenishi/rt/riak_ee"},
                    {ee_current,        "/home/kuenishi/rt/riak_ee/current"},
                    {ee_previous,       "/home/kuenishi/rt/riak_ee/riak-ee-1.4.10"},
                    %%{ee_root,         "/home/kuenishi/rt/riak_ee_onefour"},
                    %%{ee_current,      "/home/kuenishi/rt/riak_ee_onefour/current"},
                    {cs_root,           "/home/kuenishi/rt/riak_cs"},
                    {cs_current,        "/home/kuenishi/rt/riak_cs/current"},
                    {cs_previous,
                           "/home/kuenishi/rt/riak_cs/riak-cs-1.5.1"},
                    {stanchion_root,    "/home/kuenishi/rt/stanchion"},
                    {stanchion_current, "/home/kuenishi/rt/stanchion/current"},
                    {stanchion_previous,
                       "/home/kuenishi/rt/stanchion/stanchion-1.5.0"}
                   ]},
     {test_paths, ["/home/kuenishi/cs-2.0/riak_cs/riak_test/ebin"]},
     {src_paths, [{cs_src_root, "/home/kuenishi/cs-2.0/riak_cs"}]},
     {lager_level, debug},
     {build_type, oss},
     %%{build_type, ee},
     %%{flavor, {multibag, disjoint}},
     {sibling_benchmark,
      [{write_concurrency, 16}, %% seems not working more than 20
       {duration_sec, 100},
       {leave_and_join, 3},
       %%{version, previous}]
       {version, current}]
       %%]
     },

     {flavor, basic},
     {backend, {multi_backend, bitcask}}
]}.

{rt_cs_dev_old, [
     {rt_project, "riak_cs-1.5"},
     {rt_deps, [
                "/home/kuenishi/cs-1.5/riak-ee-1.4.10/deps",
                "/home/kuenishi/cs-1.5/riak_cs/deps",
                "/home/kuenshi/cs-1.5/stanchion/deps"
               ]},
     {rt_retry_delay, 500},
     {rt_harness, rt_cs_dev},
     {build_paths, [%%{root,              "/home/kuenishi/rt/riak"},
                    %%{current,           "/home/kuenishi/rt/riak/current"},
                    {root,           "/home/kuenishi/rt/riak_ee_onefour"},
                    {current,        "/home/kuenishi/rt/riak_ee_onefour/current"},
                    {ee_root,           "/home/kuenishi/rt/riak_ee_onefour/"},
                    {ee_current,        "/home/kuenishi/rt/riak_ee_onefour/current"},
                    {cs_root,           "/home/kuenishi/rt/riak_cs"},
                    {cs_current,        "/home/kuenishi/rt/riak_cs/current"},
                    {stanchion_root,    "/home/kuenishi/rt/stanchion"},
                    {stanchion_current, "/home/kuenishi/rt/stanchion/current"}
                   ]},
     {test_paths, ["/home/kuenishi/cs-1.5/riak_cs/riak_test/ebin"]},
     {src_paths, [{cs_src_root, "/home/kuenishi/cs-1.5/riak_cs"}]},
     {lager_level, debug},
     {build_type, oss},
     {flavor, basic},
     {backend, {multi_backend, bitcask}}
]}.

長かった。ここでは3つのアプリケーションのテストがセットアップされている。それぞれ長さ2のタプルであるが、ひとつめの要素はテストの名前だ。これを使ってテストを呼び出す。このファイルだと、 rtdev, rt_cs_dev, rt_cs_dev_old が用意されている。ひとつめは Riak のテストだ。ふたつめは現在開発中の Riak CS のテスト、みっつめは現行バージョンの Riak CS 1.5 系のテストだ。Riakのテストをサンプルに、各設定を説明しよう。

  • rt_deps ... テスト用の依存ライブラリがまとまっているディレクトリを指定する。 ERL_LIBSの代わりだ。
  • rt_retry_delay ... 環境によってテストの実行時間は変わるので、それに合わせたリトライ時間を設定する。
  • rt_harness ... これで setup/teardown (ノードの上げ下げ)のハーネスを指定する。 rtdev は Riak のノードを上げたり下げたりするためのものだ。 rt_cs_dev はRiak CS用。実態はただのモジュール名で、それぞれ riak_test のレポジトリ内に用意されている。もしも自分でテストをするときは、このモジュールを自作して setup/teardown のコールバックを作成する。
  • rtdev_path ... これは riak用の設定で、バージョンを記述する。current はまさにテスト対象の最新版、 previous はテスト対象のひとつ前、 legacy はもうひとつ前、といった風である。 rtdev 内にコードがあるが、このようにバージョンへのエイリアスを作成することによって、開発が進んでもテストコードを書き換えないでいいようにしている。また、 rtdev はこのディレクトリ下にRiakのリリースが入っている前提で、ここから起動終了など様々なRiakのコマンドを動かす。
  • test_paths ... コンパイル済みのテストコードの .beam を入れておくところ。ここからテストを探す。
  • lager_level ... ログレベルを設定
  • basho_bench ... これはよく知らない。がおそらく b_b のディレクトリを指定しているのだろう。このようにカスタムの設定をハーネスや環境によって変えることができる。たとえばその下にあるRiak CSにはもっと沢山の設定があって、これらの設定はテストを作る側がいろいろ変更できるようになっている。

テストコードを書く

実際のテストコードはとても単純だ。 confirm/0 という関数がエクスポートされていることが動作条件だ。この関数の spec は

-spec confirm() -> pass.

というとても単純なもので、なにかテストがコケたら例外でもbadmatchでも何でも飛ぶので、それを上で自動でキャッチしてくれる。 pass というアトムを返せば成功したことになる。どんなアサーションを入れても大丈夫だ。

うごかす

$ ls /path/to/your/test-beams
foo_test.beam              bar_test.beam
$ riak_test -c rt_your_dev -d /path/to/your/test-beams
||

とすると、 foo_test と bar_test.beam を実行する。ピンで実行したかったら、

>|sh|
$ riak_test -c rt_your_dev -t foo_test

とする。

テスト結果のオーバービューを可視化する

Giddyupというモノがある。これはいわゆる「スコアカード」というやつだ。 riak_test の実行結果は環境によって変わるだろうから、それらの結果を一覧表示してどこがダメかに早めに気付けるようにしている。ちなみに Riak CS では使ってないので、よく知らないです…やらなきゃダメなんだけれども。