読者です 読者をやめる 読者になる 読者になる

テスト自動化をサポートするDVCS #swtestadvent2011

 このエントリは「Software Test & Quality Advent Calendar 2011」の12/13担当分です。前回の記事は「UWSC、ウチのプロジェクトではこう使ってます - shiro庵」、次回の記事は「ソフトウェアの品質を学びまくる:それでもやはり、品質を「予測」したい #swtestadvent2011」です。

はじめに

 ここ数年バージョン管理システムの一形態として分散型バージョン管理システム(以下DVCS)が注目されていますが、これは自動テストと大変親和性が高いツールだと実感しています
 例えば継続的インテグレーションでの継続的なタスクとして自動テストを実行する構成(以下継続的な自動テスト)でDVCSを導入すると、そこでの自動テストの構成の選択肢が大きく広がります。良い機会ですので、今回の担当分では継続的な自動テストとDVCSの組み合わせの効果についてまとめていきたいと思います。

集中型VCSの問題

 バージョン管理システムについては、今でもSubversionといった集中型バージョン管理システム(以下集中型VCS)が主流です。
 ただ集中型VCSで継続的な自動テストを構築しようとすると、テスト実行がコミット前であれ後であれ、変更管理で以下のような問題を抱えることになります。

コミット後にテスト実行→テストを失敗させるコミットを防ぎにくい

 自動テストの運用環境では、コミット直後に共用環境側で自動テストを実行する環境がしばしばとられます。これは自動テスト実行をpost-commitに紐づける、CIサーバ等でコミットを監視するといった手段で実現されます。
 ただ集中型VCSでこうした構成をとると、テスト結果がコミット後に明らかになることからテストを失敗させるコミットを防ぎにくくなります。そうなると開発が活発な間は以下のような問題が発生します。

  • コミット・更新の際は、最新リビジョンのテストが失敗していないか注意を向けなければなりません。テストが失敗している間は、変更管理を諦める場面も出てきます。というのも、テストが失敗している状態でコミット/更新によりマージを実行すると、テストを失敗させる変更ミスが作業ファイルにマージされるリスクがあるためです。さらに集中型VCSではマージ前でのコミットが難しいため、そのような問題あるマージからの復帰はしばしば困難です。
  • テスト失敗時の手間を予防するため、コミットの際はテストを失敗させないようにコードを作りこむ必要があります。そこでは途中で変更を記録したいと思ってもその作り込みが終わるまでコミットできません。
  • 集中型VCSではマージ前でのコミットが難しいため、マージがリスクある作業となります。マージしてコミットした変更でテストが失敗した場合、自分の元々の変更に原因があるのか、マージ作業ミスに原因があるのか特定が困難な場面が出てきます。

 なおこうした問題は特にテスト実行に時間がかかる場合や、テスト失敗からの修正に時間がかかる場合で深刻になります。
 まずテスト実行に時間がかかる場合は、コミットからテスト結果が得られるまで結果が不明な状態が生まれるため、誤った変更がマージされたり、一定期間コミットができなくなったりする問題が出てきます。またコミット間隔よりテスト実行時間が長くなると、テスト実行時間に応じてコミットの頻度を下げる、テスト用ブランチを新規で作るといった手間が別途要求されます。
 またテスト失敗からの修正に時間がかかる場合も同様です。そこでの修正中、他の作業者はコミットやアップデートがしづらくなり、変更管理をある程度諦める場面が度々出てきます。

コミット前にテスト実行→こまめな変更管理が困難となる

 一方、上記問題を避ける手段として、コミット直前にテストを実行する構成が取りえます。例えばこれはpre-commitに自動テストをフックする、開発環境やビルドプロセスにテスト実行を組み込む、手動で自動テスト環境を動かすといった手段で実現されます。こうした環境を構築すると、集中型VCSでもテストを成功させたコミットのみを共有リポジトリで管理できるようになります。
 ただこうした構成では、テストを成功させるまでの変更管理が困難となるという制約を抱えています。これらは以下のような問題を生みだします。

  • テストに失敗した場合、元々コミットしようとしてた変更に、テスト失敗を解消するための修正変更を加えることになりますが、そこでの変更管理ができません。修正で元々コミットしようといた変更が破壊されても、元に戻すことがしばしば困難になります。
  • マージ前のコミットが難しいため、マージがリスクある作業となります。マージしてコミットした変更でテストが失敗した場合、他者の変更に原因があるのか、マージ作業ミスに原因があるのか特定が困難な場面が出てきます。


 いずれにせよ自動テストとコミットを紐つけると、それがコミット前で実行するか後に実行するかに関わらず、テストをパスできると十分判断できるまでコミットしにくくなります
 当然ながらバージョン管理はテストをパスさせる作業のみに使われるものでないため、そうした制約は負担を各所で発生させることになります。

集中型VCSの問題の対策の問題

 なお集中型VCSでも、デファクトスタンダードとなっているだけに、上記問題に対する対策も存在します。ただそれらはDVCSと比べ相対的な問題を抱えがちです。いくつかの例を以下に挙げます。

