CoberturaをAntから起動する

カバレッジ見える化、大事だと思う。なんたって、うっかり八兵衛が必ずあるから。なので、指摘してもらえると本当に助かるし、安心できる。これは、みんなそうなのだろうと思う。
で、ここからは趣味の世界。個人的には、結果が見える化してあると、テストしている感が高まる。何度もカバレッジの測定ツールを動かし、その結果を見るのが好き。けっこう楽しめる。変態かもw
そもそも、テストコードを書くのがけっこう好き。まぁ、そんなことはいいや。


で、掲題の通りの作業を振ってもらった。のだが、今の職場の環境では、あろうことか、GitHubにアクセスできない。もちろん、本家の情報にアクセスできない。次のページを見たいだけなのに。。なので、お家で調べることになった。
https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference


まず、Cobertura Antタスクを定義する。taskdef要素を使って、次のように記述すればよいみたいだ。

    <path id="cobertura.classpath">
        <fileset dir="${cobertura.dir}">
            <include name="lib/ant/cobertura/cobertura-2.1.1.jar">
            <include name="lib/ant/cobertura/lib/*.jar">
        </fileset>
    </path>

    <taskdef classpathref="cobertura.classpath" resource="tasks.properties" />

おぉ、スマートだ。この「tasks.properties」ってファイルが、jarに同梱されていて、タスク名と対応するクラス名が記述されている。taskdef要素の設定って、自分は次のような記述方法に慣れてしまっていて、思考停止していた。。あ、もちろん次の記述でも動作しますよ。

    <taskdef
        name="cobertura-instrument"
        classname="net.sourceforge.cobertura.ant.InstrumentTask">

        <classpath refid="cobertura.classpath" />
    </taskdef>

    <taskdef
        name="cobertura-report"
        classname="net.sourceforge.cobertura.ant.ReportTask">

        <classpath refid="cobertura.classpath" />
    </taskdef>

(4月2日追記ここから)
クラスファイルではなく、jarファイルを指定しても動作するっぽい。

	<!-- テスト対象のクラスファイルにカバレッジの情報を埋め込む -->
    <target name="instrument">
        <delete file="${work.dir}/cobertura.ser" />

        <cobertura-instrument
            datafile="${work.dir}/cobertura.ser">

            <fileset dir="${lib.dir}">
                <include name="jp.example.target.jar" />
            </fileset>
        </cobertura-instrument>

   </target>

(4月2日追記ここまで)


次に、テスト対象のクラスに、Coberturaの「仕掛け」を埋め込む処理を記述する。本家の「Ant Task Reference」に、2通りのサンプルがあるので、秘技、見様見真似を使えば、さくっと書けると思う。注意する点といえば、、うーん、、間違ってテストクラスを指定しないこと。かなw
次の例では、テスト対象のクラスファイルが格納されているディレクトリとして、「../../project/build/work/classes」を指定している。Coberturaは、このディレクトリ内のクラスファイルを加工し(コードを埋め込み)、「${work.dir}/coverage_classes」に出力する。本番環境に、細工されたクラスファイルをもっていかないよう、todir属性を指定してあげるとよいのでは。
この部分はけっこう柔軟に記述することができる。例えば、対象とするクラスや対象としないクラスを、正規表現を用いて指定することができる。

    <!-- テスト対象のクラスファイルにカバレッジの情報を埋め込む -->
    <target name="instrument">
        <delete file="${work.dir}/cobertura.ser" />

        <cobertura-instrument
            datafile="${work.dir}/cobertura.ser"
            todir="${work.dir}/coverage_classes">

            <includeclasses regex=".*" />

            <instrumentationClasspath>
                <pathelement location="../../project/build/work/classes" />
            </instrumentationClasspath>

        </cobertura-instrument>
    </target>


続いて、テストを実行する部分。Coberturaの動きから、junit要素のfork属性の値をture(またはyes)にすることが重要とのこと。onとoffではなかったっけ?まぁ、いいや。Coberturaは、JVMが終了するときにだけ、カバレッジのデータファイルの変更をディスクに吐き出す。なので、JUnitがAntと同じJVM上で動作していたら、Antが終了する「前に」、レポートの出力をすることができないようだ。
加えて、forkmode属性の値をonceにすることも重要とのこと。JUnitの全てのテストは、たったひとつのJVMが起動させる。JVMが起動・停止するたびに、Coberturaが読み込み・書き込みをしてしまうことを防がないと、パフォーマンスが落ちるようだ。
クラスパスは、その他クラスより前に、Coberturaにより加工された(コードが埋め込まれた)クラスに通しておく必要があるっぽい。
メイン部分だけあって、ちょっと注意がいるみたい。

    <!-- JUnitによるテストを実行する(テストスイートを起動する) -->
    <target name="test">
        <delete
            file="${dest.dir}/TEST-jp.example.AllTests.xml" />

        <junit fork="yes" forkmode="once" haltonfailure="off">
            <sysproperty
                key="net.sourceforge.cobertura.datafile"
                file="${dest.dir}/cobertura.ser" />

            <formatter type="xml" />

            <classpath location="${work.dir}/coverage_classes">
            <classpath location="lib/ant/cobertura/cobertura-2.1.1.jar" />
            <classpath refid="build.classpath" />

            <test
                todir="${dest.dir}"
                name="jp.example.AllTests" />
        </junit>
    </target>


最後に、結果を出力する部分。ま、正直、あまり言及することはない。テスト対象クラスのソースコードを、きちんと指定してあげるくらいかな。ここも、テストクラスのソースコードを指定しないこと。

    <!-- カバレッジの結果を出力する -->
    <target name="report">
        <delete dir="${dest.dir}/report" />
        <mkdir dir="${dest.dir}/report" />

        <cobertura-report
            format="html"
            datafile="${work.dir}/cobertura.ser"
            destdir="${dest.dir}/report"
            encoding="UTF-8">

            <fileset dir="../../project/src">
                <include name="**/*.java" />
            </fileset>
        </cobertura-report>
    </target>

(4月2日追記ここから)
ソースコードは、jarファイルを指定しても、認識してくれないっぽい(jp.example.target-source.jarとかではダメ)。なので、次のようにして展開してあげた。

        <unjar
            src="${lib.dir}/jp.example.target.jar-source.jar"
            dest="${lib.dir}/src" />

(4月2日追記ここまで)



さ、もう一息。今日中には終わらせるぞ。

2015/04/02追記

そもそも、テスト対象を作成するプログラムのビルドがうまくいっていなかったらしく(テスト対象のjarファイルをよくよく見ると、META-INFディレクトリとマニフェストファイルのみで作成されていた)、そのことに全く気が付かなかった。そこぉ?
これは難しかった。もっときっちり追いつめるべきだった。。
で、2日遅延して、やっと終わった。。