7. カバレッジツール

カバレッジツールは、単体テストで使用するもので、コードのテスト網羅率を計測する機能を提供します。このツールを使用してテスト仕様書のテストケースの品質を確認します。カバレッジはテスト作業における必要条件でしかありません。品質を確認するための十分条件にはなり得ないことに注意してください。次のようなカバレッジツール使い方はお奨めできません。このツールはJavaプログラムのみ対象としたものです。 WebOTXではcobertura1.9.4.1(WebOTX Developerに同梱)を利用します。以下の章ではこちらのcoberturaを利用します。

7.1. カバレッジとは

カバレッジ(網羅率)とは、ホワイトボックステストを行うとき用いる基準で、ソースコードに対して「どの程度テストを実施したか」とを表すための指標で、C0(命令)網羅、C1(分岐)網羅、C2(条件)網羅の3種類があります。C0網羅は、コード全てのステートメントを少なくとも1回実効するテストです。C1網羅はコード内の全てのブランチの真偽を少なくとも1回実行するテストです。C2網羅はコード内の全てのブランチの真偽の全ての組み合わせを実行するテストです。単体テストにおいては、C0網羅のテストを実施します。


図7.1-1


図7.1-2

Memo
本ツールが出力するブランチカバレッジは上記C1とは異なります。本ツールでは分岐点を少なくとも1回は実行した場合(上図の場合、A,Bを通過すること)となります。

7.2. 複雑度とは

複雑度とは、プログラムの複雑さを測る指標のひとつで、値が大きいほどテストや保守が難しいことを意味します。表7.2-1は、プログラムの複雑さを測る指標のひとつであるサイクロマティック複雑度(以下、複雑度)とバグの誤修正率を表したもので、複雑度が高い(ソースコードの構造が悪い)と、バグの誤修正率が高いことを表しています。
表7.2-1 複雑度のバグの誤修正率
複雑度
誤修正率
ソースコードの状態
<10
約5%
良い構造
20-30
約20%
構造に疑問がある
50以上
約40%
テストが不可能
100近く
約60%
いかなる変更も誤修正を生む可能性がある
図7.2-1は、100個のバグが1回目のテストで検出された場合、表7.2-1の誤修正率でバグが減少すると仮定した場合のバグ減少曲線です。良い構造であれば3回目のテストでバグはほぼ0件になりますが、構造が悪いとバグが中々減少しません。


図7.2-1

Memo
サイクロマティック複雑度はプログラムコードの構造の良し悪しを数値化する指標です。

Memo
複雑度の特性として、悪い構造のプログラムは、高い複雑度になります。良い構造のプログラムのほとんどは複雑度が低い数値になりますが、稀に高い数値のときがあります。すなわち、「複雑度の値が高い = プログラム構造の品質が悪い」となるわけではないことに注意してください。
複雑度の指標とコードレビューの関係
30以上   : 第3者によるソースコードレビュー の対象とします。
100以上 : ソースコードレビューの価値は有りません。作り直しを検討します。

7.3. 利用手順

カバレッジツールは、次の手順で実行します。

7.3.1. カバレッジ測定用フォルダの作成

cobertura では、カバレッジ測定を行うために、コンパイル済みのクラスファイル中にカバレッジ測定用のコードを直接挿入します。
カバレッジ測定用コードが挿入されたクラスファイルを元のクラスファイルと同じフォルダに出力してしまうと、元のクラスファイルに上書きしてしまいます。そのため、カバレッジ測定用コードを挿入したクラスファイルは図7.3.1-1のように別のディレクトリに出力することをお勧めします。


図7.3.1-1 カバレッジ測定フォルダの作成

以降の説明では、上記を踏まえて動的Webプロジェクト上で以下のようなファイル・フォルダ構成を前提として解説します。

7.3.2. coberturaを実行するためのAntタスクの記述

