Ruby/Railsの遅いテストを診断するgem(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社エンジニアの「?」を「!」に。原著者の許諾を得て翻訳・公開いたします。Ruby/Railsの遅いテストを診断するgem(翻訳)テストを書くことは、開発における重要なプロセスであり、RubyやRailsのコミュニティには特に当てはまります。私たちはテストでgreenが点灯するまで長時間待たされていることに気づいて、初めてテストスイートのパフォーマンスというものに関心を寄せるようになるものです。私はテストスイートのパフォーマンスの分析に多くの時間を費やし、テストを高速化するテクニックを編み出すとともにツールを開発しました。したノウハウをすべてTestProfという名のメタgemに盛り込みました。TestProfはRubyのテストをプロファイリングするツールボックスです。開発の動機テストが遅いと生産性が低下し、無駄な時間を費やす羽目になる「テストスイートのパフォーマンスがどうしてそんなに重要なんだろうか?」とお思いの方もいらっしゃるでしょうから、議論を始める前にいくつかの統計情報をお見せしたいと思います。今年初頭に、Ruby開発者にテストのパフォーマンスに関する簡単な聞き取り調査を行いました。最初によいお知らせです。Ruby開発者のほとんどすべてがテストを書いています(正直、私は驚きませんが)。私はRubyコミュニティのこういうところが好きです。調査の結果、テスト実行に10分以上かかっているケースは4分の1程度にとどまりました。半分近くは5分以内に収まっています。これなら大きな心配はなさそうです。それでは、exampleが1000を超えるテストスイートに限定して聞いてみましょう。今度はだいぶ残念な結果になりました。テストスイートの約半分は実行に10分以上を要し、ほぼ3割近くが20分以上かかっています。ところで、私がこれまで経験した典型的なRailsアプリでは、ざっくり6000件から10000件のexampleがありました。変更のたびにテストスイート全体を実行する必要などありません。通常、私が中規模の機能を手がけている場合はコミットあたり100件のexampleを実行しており、実行には1分程度しかかかりません。Searlsの動画↓をご覧ください)、その間私の貴重な時間は無駄になります。それでも私たちは、デプロイサイクル中にCIサービスですべてのテストを実行しなければなりません。テスト完了まで10分ばかり(キューでビルドの負荷が生じれば数時間に達することもあります)待たされても平気ですか?私にはそうは思えません。ビルドの並列処理化で待ち時間を軽減することもできますが、結局コストに跳ね返ります。次のグラフをご覧ください。1250件あたり2.5分を要します。minute)は500になります。最適化を行う前は、800件のexampleで4分を要しました。EPMにしてわずか200です。最適化によってビルドあたり3、4分を節約できたのです。明らかに、遅いテストはあなたの貴重な時間を奪い、生産性を低下させているのです。ツールボックスあなたがテストスイートの遅さに気づいたとしましょう。gemをご紹介いたします。TestProfはテストスイートのボトルネックを突き止め、改善方法を示してくれます。これより、私がTestProfを使ってテストスイートの分析と改善を行ってみます。一般的なプロファイリングテストスイートの詳細にいきなり切り込むよりも、まず一般的な情報を集める方が有用なことがしばしばあります。試しに、テストスイートについて以下の質問に答えてみてください。コントローラか、モデルか、サービスか、ジョブか?最も時間のかかっているモジュールやメソッドはどれかこれだけでも面倒な作業ですよね。Profilerを使います。これはRSpecの特定のタグ値でグループ化された統計情報を収集してくれます。RSpecはexampleにtypeタグを自動で追加するので、以下のように使えます。...これで、ボトルネックを調べる際のテストのtypeを絞り込めるようになりました。RubyProfやStackProfのような一般的なRubyプロファイラを使ったことがある方もいると思いますが、TestProfは面倒な設定や改造を一切行わずにテストスイートに対して手軽に実行できます。rspecTestProfの各種プロファイラが生成するレポートを使って、最も利用頻度の高いスタックパスを突き止めることができ、質問2.にも答えられるようになります。このプロファイリングについては残念ながらリソース消費が著しく、既に遅いテストスイートの実行がますます遅くなりますので、テストの一部に絞り込んでプロファイルを実行しなければなりません。やって絞り込めばよいのでしょうか。実は、ランダムに絞り込めるのです。TestProfには特殊なパッチが同梱されており、RSpecのexampleグループ(またはMiniTestのスイート)をランダムに選んで実行できます。rspec後はコントローラのテストのサンプルに対してStackProfを実行し(TagProfでここが最も遅かったので)、出力結果を元に分析すればよいのです。私があるプロジェクトに対して実行した結果を以下に示します。*Class#newここから、test環境におけるSorceryの暗号化設定がproduction環境と同じ制約の強い設定になっていることがわかります。典型的なRailsアプリの場合、レポートはほとんどの場合以下のようになるでしょう。ActiveRecord::LazyAttributeHash#[]ActiveRecordが随分と多く、データベースの利用も突出しています。これをどうやって改善すればよいのでしょうか。データベースとのやりとりテストスイートの実行時間にデータベースが占める割合がどのぐらいあるかを把握していますか?まずは自分であたりを付けてから、TestProfであぶり出してみましょう。私たちはRailsのInstrumentation周りを既に拡張した(ActiveSupportのNotification/Instrumentation機能)ので、EventProfilerの基本的な概要やご紹介は省略いたします。EventProfはテストスイート実行中にinstrumentationメトリックスを収集し、遅いグループのランキングや、特定のメトリックスに関連するexampleについての一般的な情報をレポートします。現時点では、ActiveSupport::Notificationsのみ無設定で利用できますが、自分のソリューションへの統合は簡単なはずです。データベース利用量の情報を取得するには、sql.active_recordイベントを使います。--profileの出力と非常に似ています)。5)私の現在のプロジェクトでは、DBが総時間に占める割合はおよそ20%ですが、これは既に十分最適化を行った結果です。最適化前は30%を超えていました。どのプロジェクトでも共通でチェックに使えるようなメトリックの値というものはありません。これはテストのスタイルによって大きく変わるものであり、単体テストと結合テストのどちらが多いかによって異なります。私たちの場合は結合テストがほとんどだったため、20%という値は決して悪くありません(もちろんさらに改善は可能なはずですが)。データベースがテスト時間に占める割合が高い典型的な理由とは何でしょうか。そのすべてを列挙するのは無理ですが、一部をリストアップしてみました。create問題」)です。モデルの単体テストでデータベースを叩く必要は普通ないはずなので、データベースにアクセスしなければ済みます。テストコードでデータベースにアクセスしているとしたらどうでしょう。テストで永続性が不要かどうかをどうやって突き止めればよいでしょうか。Doctorの登場です。Doctorは、不要なデータ作成がいつ行われたかを通知してくれます。Doctorは残念ながら魔法使いではありませんので(ここではまだ学習が終わっていません)、偽陽性や偽陰性が生じることもあります。2番目の問題は、ややトリッキーな点です。次の例をご覧ください。この後15件ほどexampleが続くend「そんなのfixtureでいいじゃない」とお思いかもしれません。十数個ものモデルが毎日変更されるようなかなり大きなプロジェクトでなければ悪くない方法です。ひとつの方法はbefore(:all)フックでデータを1度だけ生成することです。この方法には1つ注意点があります。before(:all)はトランザクションの外で実行されるので、データベースを手動でクリーンアップしなければなりません。なければ、グループ全体を1つのトランザクションに手動で閉じ込めるというのはどうでしょうか。TestProfのbefore_allヘルパーはまさにこれを行っているのです。Fixtureをご検討ください。これは、(ファクトリーを使って)コードからフィクスチャを生成するのに使えます。ファクトリーのカスケード問題ファクトリーのカスケード問題については、これだけで記事になります。ショッキングな結果が出ます)。バックグラウンドジョブデータベース以外にもさまざまなボトルネックがあります。その中からひとつを取り上げてみましょう。テストでよく行われるのが、バックグラウンドジョブをインライン化する(Sidekiq::Testing.inline!など)という手法です。通常、重たい作業はバックグランドに回しますので、すべてのジョブを実行すると実行時間が無条件に長くなります。TestProfはバックグラウンドに要した時間のプロファイリングもサポートしています(現時点ではSidekiq限定)。以下のようにプロファイルにsidekiq.inlineを指定するだけでできます。rspecこれで、無駄になっている時間を正確に知ることができます。次はどうすればよいでしょうか。インラインモードをオフにすると、テストのexampleが大量に動かなくなるかもしれません。あまりに多さにすぐには修正しきれないほどです。解決方法は、インラインをグローバルにオフにし、必要な場合のみオンにすることです。RSpecをお使いの場合は次のようにします。...end失敗したexampleのひとつひとつにタグを付けて回らなければならないのでしょうか?TestProfならそんな必要はありません。Stampという特殊なツールが含まれており、特定のタグを自動で追加してくれます。Stampの内部ではソースファイルをパースしてタグを正しく挿入するためにRipperを用いています。Stampのマニュアルをご覧ください。TestProfはGitHubとrubygems.orgで公開されており、いつでもアプリに導入してテストスイートのパフォーマンス向上に役立てることができます。本記事はTestProfの紹介のみにとどまっており、すべての機能をカバーしているわけではありません。詳しくは以下の追加リソースをどうぞ。Martiansのフォームにて待つ。コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。enno.jpを運営。実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。Storageを使ってはいけない(翻訳)#Tags#会社紹介#自社サービス#勉強会#Rails#PHP#デザイン#実績#週刊RailsウオッチCONTACTお気軽にご相談ください。(翻訳)CONTACTTechRachoでは、パートナーシップをご検討いただける方からのご連絡をお待ちしております。お気軽にご意見・ご相談ください。