第7回

Eclipse、CVS、Antを利用した
継続的インテグレーション&テスト環境の構築


縣俊貴
橋本正徳
Project Mobster/メディアファイブ株式会社

2003/6/13

 XPのプラクティスに「継続的インテグレーション」というのがあります。JUnitによるテストと同様にXP開発ではないプロジェクト(チーム)でも実行可能な項目です。新規クラスの追加やリファクタリングの後、リファクタリングがほかのクラスに影響を及ぼしたことや、ユニットテストでも見つからなかった結合時に発生するバグを継続的インテグレーションを行えば見つけることができます。さらに、テスト用マシンを本番環境さながらに用意しておけば、環境依存のバグをいとも簡単に発見することができます。継続的インテグレーションで高速道路をフルスロットルで飛ばしましょう!

継続的インテグレーションのメリットとビルド専用マシン

  継続的インテグレーションを実践するメリットはいくつか考えられます。一番のメリットは環境依存のバグを発見できる点です。特にXPでは「ビルド専用マシン」を1台確保して、そのマシンで最新ソースのビルド、テストの実行、動作確認を行います。このことにより、OS、JDK、ライブラリ、環境変数などの違いによるバグを「早期に発見」することができます。「お客さんの環境で初めてバグが発見された」という最悪の事態を避けるためにも最終の実行環境に限りなく近いビルド用のマシンを用意しましょう。また、テストケースが1000を超えるような場合、すべてのテストを実行するにはかなりの時間がかかるようになってきます。そのような場合、自動テスト環境を構築してしまえば、テストのオーバーヘッドを最小限にすることができます。

継続的インテグレーション&テスト環境の構築のチュートリアル

 今回は継続的インテグレーションのチュートリアルを用意しました。このチュートリアルでは自動的に1時間に1回、CVSより最新のソースを取得します。その後Antによるコンパイル・テストを行い、テストの結果をAntのJUnitReportタスクでHTMLに変換します。最終的にテスト結果をWebブラウザで確認できる環境を構築します。チュートリアルの環境は次のようになります(図1)。ビルド用の別のマシンを1台確保する点がポイントです。できれば、このビルドマシンは常時稼働できて、ビルド専用のマシンとして利用できるのが理想です。

図1 チュートリアルの環境

 なお、CVS for NTはすでにセットアップ済みとします。まだ導入されていない方は本連載の第2回の「CVSとEclipseで『コードの共同所有』」を参考にしてください。

図2 JUnitの結果をWebブラウザで確認(クリックすると拡大)

(1)チュートリアル用のプロジェクトの作成

  1. [ファイル] - [新規作成] - [プロジェクト] - [Javaプロジェクト] - [次へ]

  2. [プロジェクト名]に「Integration」と入力して[次へ]

  3. 「プロジェクトに含まれるソース・フォルダを使用」を選択して「新規フォルダの作成」

  4. [新規ソースフォルダー]ダイアログが表示されるので相対パス「src」を入力して「OK」

  5. 「ビルド出力フォルダをIntegration/binにしますか」と聞いてくるので「はい」

  6. 「終了」

  7. [ファイル] - [新規作成] - [フォルダー] - フォルダ名に「lib」と入力 - [終了]

 以上の操作でチュートリアル用プロジェクトが作成されました。プロジェクトの構成は以下のようになります。

図3 プロジェクトの新規作成

(2)junit.jarをプロジェクトに追加

 junit.jarをプロジェクトに追加します。次の方法でjunit.jarをインポートします。

  1. [ファイル] - [インポート]

  2. [ファイルシステム]を選択 - [次へ]

  3. 「ディレクトリ」の[ブラウズ]をクリックして「ECLIPSE_HOME/plugins/org.junit_3.7.0」を選択

  4. ファイルの一覧が表示されるので「junit.jar」にチェックを入れる

  5. [インポートするリソースの宛先の選択]のフォルダの「ブラウズ」をクリックして「Integration/lib」を選択

  6. 次のような状態になっていることを確認して[終了]
図4 junit.jarをプロジェクトに追加する(クリックすると拡大)