cobertura は、コマンドラインもしくは Ant タスクを利用して実行することができます。 本書では、Ant タスクを用いて実行する方法を解説します。
まず、{プロジェクトルート}フォルダを作成し、この直下に build.xml ファイルを作成します。
この build.xml ファイルに cobertura を実行するためのコードを記述します。
以下に、build.xml ファイルの記述例を示します。
<?xml version="1.0" encoding="UTF-8"?>
<project name="cobertura.test" default="main" basedir=".">
  <property environment="env" />

  <!-- 関連のパスを指定します -->
  <property name="webotx.dir" value="${env.WebOTX_HOME}" />
  <property name="cobertura.dir" value="${webotx.dir}/Developer/coveragetool/Cobertura" />
  <property name="cobertura.lib.dir" value="${cobertura.dir}/lib" />

  <!-- WebOTX ASの起動ディレクトリのパスを指定します。
         WebOTX ASのインストール環境に応じて適宜変更して下さい。 … ? -->
  <property name="apserver.dir" value="${webotx.dir}/domains/domain1/config" />

  <property name="junit.dir" value="${webotx.dir}/Developer/Studio/plugins/org.junit_4.10.0.v4_10_0_v20120426-0900" />

  <!-- プロジェクトのソースフォルダのパスを指定します。 -->
  <property name="src.dir" value="${basedir}/src" />
  <!-- デフォルトのクラスファイル出力フォルダのパスを指定します。 -->
  <property name="build.dir" value="${basedir}/build" />
  <property name="build.classes.dir" value="${build.dir}/classes" />

  <!-- カバレッジ測定関連のパスを指定します。 -->
  <property name="test.dir" value="${basedir}/test" />
  <property name="test.coverage.dir" value="${test.dir}/coverage" />
  <property name="test.instrumented.dir" value="${test.dir}/instrumented-classes" />
  <!-- 利用するライブラリのパスを指定します。 -->
  <path id="cobertura">
    <fileset dir="${cobertura.dir}">
      <include name="*.jar" />
    </fileset>
    <fileset dir="${cobertura.lib.dir}">
      <include name="*.jar" />
    </fileset>
  </path>
  <path id="junit">
    <fileset dir="${junit.dir}">
      <include name="*.jar" />
    </fileset>
  </path>

  <!-- cobertura が提供するオプションタスクを定義します。 … ? -->
  <taskdef classpath="${cobertura.dir}/cobertura.jar" resource="tasks.properties" />

  <!-- カバレッジ測定関連フォルダの初期化およびカバレッジ測定用コードの挿入を実行します。 -->
  <target name="main" depends="init,instrument" description="Execute init, instrument task." />

  <!-- カバレッジ関連のフォルダを新規作成します。 -->
  <target name="init" description="Initialize coverage rerated directories.">
    <mkdir dir="${test.coverage.dir}" />
    <mkdir dir="${test.instrumented.dir}" />
  </target>

  <!-- オリジナルのクラスファイルにカバレッジ測定用コードを挿入し、
         test/instrumented-classes フォルダに出力します。 … ? -->
  <target name="instrument" description="Insert cobertura instrumentation into original classes.">
      <delete file="cobertura.ser"/>
      <delete dir="${test.instrumented.dir}"/>
    <cobertura-instrument todir="${test.instrumented.dir}" datafile="cobertura.ser">
      <classpath refid="cobertura"/>
      <fileset dir="${build.classes.dir}">
        <include name="**/*.class" />
        <exclude name="**/*Test.class" />
      </fileset>
    </cobertura-instrument>
    <copy todir="${apserver.dir}" file="cobertura.ser"/>
  </target>

  <!-- テストケースを実行します。 -->
  <target name="junit">
    <junit printsummary="on" haltonfailure="off">

      <sysproperty key="net.sourceforge.cobertura.datafile" file="${apserver.dir}/cobertura.ser" />

      <classpath refid="cobertura" />
      <classpath location="${test.instrumented.dir}" />
      <classpath location="${build.classes.dir}" />
      <classpath refid="junit" />

      <formatter type="xml" />
      <batchtest fork="yes" todir="${test.dir}" unless="testcase">
        <fileset dir="${src.dir}">
          <include name="**/*Test.java" />
        </fileset>
      </batchtest>
    </junit>
  </target>

  <!-- カバレッジ測定結果のレポートを、test/coverage フォルダに出力します。 … ? -->
  <target name="report" description="Generate HTML or XML coverage reports in test/coverage.">
      <delete dir="${test.coverage.dir}"/>
        <cobertura-report destdir="${test.coverage.dir}"
        datafile="${apserver.dir}/cobertura.ser" format="html">
      <classpath path="${cobertura.lib.dir}/log4j-1.2.9.jar" />
      <fileset dir="${src.dir}">
        <include name="**/*.java"/>
      </fileset>
    </cobertura-report>
  </target>
</project>
build.xml ファイル記述例の?〜?について、以下で説明します。

