こんにちは。CTOの前田です。
この記事はロコガイド Advent Calendar 2020の3日目です。
ロコガイドでは大小様々なアプリケーションが日々開発されていますが、CI/CDの環境整備は現代の開発において必須要件です。
弊社では標準環境としてCircleCIとAWS CodeBuildを利用していますが、日々運用を重ね、年月が経ってくるとテストの実行時間の伸びや不安定なテストの存在が問題になってきます。
今日は、CIにおける諸問題についてどのように対処しているか、そして現在進行している改善活動の内容についてお話します。
解消する問題
昨今はマイクロサービスアーキテクチャに代表されるように、そもそもアプリケーションコードの肥大化をさせないためのアプローチを取ることも多くなってきました。
しかし、弊社においては最も規模が大きいサービスの トクバイ は所謂モノリシックアプリケーションであり、コードベースの規模が大きくテストケースの数も膨大です。
また、テストケース全体におけるE2Eの比率の高さが、実行時間が伸びる要因にもなっています。そして、特定条件が重なる事によって稀に失敗する不安定なテストも増加してきます。
本質的な改善としてモノリスアプリケーションを分割する取り組みも始めていますが、こちらに関しては後日のご紹介とし、本日はテストの高速化と安定化のための分散実行環境に絞ってご紹介します。
テストの分散実行環境
直列で全件実行すると数時間かかってしまうようなテストでも、並列で実行することで大きく実行時間を短縮できます。
CircleCIなどのCIツールでは実行するファイル群を複数コンテナに分配して分散実行する機構を備えていますが、ロコガイドではAWS CodeBuildを用いた分散実行基盤を自社開発し運用しています。
- (初期開発当時)コンピューティングリソースのスケールアップ/スケールアウトに最も柔軟に対応できる環境であったこと
- system testの実行時に、マシンパワーの不足によるTimeout Errorが散見される状態であった
- CIサービス各社と比較した時に安価であること
- 開発者が各自でローカルに保持しているソースをリモートでテスト実行する用途が実現しやすかったこと
以上の3点が理由でしたが、現在においては1はCircleCIにPerformance Planが登場していることから、それほどの優位性はなくなりました。
しかし、現在でも2と3のメリットが十分に上回る状況であることと、後述する不安定なテスト向けの機能拡充を実現しやすいため、自社開発機構のメンテナンスを続けています。
分散実行戦略
複数コンテナへの分散実行以外にも、コンテナ内で分散実行を行うtest-queueやparallel_testsなどのツールが存在し、Rails 6でも公式にテストの並列実行がサポートされました*1
ロコガイドにおいては、複数コンテナへのテスト実行ファイルの分散と、コンテナ内でのプロセスベースでの分散の併用により並列度を引き上げる戦略を取っています。
これにより、コンピューティングリソースのスケールアップ、スケールアウト双方で並列性を向上させることが出来るようにしています。
また、テストを複数のコンテナで並列実行時すると、実行結果は通常各コンテナ毎に吐き出されます。
AWS CodeBuildにおいてはWeb ConsoleやCloudWatch Logsで実行結果を参照可能ですが、複数のコンテナでテストが失敗した場合、各コンテナの結果を参照しにいかないといけません。*2
そのため、弊社のテスト基盤ではRSpecのJSONFormatterで吐き出された全テストの実行結果を収集し、1つのまとまった結果として表示する機能を実装しています。
こうすることで、開発者はローカルマシンで全テストケースを実行したときと変わりのない使用感で実行できるようになり、修正までのリードタイムや心理的負担を減らすことにつながっています。
細かい仕様ではありますが、mainブランチで実行しているテストが失敗したケースでは失敗状況を迅速に把握し修正方針を立てることが開発チーム全体の生産性につながっていくため、重要なポイントだと考えています。
不安定なテストを解消しやすくする仕組み
日々開発を重ねていくと、確率的に失敗する不安定なテストが発生しがちです。
不安定なテストは、テストケースを一定の順序で実行すると再現するもの、単体でも繰り返し実行すると一定確率発生するものなど、発生要因は様々です。
これらのテストケースは発生するとCIの実行結果への信頼度が下がり、問題の発覚が遅れ、開発者の時間を浪費する結果に繋がるため、「発見のしやすさ」「要因特定のしやすさ」の2点を重要なテーマとしてプロトタイピングに取り組んでいます。
まず、トリガーとなるのは毎日夜間に実行している定期ビルドです。この定期ビルドでは、成功率の日次把握と不安定なテストケースの特定を目的としています。
Success Rate 70% Success Build 7 Fail Build 3 ./spec/features/sample_spec.rb(fail count: 3) 1. rspec --seed 24562 ./spec/models/sample_spec.rb ./spec/features/sample_spec.rb 1. rspec --seed 34513 ./spec/controllers/sample_spec.rb ./spec/features/sample_spec.rb
得られる出力は全体の成功/失敗の状況とテストごとの失敗数、再現コマンドで、これらの情報は全てAmazon S3上に記録されています。
サンプルとしてお見せしている例ではテストファイル数が少ないですが、実際のアプリケーションは数十、数百のテストが同時に実行されます。
再現時のテストの実行数が多くなると、手元での再現やデバッグの効率が上がらず、時間を浪費しかねません。そのため、定期ビルドの実行結果をもとにしてBisectを実行し、最小再現手順を特定しています。
./spec/features/admin/offices/office_leaflets_spec.rb 1. [FOUND] rspec './spec/models/sample_spec.rb[1:1:2:1]' './spec/features/sample_spec.rb[1:1,1:3:1]' --seed 24562 2. [FOUND] rspec './spec/controllers/sample_spec.rb[1:1:2:1]' './spec/features/sample_spec.rb[1:1,1:3:1]' --seed 34513
ここまで狭まれば手元での再現、デバッグは容易になります。現在試験的に運用を始めた段階ですが、テストの修正終了までの時間は大幅に短縮できた実感があり、この段階まで自動化が進めば現実的なコスト感で修正がうまく回るのではないかと考えています。
最後に
CIの環境に限らず「持続的に、無理なく改善がやりやすい仕組み」をつくることが、健全で開発者生産性が高い仕組みを維持するために重要だと考えています。
ロコガイドでは「CIの実行時間10分以内」「偽陰性での失敗をゼロにする」の目標を掲げて取り組み始めたばかりですが、本事例が皆様の環境改善の何かの参考になりますと幸いです。
PR
ロコガイドでは開発者生産性を共に向上させていくソフトウェアエンジニアを募集中です! 少しでも興味をもって頂けた方がいらっしゃれば、ぜひ一度カジュアルにお話させてください!