(3)junit.jarをクラスパスに追加

  1. [プロジェクトで右クリック] - [プロパティ]

  2. [Javaのビルドパス]を選択

  3. [ライブラリ]タブを選択

  4. [JARの追加]で(2)でインポートしたjunit.jarを選択 - [OK]

  5. [OK]でプロパティダイアログを閉じる
図5junit.jarをクラスパスに追加する(クリックすると拡大)

(4)クラスとテストケースのソースを作成

 テスト対象クラスとテストクラスを作成します。いつものStringUtilを例として使用します。

    [ファイル] - [新規作成] - [クラス]で以下の2つのクラスを作成します。
package sample;

import java.util.StringTokenizer;

/**

  * StringUtil
  */
public class StringUtil {
         /**
          * 文字列の分割(区切り文字指定)
          * @param str 対象文字列
          * @param delim 区切り文字列
          * @return 分割後の文字列
          */
         public static String[] split(String str, String delim) {
                  StringTokenizer st = new StringTokenizer(str, delim);
                  int length = st.countTokens();
                  String[] result = new String[length];
                  for (int i = 0; i < length; i++) {
                      result[i] = st.nextToken();
                  }
                  return result;
         }

}

パッケージ:sample
クラス名:StringUtil
スーパークラス:java.lang.Object

package sample;

import junit.framework.TestCase;

/**
  *TestCaseを継承したStringUtilのテストケース
  */
public class StringUtilTest extends TestCase {

          public StringUtilTest(String arg0) {
                  super(arg0);
          }

          public void testSplit() {
                  // 分割対象の文字
                  String str = "a,b,c";

                  // カンマで文字列を分割
                  String[] result = StringUtil.split(str, ",");

                  // 以下結果を確認 -----------

                  // resultはnullではないはず
                  assertNotNull(result); //

                  // 配列の長さは3のはず
                  assertEquals(3, result.length);

                  // 分割結果が配列に格納されているはず
                  assertEquals("a", result[0]);
                  assertEquals("b", result[1]);
                  assertEquals("c", result[2]);
          }

          public void testSplit2() {
                  String str = null;
                  String result[] = null;

                  // 別の文字列を指定
                  str = "A*B*C";
                  result = StringUtil.split(str, "*");
                  assertNotNull(result);
                  assertEquals(3, result.length);
                  assertEquals("A", result[0]);
                  assertEquals("B", result[1]);
                  assertEquals("C", result[2]);

                  // 長い区切り文字
                  str = "123[SEP]456[SEP]789";
                  result = StringUtil.split(str, "[SEP]");
                  assertNotNull(result);
                  assertEquals(3, result.length);
                  assertEquals("123", result[0]);
                  assertEquals("456", result[1]);
                  assertEquals("789", result[2]);
          }

          public void testSplit3() {
                  // 分割対象の文字(空文字列あり)
                  String str = "a,,";

                  // カンマで文字列を分割
                  String[] result = StringUtil.split(str, ",");

                  // 以下結果を確認 -----------

                  // resultはnullではないはず
                  assertNotNull(result);

                  // 配列の長さは3のはず
                  assertEquals(3, result.length);

                  // 分割結果が配列に格納されているはず
                  assertEquals("a", result[0]);
                  assertEquals("", result[1]);
                  assertEquals("", result[2]);

          }
}
パッケージ:sample
クラス名:StringUtilTest
スーパークラス:junit.framework.TestCase

(5)Antでビルドファイルを作成する

 CVSからファイルの更新、コンパイル、テストの実行・テスト結果の出力を行うAntの定義ファイルを作成します。

  1. [ファイル] - [新規作成] - [ファイル]

  2. 「Integration」プロジェクトのトップディレクトリを選択

  3. ファイル名に「build.xml」と入力して[終了]

  4. 「build.xml」に以下の内容を記述する