? ここには、テスト実行後にカバレージ測定データファイルを出力するフォルダを指定します。
このファイルを出力するフォルダはAPサーバごとに異なり、WebOTX では <WebOTX のインストールフォルダ>\domains\<ドメイン名>\config 配下に cobertura.ser ファイルが出力されます。

? ここでは、cobertura.jar ファイルに格納されている tasks.properties ファイルを読み込み、 cobertura が提供するオプションタスクを定義しています。
cobertura が提供する Ant タスクの一覧、詳細については、以下を参照してください。 http://cobertura.sourceforge.net/anttaskreference.html

? cobertura が提供する cobertura-instrument タスクを起動します。
このタスクを実行することにより、カバレッジ測定に必要となるコードを挿入したクラスファイルを生成します。 カバレッジ測定を行う際には、このタスクを実行して生成したクラスファイルを実行する必要があります。

? cobertura が提供する cobertura-report タスクを起動します。
デフォルトでは、HTML 形式でレポートを出力しますが、/project/target/cobertura-reportのformat 属性に xml を指定することでXML 形式でレポートを出力することも可能です。

なお、この build.xml では、コンパイル済みクラスへのカバレッジ測定用コードを挿入、実行の処理を記載しています。 挿入先となるクラス自体は、プロジェクトのビルドで生成されている事を前提としているため、 ビルドに対応する "compile" は target として記述していません。

また、ソースフォルダが複数存在する場合の記載例を、 [7.3.4. 複数のソースフォルダに対応したAntタスクの記述] で説明しています。

7.3.3. カバレッジ測定の手順

カバレッジ測定の手順は以下のような流れです。
  1. プロジェクトのビルド準備
  2. instrumentタスク実行
  3. エクスポート
  4. デプロイ
  5. テスト実行
  6. WebOTX ASのシャットダウン

7.3.3.1. プロジェクトのビルド準備

カバレッジ測定対象のプロジェクトに対するビルド準備をします。 プロジェクトをビルドするには以下の三つの条件を満たしている必要があります。
  1. プロジェクトのプロパティー|Java のビルド・パス|ソースタブ|デフォルト出力フォルダーに、 <プロジェクトフォルダ>/test/instrumented-classes フォルダを指定していること。

    デフォルト出力フォルダーは、デフォルトでは<プロジェクトフォルダ>build/classesに設定されています。<プロジェクトフォルダ>/test/instrumented-classesに変更したとき、ビルド・パスの設定ダイアログを表示します。以前のロケーションとコンテンツを除去するか尋ねられるので、「いいえ」を選択します。


    図7.3.3.1-1 デフォルト出力フォルダーの確認


    図7.3.3.1-2 ビルド・パスの設定ダイアログの表示例

  2. Deployment Assembly に<WebOTXインストールフォルダ>/Developer/coveragetool/Cobertura/cobertura.jarを追加していること。


    図7.3.3.1-3 cobertura.jar の追加

  3. Ant|ランタイム|クラスパス|グローバル項目に、 を追加していること。
    追加していない場合は、ウィンドウ(W)|設定を選択し、Ant|ランタイム|クラスパスタブを開き、 グローバル項目に対して外部JARの追加で、前述の JAR ファイルを指定します。


    図7.3.3.1-4 クラスパスのグローバル項目にlog4j-1.2.9.jarを追加

7.3.3.2. instrumentタスク実行

まず、既にWebOTX ASが起動していた場合には停止します。
<プロジェクトフォルダ>/test/build.xmlの右クリックメニューから実行|3 Antビルドの実行を選択し、構成の編集ダイアログを表示します。構成のターゲットとしてmainを選択し、ビルドを実行します。 正常にビルドが完了したら、WebOTX ASを起動します。
※クラスファイルの生成にはデフォルトの出力ディレクトリ(標準ではbuild/classes)に出力されたファイルを利用します。ソースコードを変更した場合には、デフォルトの出力ディレクトリのクラスファイルの更新が必要です。

7.3.3.3. エクスポート

プロジェクト(P)|自動的にビルド(M)のチェックを外します。


図7.3.3.3-1 自動的にビルドのチェックを外す

プロジェクトのエクスポートを実行します。
Springで実装をしている場合は動的WebプロジェクトをエクスポートしてWARファイルを出力します。 EJBで実装をしている場合はエンタープライズプロジェクトをエクスポートしてEARファイルを出力します。

