継続的インテグレーション(オリジナル版)

ソフトウェア開発プロセスの重要な部分として、ソフトウェアの信頼性の高いビルドを取得することが挙げられます。その重要性にもかかわらず、これが行われていないことに驚くことがよくあります。ここでは、Thoughtworksの大規模プロジェクトでMattが導入したプロセスについて説明します。これは、会社全体でますます使用されているプロセスです。これは、テストを含む完全に自動化され、再現可能なビルドを強調しており、1日に何度も実行されます。これにより、各開発者は毎日統合できるようになり、統合の問題が軽減されます。

2000年9月10日



この記事は、より最新のバージョンに置き換えられました。

ソフトウェア開発には、多くのベストプラクティスがありますが、それらはしばしば議論されるものの、実際に行われることは少ないようです。最も基本的な、そして貴重なプラクティスの一つに、チームが1日に何度もソフトウェアをビルドしてテストすることを可能にする、完全に自動化されたビルドおよびテストプロセスがあります。デイリービルドのアイデアは、多く語られてきました。McConnnellはそれをベストプラクティスとして推奨しており、それは長い間Microsoftの開発アプローチの特徴として知られています。しかし、私たちはXPコミュニティと同様に、デイリービルドは最低限であると考えています。1日に数回ビルドすることを可能にする完全に自動化されたプロセスは、実現可能であり、努力する価値があります。

私たちは、XP(エクストリームプログラミング)のプラクティスの一つとして使用されている「継続的インテグレーション」という用語を使用しています。しかし、このプラクティスは長い間存在しており、XPを仕事に適用することを決して考えない多くの開発者によって使用されていることを認識しています。私たちはソフトウェア開発プロセスにおいてXPをタッチストーンとして使用しており、それは私たちの用語とプラクティスの多くに影響を与えています。しかし、XPの他の部分を使用せずに継続的インテグレーションを使用することもできます。実際、私たちはそれがあらゆる有能なソフトウェア開発活動の不可欠な部分であると考えています。

自動化されたデイリービルドを機能させるには、いくつかの部分があります。

これらすべてには、ある程度の規律が必要です。プロジェクトに導入するには多くの労力が必要であることがわかりました。また、一度インストールされると、維持するのにそれほど労力はかからないこともわかりました。

継続的インテグレーションのメリット

継続的インテグレーションについて最も表現するのが難しいことの1つは、それが開発パターン全体に根本的な変化をもたらすことであり、それを実践した環境で働いたことがない場合は、見にくいことです。実際、ほとんどの人は、一人で作業している場合はこの雰囲気を見ます。なぜなら、彼らは自分自身としか統合しないからです。多くの人にとって、チーム開発には、その領域の一部である特定の問題が伴います。継続的インテグレーションは、ある程度の規律と引き換えに、これらの問題を軽減します。

継続的インテグレーションの基本的なメリットは、ある人の作業が別の人々の作業を妨害し、どちらの人も何が起こったのかを気づかない状況で、バグの追跡に時間を費やすセッションがなくなることです。これらのバグは、問題が1人の担当領域ではなく、2つの作業の相互作用にあるため、見つけるのが困難です。この問題は時間とともに悪化します。統合バグは、最初に発生する数週間または数ヶ月前に挿入されることがよくあります。その結果、発見に多くの時間がかかります。

継続的インテグレーションを使用すると、このようなバグの大部分は導入された当日には明らかになります。さらに、相互作用の少なくとも半分がどこにあるかがすぐにわかります。これにより、バグの検索範囲が大幅に削減されます。そして、バグが見つからない場合は、問題のあるコードを製品に投入することを避けられるため、最悪の場合でも、バグも追加する機能を追加しないことになります。(もちろん、バグを嫌うよりも機能を優先したい場合もありますが、少なくともこの方法であれば、情報に基づいた選択ができます。)

もちろん、すべての統合バグが解決されるとは限りません。この手法はテストに依存しており、ご存知のとおり、テストはエラーがないことを証明するものではありません。重要なのは、継続的インテグレーションがコストに見合うだけの十分なバグを捕捉することです。