<?xml version="1.0" encoding="Shift_JIS"?>
<project name="Integration" default="javac" basedir="./">

         <!-- プロパティ -->
         <property name="src.dir" value="./src"/>
         <property name="classes.dir" value="./bin"/>
         <property name="lib.dir" value="./lib"/>
         <property name="doc.dir" value="./doc"/>
         <property name="report.dir" value="${doc.dir}/reports"/>
         <property name="result.dir" value="${doc.dir}/result"/>


         <!-- ライブラリ -->
         <path id="libs">
                 <fileset dir="./lib">
                          <include name="*"/>
                 </fileset>
         </path>

         <!-- 初期化 -->
         <target name="init">
                 <mkdir dir="${classes.dir}"/>
                 <mkdir dir="${doc.dir}"/>
                 <mkdir dir="${report.dir}"/>
         </target>

         <!-- コンパイル -->
         <target name="javac" depends="init">
                 <javac srcdir="${src.dir}"
                                  destdir="${classes.dir}">
                          <classpath refid="libs"/>
                 </javac>
         </target>

         <!-- テスト -->
         <target name="junit" depends="javac">
                          <delete>
                                         <fileset dir="${report.dir}" includes="**/*" />
                          </delete>

                          <!-- テストを実行して結果をXMLファイルとして出力 -->
                          <junit printsummary="yes" haltonfailure="no">
                                         <classpath>
                                            <pathelement location="${classes.dir}"/>
                                            <fileset dir="./lib">
                                                      <include name="*"/>
                                         </fileset>
                                         </classpath>
                                         <formatter type="xml"/>
                                         <batchtest fork="yes" todir="${report.dir}">
                                                      <fileset dir="${src.dir}">
                                                                         <include name="**/*Test.java"/>
                                                      </fileset>
                                         </batchtest>
                          </junit>
         </target>

         <!-- JUnitReport -->
         <target name="report" depends="junit">

                  <!-- XMLファイルを基にHTML形式のレポートを出力 -->
                  <junitreport todir="${report.dir}">
                                    <fileset dir="${report.dir}">
                                                      <include name="TEST-*.xml"/>
                                    </fileset>
                                    <report format="frames" todir="${result.dir}"/>
                  </junitreport>

                  <!-- タイムスタンプ作成 -->
                  <tstamp>
                         <format property="NOW" pattern="yyyy-MM-dd HH:mm:dd"/>
                  </tstamp>

                  <!-- 実行時間を知るためにHTMLの一部を現在の日付時刻に置換 -->
                  <replace file="${result.dir}/overview-summary.html"
                         token="Unit Test Results"
                         value="Unit Test Results - ${NOW}"/>
         </target>

         <!-- CVS update -->
         <target name="cvs">
                  <cvs command="update -P -d"/>
         </target>
</project>
build.xml

(6)定期実行用のバッチファイルを作成する

 ビルドマシンで実行するバッチファイルを作成しておきます。このバッチファイルをCronで定期的に実行することになります。

  1. [ファイル] - [新規作成] - [ファイル]

  2. 「Integration」プロジェクトのトップディレクトリを選択

  3. ファイル名に「build.bat」と入力して[終了]

  4. 「build.bat」に以下の内容を記述する
cd c:\project\Integration ……(ビルドマシンの環境に合わせます)
call ant cvs
call ant report

(7)プロジェクトをCVSに登録する

  1. [プロジェクトを右クリック] - [チーム] - [プロジェクトの共用]

  2. [既存のリポジトリーロケーションを使用]のリポジトリを選択 - [次へ]

  3. [プロジェクト名をモジュール名として使用]をチェック - [次へ]

  4. [終了]

  5. [同期化ビュー]のトップディレクトリで[右クリック] - [コミット]

 以上で準備は完了です。最終的なファイル構成は次のようになります。

図6 最終的なファイル構成

ビルドマシンを手に入れろ!
 こういう話を聞いたことがあります。「いまやってるプロジェクト、どうも失敗しそうなんだ。本番環境のセキュリティが厳しくて、どうしてもうまくいかないんだ。失敗すると5000万円の赤字になるみたいで……」。どうやら、すべてが出来上がってから本番の環境で動かしたのですが、動かなかったそうです。それから彼には会っていませんが、どうなったか気になります。ビルドマシンはなるだけ本番のマシン環境に合わせておいた方がよいでしょう。適当なマシンがなければ購入を考える価値もあります。すべてが出来上がって動かなかったとき、いままでの苦労が水の泡どころか、信用もお金もなくなります。本番に近い環境で継続的インテグレーションをしていれば、このようなアクシデントも未然に防ぐことができるでしょう。

ビルドマシンの設定

 ではビルドマシンに移動して、継続的インテグレーション環境を構築しましょう。