サーバアプリケーションのカバレッジをWebOTX ASで行う場合は以下のパーミッションの設定が必要です。以下のファイルにパーミッションの設定を追加してください。
<WebOTX のインストールフォルダ>\domains\<ドメイン名>\config\server.policy

以下は動的プロジェクト(プロジェクト名はTestDynamicWebProject )の例です。太字の部分は配備するアプリケーションに合わせて適宜変更してください。
// for Cobertura
grant codeBase "file:${com.nec.webotx.instanceRoot}/applications/TestDynamicWebProject/-" {
   permission java.io.FilePermission "${com.nec.webotx.instanceRoot}${/}config${/}-", "read,write,delete";
   permission java.lang.RuntimePermission "shutdownHooks";
};
設定の変更を反映するために、ドメインを再起動してください。

7.3.3.4. デプロイ

7.3.3.3.で作成した war ファイル、またはEARファイルをWebOTX ASにデプロイします。


図7.3.3.4-1 デプロイ

Memo
ドメインの起動が完了していない場合、デプロイに失敗する場合があります。 ドメイン再起動を行った後には、しばらく時間をおいてから実行してください。
なお、起動に要する時間の目安は、運用管理コマンド start-domain を実行することで測定可能です。 詳細は、 [ 運用管理コマンドリファレンス > start-domain ] をご覧下さい。

7.3.3.5. テスト実行

デプロイしたWebアプリケーションに対してテストを実行します。
テストはプロジェクトとして個別に実行してください。

7.3.3.6. WebOTX ASのシャットダウン

WebOTX ASをシャットダウンします。
WebOTX ASをシャットダウンしたタイミングで cobertura.ser という名前のカバレージ測定データファイルを出力します。


図7.3.3.6-1 サーバのシャットダウン

7.3.3.7. カバレッジ測定の結果レポート出力

カバレッジ測定の結果レポートを出力します。
<プロジェクトフォルダ>/test/build.xmlの右クリックメニューから実行|3 Antビルドの実行を選択し、構成の編集ダイアログを表示します。構成のターゲットとしてreportを選択し、ビルドを実行します。 実行に成功すれば、<プロジェクディレクトリ>/test/coverage フォルダ配下に、HTML 形式または XML 形式のレポートを出力します。

<プロジェクトフォルダ>/test/coverageフォルダ下のHTML形式またはXML形式のレポートを選択し、右クリックメニューより アプリケーションから開く|システムエディターを選択します。システムデフォルトのブラウザでカバレッジレポートを表示します。
カバレッジレポートの見方の詳細は、coberturaのドキュメントを参照して下さい。


図7.3.3.7-1 カバレッジの表示(HTML形式の例)

7.3.4. 複数のソースフォルダに対応したAntタスクの記述

複数のソースフォルダに対応した Antタスクの記述方法についてい説明します。

[7.3.1. カバレッジ測定用フォルダの作成] のフォルダ構成に、ソースフォルダとして "src2" を追加が追加されたとします。 また、カバレッジ測定用コードの挿入前(classes2)、挿入後(instrumented-classes2)のクラス出力先も分離するものとします。


図7.3.4.-1