これらすべてがもたらす最終的な結果は、統合バグの追跡に費やす時間を削減することによる生産性の向上です。これを科学的な研究に近づけて行った人がいるかどうかは知りませんが、逸話的な証拠はかなり強力です。継続的インテグレーションは、「統合地獄」に費やす時間を大幅に削減でき、実際には地獄を非イベントに変えることができます。

頻繁に行うほど良い

継続的インテグレーションの中心には、根本的に反直感的な効果があります。それは、めったに統合するよりも頻繁に統合する方が良いということです。それを実行する人にとっては明らかですが、実行したことがない人にとっては、直接的な経験とは矛盾しているように見えます。

毎日よりも少ない頻度で、例えば週に1回だけ統合する場合、統合は時間とエネルギーを多く消費する苦痛な作業になります。実際、それは非常に面倒なので、最もしたくないことは、より頻繁に行うことです。「これほど大きなプロジェクトでは、デイリービルドはできません」というコメントをよく聞きます。

しかし、実行するプロジェクトもあります。私たちは、50人のチームが作業する約20万行のコードベースで、1日に数十回ビルドします。Microsoftは、数千万行のコードを持つプロジェクトでデイリービルドを行っています。

これが可能な理由は、統合の労力が、統合間の時間量に比例して指数関数的に増加するからです。これに関する測定値は認識していませんが、これは、週に1回統合するのに、1日に1回統合するのに5倍の時間ではなく、25倍の時間かかることを意味します。したがって、統合が苦痛であれば、より頻繁に統合できないという兆候としてそれを受け取るべきではありません。適切に行えば、より頻繁な統合は苦痛ではなくなり、統合の実行に費やす時間が大幅に短縮されます。

その鍵となるのは自動化です。統合の大部分は、自動化でき、自動化する必要があります。ソースの取得、コンパイル、リンク、および重要なテストはすべて自動化できます。最終的には、ビルドが成功したかどうかを示す簡単な指標が残されます。成功した場合は無視し、失敗した場合は、構成の最後の変更を簡単に元に戻し、今回はビルドが確実に動作することを確認できます。動作するビルドを取得するために、考えは必要ありません。

このような自動化されたプロセスを使用すると、好きなだけ頻繁にビルドできます。唯一の制限は、ビルドにかかる時間です。

成功したビルドとは何か?

成功したビルドを何にするかを決定することが重要です。それは明白に見えるかもしれませんが、それがどのように曖昧になるかは注目に値します。マーティンはかつてあるプロジェクトをレビューしました。彼は、そのプロジェクトがデイリービルドを行っているかどうかを尋ね、肯定的な回答を得ました。幸いなことに、ロン・ジェフリーズがいて、さらに詳しく調べることができました。彼は「ビルドエラーをどうしますか?」という質問をしました。答えは「関連する担当者にメールを送信します」でした。実際、そのプロジェクトは数ヶ月間ビルドに成功していませんでした。それはデイリービルドではなく、デイリービルドの試みです。

私たちは、成功したビルドの意味について非常に積極的です。

  • 最新のソースがすべて構成管理システムからチェックアウトされます。
  • すべてのファイルがゼロからコンパイルされます。
  • 結果のオブジェクトファイル(私たちの場合、Javaクラス)がリンクされ、実行のためにデプロイされます(jarファイルに配置されます)。
  • システムが起動され、テストスイート(私たちの場合、約150個のテストクラス)がシステムに対して実行されます。
  • これらのステップがすべてエラーや人間の介入なしで実行され、すべてのテストがパスした場合、成功したビルドになります。

ほとんどの人は、コンパイルとリンクをビルドと考えています。少なくとも、ビルドにはアプリケーションの起動と、それに対するいくつかの簡単なテストの実行を含める必要があると考えています(McConnnellは「スモークテスト」という用語を使用しました。スイッチを入れて、煙が出るかどうかを確認します)。より包括的なテストセットを実行すると、継続的インテグレーションの価値が大幅に向上するため、私たちもそうすることを好みます。

単一ソースポイント

簡単に統合するために、どの開発者も簡単に最新のソースの完全なセットを取得できる必要があります。異なる開発者に最新のビットを依頼し、それらをコピーして配置場所を特定するという作業よりも悪いものはありません。ビルドを開始する前に、これらすべての作業を行う必要があります。