(1)CVSクライアントのインストール

  1. http://ccvs.cvshome.org/servlets/ProjectDownloadListよりWindows版の最新バイナリである「cvs-1.11.4.zip」をダウンロード

  2. ダウンロードしたファイルを適当な場所に解凍。ここの例ではCドライブの直下に解凍した

  3. 解凍ファイルの「cvs-1.11.4.exe」を「cvs.exe」にリネーム

  4. 解凍したディレクトリにパスを通す。例えば、「C:\cvs-1.11.4」に解凍した場合、環境変数Pathに「C:\cvs-1.11.4」を追加する

  5. コマンドプロンプトを立ち上げて「cvs」と入力してEnter。次のメッセージが表示されCVSコマンドが使用できることを確認する
>cvs[Enter]
Usage: cvs [cvs-options] command [command-options-and-arguments]
(中略)

(2)プロジェクトをチェックアウトする

 コマンドプロンプトからCVSコマンドを使用して、チェックアウトしてみます。

  1. 作業用のディレクトリを作成する。ここでは「C:\project」を作業用ディレクトリとする

  2. コマンドプロンプトより次のコマンドを実行して、プロジェクトをチェックアウトする
>mkdir c:\project
>cd c:\project
c:\project>set CVSROOT=:pserver:agata@mobster.jp/home/cvs/root


c:\project>cvs login
Logging in to :pserver:agata@mobster.jp:2401/home/cvs/root
CVS password:[パスワードを入力]


(CVSを初めて使用する場合、ここで「.passwdファイルが読み込めない」というエラーが表示されますが気にする必要はありません)

c:\project>cvs checkout Integration
cvs server: Updating Integration
U Integration/.classpath
U Integration/.project
U Integration/build.xml
 ……(中略)
U Integration/src/sample/StringUtilTest.java

 「c:\project\Integration」以下にプロジェクトのファイルがチェックアウトされていることを確認します。

(3)Antのセットアップ

 ビルドマシンにAntをセットアップします。すでにAntが導入済みの場合はこの項目は省くことができます。ただし、AntからJUnitタスクを利用するためにはJUnitがクラスパスに通っている必要がありますのでご注意ください。ANT_HOME/libにjunit.jarを置けば自動的にクラスパスに追加されます。

  1. http://ant.apache.org/」よりAntの最新版をダウンロードする。現在の最新版は「apache-ant-1.5.3-1-bin.zip」

  2. ダウンロードしたファイルを解凍する。今回はCドライブの直下に解凍した

  3. 環境変数「ANT_HOME」「C:\apache-ant-1.5.3-1」を設定

  4. 環境変数「Path」に「%ANT_HOME%/bin」を追加

  5. c:\project\Integration\lib\junit.jarをC:\apache-ant-1.5.3-1\libにコピー

  6. コマンドプロンプトより次のコマンドを実行して、Antにパスが通っていることを確認する
>ant -version
Apache Ant version 1.5.3 compiled on April 16 2003

手動でAntからCVSの更新を実行してみる

 コマンドプロンプトから次のコマンドを実行。

>cd c:\project\Integration
C:\project\Integration>ant cvs
Buildfile: build.xml

cvs:
      [cvs] Using cvs passfile: C:\Documents and Settings\agt\.cvspass
      [cvs] ? doc
      [cvs] ? bin/sample
      [cvs] cvs server: Updating .
      ……(中略)
      [cvs] cvs server: Updating src/sample

BUILD SUCCESSFUL
Total time: 2 seconds

JUnitReportを手動で実行してみる

 コマンドプロンプトから次のコマンドを実行。

>cd c:\project\Integration
c:\project\Integration>ant report
Buildfile: build.xml
……(中略)
init:
javac:
junit:
report:
[junitreport] Using Xalan version: Xalan Java 2.2.D11
[junitreport] Transform time: 1016ms

BUILD SUCCESSFUL
Total time: 4 seconds

Cronと組み合わせてCVSの更新とJUnitReportを定期的に実行する

 手動での実行ができるようになったところで、次は定期実行に挑戦します。定期的にある処理を実行するにはUNIXなら「Cron」、Windowsならば「タスク」があります。今回はMobCronというWindows用のCronを利用します。MobCronはJava+SWTで動作します。GUIから設定を行うことができるシンプルなCronプログラムです。

