goccyでごっしー

技術的な話題をつらつらと書いたり書かなかったり

より高速なテスト環境を実現するために

テストを書くことは良い事です。どんどん書いて品質を上げたいところではありますが、テストの数が数万〜数十万に及ぶようなプロジェクトでは、フルテストに要する時間が無視できない程膨れ上がってしまいます。

今回は、そんな増えすぎたテストコードの影響でフルテストに時間がかかっているプロジェクトのために、テスト高速化を実現するためのモジュールとその使い方について紹介したいと思います。

 

モジュールは、App::Ikarosというもので、現在version 0.02がリリースされています

使い方等はここにまとめてあるので(随時更新する予定)、この記事ではどのようなことができるモジュールなのかをざっと説明しようと思います。

 

まず、ざっくりとですがApp::Ikarosが提供する機能には以下の様なものがあります。

  • 多数のノードによる分散テスト実行
  • forkproveによるテスト実行の高速化
  • Devel::Coverによるカバレッジ計測
  • テストクラスタ最適化のためのProfiling機能
  • Jenkinsとの連携(JUnit形式のXML生成)
  • .proveを利用したテストクラスタの自動最適化
  • 失敗したテストの再実行

それぞれを順に見ていきます

 

1. 多数のノードによる分散テスト実行

これが本モジュールのメイン機能になります。

テスト実行したいノードを設定ファイルに書いておくと、App::Ikaros側で大体同じ時間でテストが終わるように、良い感じにクラスタリングして各ノードに振り分けます。テスト実行のログは、逐次振り分け元のmasterに送られ、テスト終了後には各slaveで生成された結果(JUnit形式のXMLファイル)を回収してJenkinsに渡します

 

2. forkproveによるテスト実行の高速化

forkproveはmiyagawaさん作のモジュールで、prove + モジュールのpreload機能がついたものです。システムが大きくなってくると、useすると芋づる式にほとんどのモジュールをuseしてしまうようなモジュールが存在するケースがあります。こういったモジュールを多数のテストで利用している場合、各テストのコンパイルに要する時間も馬鹿になりません。そこで、proveがテスト実行のためにfork + execをする前に、コンパイルに時間のかかるモジュールを読み込んでおくことで、モジュール読み込み時間をカットするというものです。

実際これは結構有効なケースがあって大変便利なのですが、先にモジュールを読み込んでしまうことによって生じる弊害も少なくありません。

例えばTest::MockObjectを利用しているテストがある場合、先にモジュールを読み込んでしまうとエラーになるケースがあったりします。

上記は正確に言うと回避策があるのでそこまで問題にはなりませんが、やんごとなき事情でforkproveの対象から除外したいテストもあるかと思います。

App::Ikarosでは、このあたりをいい感じに処理するために、「このテストクラスタはproveで、このテストクラスタはforkproveで処理する」というようなことを簡単に設定できるようになっています。

ですので、気軽にforkproveを使って高速化したいテストだけ高速化することができます。

 

3. Devel::Coverによるカバレッジ計測

Devel::Coverを有効にしてテストを走らせると、通常の何倍も時間を食います。

もともとが30分〜1時間以上かかる場合、テスト完了までに途方もない時間が必要となることが予想されます。

ですが、分散実行すればその時間も劇的に短くなるので、App::IkarosではDevel::Coverによるカバレッジ計測を簡単に行えるようサポートしています。

 

4. テストクラスタ最適化のためのProfiling機能

大量のテストからいくつかのテストクラスタを作ってそれを処理するようにすると、

テストを処理するノードによってテスト時間の偏りが生じがちです。

理想的には全てのテストクラスタが同じ時間で終わるように調整されるべきです。

また、ノードをどんどん追加していくと、理論的には追加したぶんだけ高速化されるはずですが、そうならないケースが存在します。一部の時間のかかるテストに全体のテスト終了時間が影響を受けるケースです。

App::Ikarosでは、テストクラスタ毎のテスト時間の偏りをなくしたり、一部の時間のかかるテストを効率的に見つけるためのProfiling機能を持っています。この記事を書いている時点では収集データをData::Dumperで出力するだけの貧弱なProfiling結果ですが、後のアップデートでちゃんとしたビジュアライザも用意したいなと思っています

 

5. Jenkinsとの連携(JUnit形式のXMLファイルの生成)

自分がCIツールにJenkinsを利用しているというのもあり、最終的な結果としてJUnit形式のXMLファイルを生成するようになっています。

Jenkins上で、「JUnit結果の集計」の項目で生成されたxmlファイルを指定すれば、簡単にJenkinsと連携させることができます。

 

6. .proveによるテストクラスタの自動最適化

App::Ikarosがデフォルトでやっているテストクラスタの最適化は.proveファイルを用います。.proveは、prove --state=saveとすることによって生成されるファイルで、中にはテスト実行時間等が記述されています。これをもとに、同じくらい時間のかかるテストがなるべく別クラスタになるよう調整され実行されます。

 

7. 失敗したテストの再実行

多数のノードを使って分散実行することになると、同時に走るテストの数が数十 ~ 数百になります。この規模になると、DBのデータ不整合等の問題も起きやすくなり、本来成功するはずのテストが失敗してしまったりします。そういったテストを最終的に失敗として報告するのは問題なので、最後の最後に失敗したテストを同一ホスト上で、並列度なしの状態で再度実行します。この結果失敗したテストがある場合は、本当に失敗したと判断し、最終結果に反映させます。

 

とまぁいろいろな機能がありまして、それぞれを簡単に利用できるように作っているつもりではいますが、まだまだ使いにくさなどあるかと思います。

そんなときは、pullreqなり@goccy54まで連絡していただければ幸いです。

 

今回、一切具体的な使い方について書かなかったので、後ほど気が向いたら

使い方も交えて書こうと思います。