標準は単純です。誰でもクリーンなマシンを用意し、ネットワークに接続し、単一のコマンドを使用して、開発中のシステムをビルドするために必要なすべてのソースファイルをダウンロードできる必要があります。

明白な(願わくば)解決策は、すべてのコードのソースとして構成管理(ソースコントロール)システムを使用することです。構成管理システムは通常、ネットワークを介して使用するために設計されており、人々が簡単にソースを取得できるツールを備えています。さらに、バージョン管理も含まれているため、さまざまなファイルの以前のバージョンを簡単に検索できます。CVSは優れたオープンソースの構成管理ツールであるため、コストの問題はありません。

これが機能するには、すべてのソースファイルを構成管理システムに保存する必要があります。すべてというのは、人々が考えるよりも多くの場合が多くなります。ビルドスクリプト、プロパティファイル、データベーススキーマDDL、インストールスクリプトなど、クリーンなマシンでビルドするために必要なものも含まれます。コードが管理されているにもかかわらず、見つける必要がある他の重要なファイルが見落とされていることがよくあります。

構成管理システム内の単一のソースツリーの下にすべてを配置するようにしてください。場合によっては、異なるコンポーネントに対して構成管理システムで個別のプロジェクトを使用する人もいます。これの問題点は、人々がどのバージョンのコンポーネントがどのバージョンの他のコンポーネントと機能するかを覚える必要があることです。状況によってはソースを分離する必要がありますが、これらの状況はあなたが考えるよりもはるかに少ないです。単一のソースツリーから複数のコンポーネントをビルドできます。このような問題は、ストレージ構造ではなく、ビルドスクリプトで処理する必要があります。

自動ビルドスクリプト

数十個のファイルを持つ小さなプログラムを作成している場合、アプリケーションのビルドは、コンパイラへの単一のコマンドの問題にすぎない場合があります。javac *.java。より大きなプロジェクトでは、さらに多くのものが必要です。このような場合、多くのディレクトリにファイルがあります。結果のオブジェクトコードが適切な場所に配置されていることを確認する必要があります。コンパイルに加えて、リンクステップがある場合があります。コンパイルする前に生成する必要がある他のファイルから生成されたコードがあります。テストは自動的に実行する必要があります。

大規模なビルドには時間がかかります。小さな変更だけなら、これらすべてのステップを実行する必要はありません。そのため、優れたビルドツールは、プロセスの一部として変更が必要なものを分析します。一般的な方法は、ソースファイルとオブジェクトファイルの日付をチェックし、ソースの日付が後の場合にのみコンパイルすることです。依存関係は厄介になります。1つのオブジェクトファイルが変更されると、それに依存するファイルも再ビルドが必要になる場合があります。コンパイラは、このような処理に対応する場合と、対応しない場合があります。

ニーズによっては、ビルドする必要があるものが異なります。テストコードの有無、またはテストの異なるセットでシステムをビルドできます。一部のコンポーネントはスタンドアロンでビルドできます。ビルドスクリプトでは、さまざまなケースに対応する代替ターゲットをビルドできるようにする必要があります。

単純なコマンドラインを超えると、スクリプトが負荷を処理することが多くなります。これらはシェルスクリプトの場合もあれば、perlやpythonなどのより高度なスクリプト言語を使用する場合もあります。しかし、すぐにUnixのmakeツールのような、このような目的に設計された環境を使用することが理にかなってきます。

私たちのJava開発では、すぐに本格的なソリューションが必要であることに気づきました。実際、Mattはかなりの時間を費やし、エンタープライズJavaの作業用に設計されたJinxというビルドツールを開発しました。しかし最近、オープンソースのビルドツールAntに切り替えました。AntはJinxと非常に似た設計で、JavaファイルをコンパイルしてJarにパッケージ化できます。また、ビルド内で他のタスクを実行できるように、独自のAnt拡張機能を簡単に記述することもできます。