(1)MobCornのダウンロード

 http://www.mobster.jp/wiki/index.jsp?pid=MobCronよりMobCronの最新版をダウンロードします。

(2)MobCornの解凍

 ダウンロードしたZIPファイルを適当な場所に解凍します。

(3)MobCornの起動

 MobCron.exeからMobCronを起動します。

(4)設定の追加

  1. [一覧の上で右クリック] - [追加]

  2. build.batが1時間に1回実行されるように設定する
図7 一覧の上で右クリック(クリックすると拡大)

図8 build.batが1時間に1回実行されるように設定する

(5)設定ファイルの保存

 [ファイル] - [保存]

(6)手動で実行してみる

 (4)で追加した一覧の行を[右クリック] - [実行]で実行します。「ログ」のタブでAntの出力を確認できます。JUnitReportが実行されれば成功です。

(7)定期実行の開始

 ツールバーの「人間が走っているアイコン」で定期実行を開始します。MobCorn.exeのショートカットをスタートアップに入れておくと便利です。

WebサーバでJUnitReportの結果を確認

 最後にビルドマシンで稼働するTomcatを使用して、JUnitReportの結果をWebブラウザで閲覧できるようにしましょう。

  1. ビルドマシンにTomcatをセットアップ

  2. 「CATALINA_HOME/conf/server.xml」に次の記述を追加

    <Context path="/Integration" docBase="c:\project\Integration"/>

  3. Tomcatを起動

  4. Webブラウザで次のアドレスよりJUnitReportの実行結果を確認する
「http://[ビルドマシンのIPアドレス]:8080/Integration/doc/result/index.html」

 これでいつでも最新のテスト実行結果を確認することができますね。

最新ソースによる実行環境の構築

 Webのシステムの場合、ビルドマシン上に最新ソースによる実行環境を構築することも簡単に実現できます。この場合、ビルドマシンでコンパイルした結果をアプリケーションサーバに配備するAntタスクを実行します。

 継続的インテグレーションの実践は環境依存のバグの低減につながります。また、自動化されたテスト実行環境は1度構築してしまえば、あとはビルドマシンが納品日までに何百回とテストを実行してくれます。「早めにリスクを取り除く」ために、ぜひ継続的インテグレーションを導入することをお勧めします。

 本連載もいよいよ次回が最終回です。次回はコーディング標準と、いままで書ききれなかった点について補足したいと思います。お楽しみに!


プロフィール
縣俊貴(あがた としたか)
 メディアファイブ株式会社所属。XML,フレームワークを中心に開発業務に携わる。Javaのコミュニティー団体であるMobsterを主催。現在MonsterにてJavaベースのWikiシステム「MobWiki」を開発している。

橋本正徳(はしもと まさのり)
 メディアファイブ株式会社所属。XML、フレームワーク等の開発業務に携わる。Javaのコミュニティー団体である「Mobster」を縣と共に発起、運営。現在mobsterにてバグトラッキングシステム「mobbug」等を開発している。「日本XPユーザーグループ関西支部 九州分科会」にも参加。ちなみにこの記事自身もCVSでバージョン管理し、縣と橋本とで共同所有されて書かれている。

Project Mobster(ぷろじぇくと もぶすたー)
福岡県福岡市を中心にJava言語を研究追求し、その成果物をWeb上に公開していく団体です。年齢・スキル・会社などを超えてボーダーレスに活動しております。

IT Architect 連載記事一覧

この記事に対するご意見をお寄せください managemail@atmarkit.co.jp

「ITmedia マーケティング」新着記事

トランプ氏勝利で追い風 ところでTwitter買収時のマスク氏の計画はどこへ?――2025年のSNS大予測(X編)
2024年の米大統領選挙は共和党のドナルド・トランプ氏の勝利に終わった。トランプ氏を支...

AI導入の効果は効率化だけじゃない もう一つの大事な視点とは?
生成AIの導入で期待できる効果は効率化だけではありません。マーケティング革新を実現す...

ハロウィーンの口コミ数はエイプリルフールやバレンタインを超える マーケ視点で押さえておくべきことは?
ホットリンクは、SNSの投稿データから、ハロウィーンに関する口コミを調査した。