ブランチを活用→ブランチ管理が煩雑化する、軽快にブランチを作れない

 「テストを失敗させるコミットを防げない」「テストをパスできるまで完璧に作り込まないとコミットできない」といった制約を回避する手段として、しばしばブランチが使われています。
 例えばコミットはとりあえずサブブランチに対して行い、そこでテストをパスしたこと確認できたらそれらをメインブランチに統合するというワークフローがその一つです。そこではサブブランチに自由にコミットできつつ、メインブランチは常にテストがパスされた状態を維持できます。
 ただ集中型VCSでは、そうしたワークフローをとると以下のような問題にしばしば直面します。

  • ブランチ管理が煩雑となります。ブランチはすべて共通リポジトリ内で行われるため、共有リポジトリ内にクローズされないブランチが散在しがちです。特に「テスト失敗のため退避ブランチを作る」と言った軽快なブランチ作成を行うと、リポジトリ内のブランチが無秩序に増殖することになります。
  • ブランチの切り替えが煩雑で多用できません。
    • 例えば一般的な集中型VCSでは、アップデート/マージは安定版ブランチから、コミットは作業用ブランチへ、といった柔軟なブランチ切り替えが簡単にできません。そのためブランチ分けしても、実際は作業用ブランチにコミットする作業フェーズと、作業フェーズでの変更を安定版ブランチに反映させる安定版更新フェーズを完全に切り分けて、それらを時系列順に切り替えていく方法を採りがちです。そこでは作業フェーズと安定版更新フェーズは同時並行している訳でないため、「集中型VCSの問題」で挙げた諸問題が作業フェーズ等で度々発生します。
    • また柔軟にブランチを作れません。例えば「マージが怖いからブランチを追加して現在の変更をそちらに退避する」といった一時しのぎ的な操作はしばしば軽快に実行できません。

変更の共有→競合のリスクを高める

 一方、マージに関わる問題回避のために、事前の変更共有という手段もとられます。例えばパッチはその手段の一つです。また多くの集中型VCSでは並列するブランチ間の変更共有機能も存在します。
 ただ集中型VCSでは、しばしば変更共有機能が貧弱なため、事前共有した変更が競合化しやすいリスクをもっています。前倒しで変更を共有すると、そうした無用な競合を避けるためかえってコミットのタイミングや規模に制約が生まれる可能性が出てきます。

DVCSの効果

 このように集中型VCSでは自動テストとの紐づけに関していくつか問題を抱えているのですが、DVCSでは若干の構成管理の手間を加えることによって、そうした問題を軽減・解消することができます。というのも、以下に挙げるような構成・工夫を容易に実現できるためです。

プライベートリポジトリと共有リポジトリの分離

 基本的な構成ですが、DVCSでは下図のように作業者ごとのプライベートな環境と共有環境でリポジトリを分離することができます。ここでは通常の開発はプライベートなリポジトリへのコミットで変更管理します。そして作業完了など、切りのいいタイミングでプッシュを実行してプライベートなリポジトリに格納されている変更を共用リポジトリに反映させます。

f:id:goyoki:20111228005503p:image

 こうした構成をとると、各作業者は共用リポジトリに紐付けられた自動テストを意識することなく、自由に作業の変更管理ができるようになります。例えば自動テスト実行前の状態をコミットしたり、マージ実行前の状態をコミットしたりすることができますし、自動テストを失敗させるかもしれない変更も任意にコミット可能です。これによってよりバージョン管理の恩恵を受けられるようになり、特にマージ作業や自動テスト失敗からの修正作業が大きく効率化されます。また自動テストが失敗していたり、あるいは使用不能になっている状態でもバージョン管理が可能となります。

共有リポジトリの階層化

 DVCSではさらに共有環境上でのリポジトリの階層化も一般的に容易に可能です。
 例えば下図のように、共用環境側でテストの入力となる変更を管理するリポジトリと、テストをパスした変更を安定版として管理するリポジトリを分けて構築することができます。
 ここでは通常の変更はテストの入力となるリポジトリにコミットしつつ、他の作業者の変更は安定板リポジトリからプルorマージすることで取り込んでいきます(一般的なDVCSではそのようなリポジトリの切り替えを容易に実行可能です)。また両者のリポジトリでの変更共有は、Gate Keeper役を置いたり、あるいは自動テストに紐付けてコミットを自動化したりする等の手段で実現します。
f:id:goyoki:20111228005504p:image

 こうした構成を構築すると、さらに複数作業者の間での変更共有を、自動テストの結果に依存せず実行できるようになります。すなわち前述した「作業フェーズ」と「安定版更新フェーズ」を同時並行で進められるようになります。

コミットの整形

 またDVCSでは手軽にコミットを整える手段をいくつか持っています。
 例えば一般的にブランチ管理が容易なことから、下図のような構成でプライベート環境にてブランチを活用すると、共用リポジトリに対して、複数のコミットログを1つのブランチにまとめてコミットすることができます。
 また集中型リポジトリではリポジトリ破損リスクのため滅多に使えなかったいわゆる歴史改変(コミットの破棄等)も、プライベートなリポジトリでは自己責任において柔軟に活用できます。そのため間違ったコミットの差し戻しや冗長なコミットログの破棄を行って、共有リポジトリに対しては整ったコミットを行うことが可能です。
f:id:goyoki:20111228120118p:image

 こうした手段でコミットの整形を行えば、プライベート環境では自由にバージョン管理しながら、共用環境に変更を送るときは自動テストを意識しつつコミットの粒度や間隔を調整できるようになります。こうした工夫はテスト実行のタイミングを調整したり、チケット駆動開発のようなコミットに規律やルールを求めるプラクティスを導入する際に有効に働きます。

まとめ

 上記のように、DVCSは作業者による自由な変更管理と、自動テストと連携した規律の要求される変更管理を共立させます。結果、自動テストの運用体制の自由度を広げることができます。
 一方DVCSは集中型VCSとしての運用も可能性ですし、多少の制約もありますが集中型VCSリポジトリに対してクライアントとしても導入することもできます(Subversionリポジトリを扱えるBazaarや、git-svnといった拡張等)ので、あえて導入しない理由も意外と多くありません。継続的な自動テストを考えているのならお勧めできるツールです。