多くの人がIDEを使用しており、ほとんどのIDEには何らかのビルド管理プロセスが含まれています。しかし、これらのファイルは常にIDEに固有のものであり、多くの場合脆弱です。さらに、IDEが動作する必要があります。IDEユーザーは独自のプロジェクトファイルを設定し、個々の開発に使用します。しかし、私たちは主要なビルドにはAntに依存しており、マスタービルドはAntを使用してサーバー上で実行されます。

自己テストコード

プログラムがコンパイルされるだけでは十分ではありません。厳密に型付けされた言語のコンパイラは多くの問題を発見できますが、成功したコンパイルでも多くのエラーが通過します。これらのエラーを追跡するために、私たちは自動化されたテスト規律に重点を置いています。これはXPでも推奨されているもう1つのプラクティスです。

XPはテストを2つのカテゴリに分類します。単体テストと受け入れテスト(機能テストとも呼ばれます)。単体テストは開発者が作成し、通常は個々のクラスまたは小さなクラスグループをテストします。受け入れテストは通常、顧客または外部のテストグループ(開発者の協力を得て)によって作成され、システム全体をエンドツーエンドでテストします。私たちは両方の種類のテストを使用し、両方の種類のテストをできるだけ自動化します。

ビルドの一環として、「BVT」(ビルド検証テスト)と呼ばれる一連のテストを実行します。成功したビルドにするには、BVTのすべてのテストに合格する必要があります。すべてのXPスタイルの単体テストはBVTに含まれています。この記事はビルドプロセスに関するものであるため、ここでは主にBVTについて説明しますが、BVT以外にも2番目のテストラインがあることを覚えておいてください。したがって、BVTだけでテストとQAの取り組み全体を判断しないでください。実際、QAグループは、動作するビルドでのみ作業するため、BVTに合格しない限り、コードを見ることはありません。

基本原則は、開発者がコードを作成するときに、そのコードのテストも作成することです。タスクを完了すると、本番コードをチェックインするだけでなく、そのコードのテストもチェックインします。XPに厳密に従う人は、テストファーストスタイルのプログラミングを使用します。失敗するテストがない限り、コードを書いてはいけません。したがって、システムに新しい機能を追加する場合は、最初にその機能が存在する場合にのみ機能するテストを作成し、そのテストを機能させます。

テストは、開発に使用しているのと同じ言語であるJavaで記述します。これにより、テストの作成はコードの作成と同じになります。テストの編成と記述には、JUnitフレームワークを使用します。JUnitは、テストを迅速に記述し、スイートに編成し、スイートを対話的にまたはバッチモードで実行できるシンプルなフレームワークです。(JUnitはxUnitファミリーのJavaメンバーであり、ほとんどすべての言語でバージョンがあります。)

開発者は、ソフトウェアを作成している際に、通常、コンパイルごとに単体テストのサブセットを実行します。テストはコード内の論理エラーを見つけるのに役立つため、これは実際には開発作業のスピードアップにつながります。次に、デバッグするのではなく、最後にテストを実行してからの変更を確認できます。これらの変更は小さくなければならないため、バグを見つけやすくなります。

誰もがXPのテストファーストスタイルで厳密に作業しているわけではありませんが、重要な利点は、同時にテストを作成することです。個々のタスクの高速化に加えて、BVTを構築してエラーを検出する可能性を高めます。BVTは1日に数回実行されるため、BVTによって検出された問題は、同じ理由で簡単に見つけることができます。バグを見つけるために、少量の変更されたコードを確認できます。この変更されたコードを確認することで行うデバッグは、実行中のコードをステップ実行することで行うデバッグよりも効果的なことがよくあります。

もちろん、テストですべてを見つけることができるとは限りません。よくあることですが、テストはバグがないことを証明するものではありません。しかし、完璧さが良いBVTから恩恵を受ける唯一のポイントではありません。不完全なテストを頻繁に実行することは、決して記述されない完璧なテストよりもはるかに優れています。

関連する質問は、開発者が自分のコードのテストを書くという問題です。自分の仕事ではエラーを見落としやすいという理由から、人々は自分のコードをテストすべきではないとよく言われます。これは真実ですが、自己テストプロセスでは、テストをコードベースに迅速に反映させる必要があります。その迅速な反映の価値は、個別のテスターの価値よりも大きいです。そのため、BVTについては開発者によって記述されたテストに依存していますが、独立して記述された個別の受け入れテストがあります。