この場合、Antタスクの記述例は、以下のようになります。
太い文字の記述が複数フォルダに対応して追加された記載です。 それぞれ、直前に記載された "一つ目のフォルダ" に対する記載と類似した記載を追加しています。
<?xml version="1.0" encoding="UTF-8"?>
<project name="cobertura.test" default="main" basedir=".">
  <property environment="env" />

  <!-- 関連のパスを指定します -->
  <property name="webotx.dir" value="${env.WebOTX_HOME}" />
  <property name="cobertura.dir" value="${webotx.dir}/Developer/coveragetool/Cobertura" />
  <property name="cobertura.lib.dir" value="${cobertura.dir}/lib" />

  <!-- WebOTX ASの起動ディレクトリのパスを指定します。
         WebOTX ASのインストール環境に応じて適宜変更して下さい。 … ? -->
	<property name="apserver.dir" value="${webotx.dir}/domains/domain1/config" />
	
  <property name="junit.dir" value="${webotx.dir}/Developer/Studio/plugins/org.junit_4.10.0.v4_10_0_v20120426-0900" />

  <!-- プロジェクトのソースフォルダのパスを指定します。 -->
	  <property name="src.dir" value="${basedir}/src" />
	  <property name="src2.dir" value="${basedir}/src2" />
  <!-- デフォルトのクラスファイル出力フォルダのパスを指定します。 -->
  <property name="build.dir" value="${basedir}/build" />
  <property name="build.classes.dir" value="${build.dir}/classes" />
  <property name="build.classes2.dir" value="${build.dir}/classes2" />
	
  <!-- カバレッジ測定関連のパスを指定します。 -->
  <property name="test.dir" value="${basedir}/test" />
  <property name="test.coverage.dir" value="${test.dir}/coverage" />
  <property name="test.instrumented.dir" value="${test.dir}/instrumented-classes" />
  <property name="test.instrumented2.dir" value="${test.dir}/instrumented-classes2" />
	
  <!-- 利用するライブラリのパスを指定します。 -->
  <path id="cobertura">
    <fileset dir="${cobertura.dir}">
      <include name="*.jar" />
    </fileset>
    <fileset dir="${cobertura.lib.dir}">
      <include name="*.jar" />
    </fileset>
  </path>
  <path id="junit">
    <fileset dir="${junit.dir}">
      <include name="*.jar" />
    </fileset>
  </path>

  <!-- cobertura が提供するオプションタスクを定義します。 … ? -->
  <taskdef classpath="${cobertura.dir}/cobertura.jar" resource="tasks.properties" />

  <!-- カバレッジ測定関連フォルダの初期化およびカバレッジ測定用コードの挿入を実行します。 -->
  <target name="main" depends="init,instrument" description="Execute init, instrument task." />

  <!-- カバレッジ関連のフォルダを新規作成します。 -->
  <target name="init" description="Initialize coverage rerated directories.">
    <mkdir dir="${test.coverage.dir}" />
    <mkdir dir="${test.instrumented.dir}" />
    <mkdir dir="${test.instrumented2.dir}" />
  </target>

  <!-- オリジナルのクラスファイルにカバレッジ測定用コードを挿入し、
         test/instrumented-classes フォルダに出力します。 … ? -->
  <target name="instrument" description="Insert cobertura instrumentation into original classes.">
      <delete file="cobertura.ser"/>
    <delete dir="${test.instrumented.dir}"/>
    <delete dir="${test.instrumented2.dir}"/>
 	
    <cobertura-instrument todir="${test.instrumented.dir}" datafile="cobertura.ser">
      <classpath refid="cobertura"/>
      <fileset dir="${build.classes.dir}">
        <include name="**/*.class" />
        <exclude name="**/*Test.class" />
      </fileset>
    </cobertura-instrument>
  	
    <cobertura-instrument todir="${test.instrumented2.dir}" datafile="cobertura.ser">
      <classpath refid="cobertura"/>
      <fileset dir="${build.classes2.dir}">
        <include name="**/*.class" />
        <exclude name="**/*Test.class" />
      </fileset>
    </cobertura-instrument>
    <copy todir="${apserver.dir}" file="cobertura.ser"/>
  </target>

  <!-- テストケースを実行します。 -->
  <target name="junit">
    <junit printsummary="on" haltonfailure="off">
      <sysproperty key="net.sourceforge.cobertura.datafile" file="${apserver.dir}/cobertura.ser" />
      <classpath refid="cobertura" />
      <classpath location="${test.instrumented.dir}" />
      <classpath location="${build.classes.dir}" />
      <classpath location="${test.instrumented2.dir}" />
      <classpath location="${build.classes2.dir}" />
      <classpath refid="junit" />
      <formatter type="xml" />
      <batchtest fork="yes" todir="${test.dir}" unless="testcase">
        <fileset dir="${src.dir}">
          <include name="**/*Test.java" />
        </fileset>
        <fileset dir="${src2.dir}">
          <include name="**/*Test.java" />
        </fileset>
      </batchtest>    	
    </junit>
  </target>

  <!-- カバレッジ測定結果のレポートを、test/coverage フォルダに出力します。 … ? -->
  <target name="report" description="Generate HTML or XML coverage reports in test/coverage.">
      <delete dir="${test.coverage.dir}"/>
        <cobertura-report destdir="${test.coverage.dir}"
        datafile="${apserver.dir}/cobertura.ser" format="html">
      <classpath path="${cobertura.lib.dir}/log4j-1.2.9.jar" />
      <fileset dir="${src.dir}">
        <include name="**/*.java"/>
      </fileset>
      <fileset dir="${src2.dir}">
        <include name="**/*.java"/>
      </fileset>
    </cobertura-report>
  </target>
</project>