Buildbot で継続的インテグレーション
こんにちは。パートナーサービス部の加藤和良です。
前回、mixi における開発者テスト について説明しました。だいぶ間があいてしまいましたが、今回は、そのテストを定期的に実行する 継続的インテグレーション の仕組みを紹介したいと思います。
テストが遅い
実は、mixi のテストは「遅い」という大きな問題を抱えています。
Micheal Feathers は『レガシーコード改善ガイド』のなかで、単体テストが高速に実行できることの重要性を解き「単体テスト」を厳しく定義します。
次に当てはまるものは単体テストではない。
- データベースとやり取りする
- ネットワークを介した通信をする
- ファイルシステムにアクセスする
- 実行するために特別な環境設定を必要とする (環境設定ファイルの編集など)
上記に該当するテストが悪いというわけではない。多くの場合において、そのようなテストを書く価値はあり、しばしばテストハーネス内に記述される。しかし単体テストは、そのようなテストと切り分けて、変更を行うたびに高速で実行できるように保ち続けることが重要である。
この定義にしたがうと、mixi のテストで「単体テスト」と呼べるものはごくわずかで、それらは実際「変更を行うたびに高速で実行できる」速度に達していません。
そのためか mixi のテストは実行をさぼられがちで
- 安定版からのブランチを作成して、開発を開始
- 開発が一段落したので、そのブランチで (or マージしてから) テストを走らせる
- テストが失敗するので調べてみたら、そもそも安定版のほうでもテストが失敗していた
といったことが時折ありました。
Buildbot
継続的インテグレーション導入の目的は、こういった状況を改善することでした。現在は、Subversion レポジトリ上の安定版のツリーにコミットが行われると、自動でテストが実行され、結果が IRC に通知されます。テストの結果は Web からも確認できます。
これら一連の動作は Buildbot をつかって実現しています。Buildbot は Python で書かれた継続的インテグレーションのためのソフトウェアで、たとえば WebKit でも使われています。
継続的インテグレーションはテストを高速に実行するためのものではありません。ただ、テストが遅い状況でも、遅さに起因する問題をやや軽減させる効果はあるんじゃないかとは思います。
テストのアルファベット分割
mixi では継続的インテグレーションからのフィードバックをはやく得るために、2つの工夫をおこなっています。ひとつがテストのアルファベット順の分割です。
Buildbot では全行程がおわるまでの個々の処理を BuildStep と呼ばれる単位に分割します。BuildStep が成功すれば Web インターフェースで緑色に、失敗すれば赤色に表示されます。
mixi の場合、当初は
- svn checkout
- make test
と2つの BuildStep だけがあったのですが、この設定では make test の最中の進行状況がわかりにくいという問題がありました。前述のとおり mixi のテストは遅いので、わかりにくい時間もやや長めです。
そこでテストを何段階かに分割することを考えました。一番低レイヤーのライブラリから一番上のアプリケーションまで順に BuildStep に切る、みたいなのが真っ当なやりかただとは思うのですが、mixi のコードはお互いの依存関係がちょっと混迷を極めていて、あんまり良い分割単位が見出せません。
そこでいまは、単純かつ機械的に
- ^t/lib/Mixi/A
- ^t/lib/Mixi/B
- ...
- それ以外
とファイル名をベースに分割するようにしています。
アルファベットごとのテストの量には大分ばらつきがあり ^t/lib/Mixi/W みたいにマッチしないこともあります。ただ、テストの進行中および失敗した際のわかりやすさは大分改善されました。
最近変更した部分に対するテスト
もうひとつの工夫が「最近変更した部分に対するテスト」の別実行です。繰り返しになりますが mixi のテストは遅いため
- Buildbot が全てのテストを実行し、失敗を報告
- 失敗に対応する修正をコミット
- Buildbot が再度全てのテストを実行し、成功を報告
というサイクルがあまり早く回せません。アルファベット分割も、例えていうなら「遅い処理の間はプログレスバーを出しましょう」という話でしかありません。
そこで mixi では、全テストの実行とは別に、最近修正されたソースコードに対するテストだけの実行も行っています。
現在は、1分ごとにレポジトリをポーリングして
- full
- コミットが10分間ないとすべてのテストを実行しはじめる
- recent
- コミットがあるごとに最近変更されたコードに対応するテストを実行する
という二系統のテストを (Buildbot 用語でいう別の Builder で) 行っています。
mixi には lib/Mixi/Member.pm へのテストは
- t/lib/Mixi/Member.t
- t/lib/Mixi/Member/ 以下
まとめ
というわけで、mixi における継続的インテグレーションの仕組みと、こまごました工夫について紹介しました。
説明の順番が、まず開発者テスト、次に継続的インテグレーション、というふうになってしまいましたが、実際には、このふたつは並行して進めていました。
継続的インテグレーションの存在は「テストを書いたけど実行されない」という問題をなくし、すでにテストを書いている人のリターンを増やします。また、Buildbot が IRC にテストの失敗を通知し、実際に本番でもバグがみつかる、という流れには、いままで「テストってなんだろう?」と思ってたひとが「書くと良さそうだ」となる効果があったんじゃないかと思います。
開発者テストが書きにくかったり、遅かったり、いまひとつ開発者のなかに浸透していなかったりする環境でも、とりあえず継続的インテグレーションからはじめてみるのはおすすめですよ、というのが伝われば幸いです。それではまた。