自己テストのもう1つの重要な部分は、フィードバックを使用してテストの品質を向上させることです。これはXPの重要な価値です。ここのフィードバックは、BVTから漏れたバグの形で提供されます。ここでのルールは、失敗する単体テストをBVTに追加するまで、バグを修正してはならないということです。このように、バグを修正するたびに、それが再びすり抜けないようにテストを追加します。さらに、このテストにより、BVTを強化するために記述する必要がある他のテストについて考えることができます。

マスタービルド

ビルドの自動化は個々の開発者にとって非常に理にかなっていますが、真価を発揮するのは、チーム全体のためのマスタービルドを生成する場合です。マスタービルド手順を導入することで、チームが結束し、統合の問題を早期に発見しやすくなることがわかりました。

最初のステップは、マスタービルドを実行するマシンを選択することです。私たちはTrebuchet(Age of Empiresをよくプレイしていました)という4プロセッササーバーを使用しており、これはほぼビルドプロセス専用です。(ビルドに時間がかかっていた初期の頃は、この処理能力が不可欠でした。)

ビルドプロセスは、常に実行されているJavaクラスにあります。ビルドが行われていない場合、ビルドプロセスはwhileループで待機し、数分ごとにリポジトリをチェックします。前回のビルド以降に誰もコードをチェックインしていない場合、待機を続けます。リポジトリに新しいコードがある場合、ビルドを開始します。

ビルドの最初の段階は、リポジトリから完全なチェックアウトを行うことです。StarteamはJava用の非常に優れたAPIを提供しているため、リポジトリに接続するのは簡単でした。ビルドデーモンは、現在時刻の5分前にリポジトリを確認し、過去5分間に誰かがチェックインしたかどうかを確認します。もしそうであれば、5分前のコードをチェックアウトしても安全であると見なします(これにより、リポジトリをロックせずに、誰かがチェックインしている最中にチェックアウトするのを防ぎます)。

デーモンはTrebuchetのディレクトリをチェックアウトします。すべてチェックアウトされると、デーモンはディレクトリ内のantスクリプトを呼び出します。次にAntが引き継ぎ、完全なビルドを実行します。すべてのソースから完全なビルドを行います。antスクリプトは、コンパイルと、結果のクラスファイルをEJBサーバーにデプロイするための6つのjarに分割するまで実行されます。

Antがコンパイルとデプロイメントを完了すると、ビルドデーモンは新しいjarでEJBサーバーを起動し、BVTテストスイートを実行します。テストが実行され、すべて合格すると、成功したビルドとなります。ビルドデーモンはStarteamに戻り、チェックアウトされたソースにビルド番号を付けます。次に、ビルド中に誰かがチェックインしたかどうかを確認し、もしそうであれば、別のビルドを開始します。そうでない場合、デーモンはwhileループに戻り、次のチェックインを待ちます。

ビルドの終わりに、ビルドデーモンは、そのビルドで新しくコードをチェックインしたすべての開発者に電子メールを送信します。電子メールには、そのビルドのステータスが要約されています。コードをチェックインした後にビルドを終了する前に、その電子メールを受け取ることは、適切な手順ではありません。

デーモンは、すべてのステップのログをXMLログファイルに書き込みます。Trebuchetでサーブレットが実行され、誰でもログを検査することでビルドの状態を確認できます。

図1:ビルドサーブレット

画面には、現在ビルドが実行中かどうか、そして実行中であればいつ開始されたかが表示されます。左下には、成功したかどうかにかかわらず、すべてのビルドの履歴があります。ビルドをクリックすると、そのビルドの詳細(コンパイルされたかどうか、テストのステータス、加えられた変更など)が表示されます。

多くの開発者がこのウェブページを定期的に見ていることがわかりました。これにより、プロジェクトで何が起こっているか、そして人々がチェックインする際に何が変更されているかを理解できます。いずれは他のプロジェクトニュースもそのページに掲載するかもしれませんが、関連性を失わないようにしたいと考えています。

どの開発者でも、自分のマシンでマスタービルドをローカルにシミュレートできることが重要です。これにより、統合エラーが発生した場合、開発者はマスタービルドプロセスを妨げることなく、自分のマシンで問題を調査およびデバッグできます。さらに、開発者はマスタービルドが失敗する可能性を減らすために、チェックインする前にローカルでビルドを実行できます。

ここで、マスタービルドはクリーンビルド(つまり、ソースのみからのビルド)にするべきか、増分ビルドにするべきかという妥当な疑問があります。増分ビルドははるかに高速になる可能性がありますが、何かがコンパイルされなかったために問題がこっそり入り込むリスクもあります。また、ビルドを再現できないリスクもあります。私たちのビルドは非常に高速です(約20万行のコードで約15分)ため、毎回クリーンビルドを実行することに満足しています。ただし、一部の企業では、ほとんどの場合増分ビルドを実行しますが、そのような奇妙なエラーが発生した場合に備えて、定期的なクリーンビルド(少なくとも毎日)を実行します。

チェックイン

自動化されたビルドを使用すると、開発者はソフトウェアを開発する際に特定のリズムに従うようになります。そのリズムの最も重要な部分は、定期的に統合することです。私たちは、日次ビルドを行っている組織に出会いましたが、人々は頻繁にチェックインしていません。開発者が数週間ごとにしかチェックインしない場合、日次ビルドはほとんど役に立ちません。私たちは、すべての開発者がほぼ1日に1回コードをチェックインするという一般的な原則に従っています。

新しいタスクを開始する前に、開発者は最初に構成管理システムと同期する必要があります。これにより、ソースのローカルコピーが最新の状態になります。古いソースの上にコードを作成すると、トラブルや混乱につながるだけです。

次に、開発者はタスクに取り組み、変更が必要なファイルを更新します。開発者は、タスクが完了したとき、またはタスクの途中で統合できますが、統合するにはすべてのテストが正常に実行される必要があります。

統合の最初のステップは、開発者の作業コピーをリポジトリと再同期させることです。リポジトリで変更されたファイルは作業ディレクトリにコピーされ、構成管理システムは開発者に競合があれば警告します。その後、開発者は同期された作業セットをビルドし、それらのファイルでBVTを正常に実行する必要があります。

これで、開発者は新しいファイルをリポジトリにコミットできます。コミットが完了したら、マスタービルドを待つ必要があります。マスタービルドが成功すれば、チェックインは成功です。失敗した場合は、開発者は問題を修正し、簡単な修正であれば修正をコミットできます。より複雑な場合は、変更を元に戻し、作業ディレクトリを再同期して、ローカルで問題を解決してから再度コミットする必要があります。

一部のチェックインプロセスでは、チェックインプロセスの直列化が強制されます。この場合、1人の開発者だけが取得できるビルドトークンがあります。開発者はビルドトークンを取得し、作業コピーを再同期し、変更をコミットし、トークンを解放します。これにより、ビルド間のリポジトリの更新が複数人で行われるのを防ぎます。ビルドトークンなしでも問題が発生することはほとんどないため、使用していません。複数の人が同じマスタービルドにコミットすることは頻繁にありますが、それがビルドエラーの原因となることはまれであり、通常は簡単に修正できます。

また、チェックイン前にどれほど注意深く行うかは、開発者の判断に委ねています。統合エラーが発生する可能性の高さを開発者が判断します。可能性が高いと考える場合は、チェックイン前にローカルビルドを最初に実行し、統合エラーの可能性が低いと考える場合は、単にチェックインします。間違っていた場合、マスタービルドの実行時に判明し、変更を元に戻して何が間違っていたのかを調べなければなりません。エラーは、迅速に見つけて簡単に削除できる場合は許容できます。

まとめ

規律のある自動化されたビルドプロセスを開発することは、プロジェクトの制御に不可欠です。多くのソフトウェアの専門家がそう言いますが、現場では依然としてまれです。

重要なのは、すべてを完全に自動化し、プロセスを頻繁に実行して、統合エラーを迅速に見つけることです。その結果、統合エラーが発生しても、容易に見つけて修正できるとわかっているため、必要に応じて変更することに皆がより備えることができます。これらの利点が得られると、それらを放棄する前に抵抗するでしょう。


重要な改訂

2000年9月10日: 初版発行