天天看點

使用Cobertura統計單元測試覆寫率

轉載,原諒位址為:http://terrencexu.iteye.com/blog/718834

感謝作者

使用Cobertura統計單元測試覆寫率

對于Java而言,進行覆寫率分析的方式有三類:第一種是将instrumentation(不知道怎麼翻譯好,測試儀表?),直接加入到源代碼中;第二種是将instrumentation加入到編譯好的Java位元組碼中;第三種是在一個可編輯的虛拟機中

運作代碼。Cobertura選擇了第二種方式。

為了便于使用,Cobertura提供了兩種方式将Cobertura內建到已有的運作環境中: Ant和指令行

總結起來Cobertura做的事情就是:

1. Cobertura将instrumentation加入到編譯好的需要被檢測的Java類的位元組碼中。

2. 運作測試用例的時候Cobertura通過之前安插好的instrumentation統計每一行代碼是否被執行,所有被植入instrumentation的類的序列化資訊将被寫入cobertura.ser。

3. 根據統計結果生成報表,格式為XML或者HTML。

整個過程不需要我們額外寫任何Java代碼,隻需要通過ant腳本或者指令行觸發相應的操作。

下面首先介紹一下使用ant腳本的方式。

第一步,下載下傳最新的Cobertura,解壓。下載下傳位址為http://cobertura.sourceforge.net/download.html 

第二步,将Cobertura目錄下面的Cobertura.jar和lib下所有jar拷貝到你的工程的某個目錄下。

第三步,建立ant腳本,或者在已有的ant腳本中添加相應的target。

現在開始設定ant腳本

第一步,将cobertura.jar以及Cobertura/lib下的所有jar引入classpath

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

注:lib.dir是你存放cobertura.jar以及/Conbertura/lib/*.jar的地方

第二步,将cobertura自身定義的task引入到ant腳本中

<taskdef classpathref="lib.classpath" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  resource="tasks.properties" />
           

第三步,編譯工程代碼到某個目錄,比如${src.java.classes.dir}

注:你可以選擇将所有的業務代碼和測試代碼編譯到一個classes目錄下,或者選擇編譯到不同的目錄下,在本例中将使用不同的目錄存放java.src和test.src。

<target name="compile" depends="init">
    <javac srcdir="${src.java.dir}" destdir="${src.java.classes.dir}" debug="yes">
        <classpath refid="lib.classpath" />
    </javac>
    <javac srcdir="${src.test.dir}" destdir="${src.test.classes.dir}" debug="yes">
        <!-- This is very import to include the src.java.classes.dir here -->
        <classpath location="${src.java.classes.dir}" />
        <classpath refid="lib.classpath" />
    </javac>
</target>
           

注:src.java.dir存放所有的将被測試的java類,src.java.classes.dir存放java類的編譯位元組碼;src.test.dir存放所有的測試用例, src.test.classes.dir存放測試用例的編譯位元組碼。init target用來建立一些備用的目錄,将包含在附件的完整工程代碼中。

第四步,定義target,向生成的java.class裡插入instrumentation,test.class将不插入instrumentation,因為我們不關心測試用例本身的覆寫率。

<target name="instrument">
    <!-- Remove the coverage data file and any old instrumentation classes -->
    <delete file="cobertura.ser" />
    <delete dir="${instrumented.classes.dir}" />

    <!-- Instrument the application classes, writing the instrumented classes into ${instrumented.classes.dir} -->
    <cobertura-instrument todir="${instrumented.classes.dir}">
        <!-- The following line causes instrument to ignore any source line containing a reference to log4j, for the purpose of coverage reporting -->
        <ignore regex="org.apache.log4j.*"/>
        <fileset dir="${src.java.classes.dir}">
            <include name="**/*.class"/>
        </fileset>
    </cobertura-instrument>
</target>
           

注:instrumented.classes.dir存在所有被植入instrumentation的Java class。如果java代碼和測試用例被編譯到了同一個目錄下,可以使用如<exclude name="**/*Test.class" />忽略測試用例。

第五步,執行測試用例,同時Cobertura将在背景統計代碼的執行情況。這一步就是普通的junit的target,将執行所有的測試用例,并生成測試用例報表。

<target name="test" depends="init,compile">
    <junit fork="yes" dir="${basedir}" failureProperty="test.failed">
        <!-- Note: the classpath order: instrumented classes are before the original (uninstrumented) classes. This is important!!! -->
        <classpath location="${instrumented.classes.dir}" />
        <classpath location="${src.java.classes.dir}" />
        <classpath location="${src.test.classes.dir}" />
        <classpath refid="lib.classpath" />
        
		<formatter type="xml"/>
        <test name="${testcase}" todir="${reports.junit.xml.dir}" if="testcase" />
        <batchtest todir="${reports.junit.xml.dir}" unless="testcase">
            <fileset dir="${src.test.dir}">
                <include name="**/*.java"/>
            </fileset>
        </batchtest>
    </junit>

    <junitreport todir="${reports.junit.xml.dir}">
        <fileset dir="${reports.junit.xml.dir}">
            <include name="TEST-*.xml"/>
        </fileset>
        <report format="frames" todir="${reports.junit.html.dir}"/>
    </junitreport>
</target>
           

注:這一步非常需要注意的是${instrumented.classes.dir}應該最先被引入classpath.

第六步,生成測試覆寫率報表。

<target name="alternate-coverage-report">
    <!-- Generate a series of HTML files containing the coverage data in a user-readable form using nested source filesets -->
    <cobertura-report destdir="${coverage.cobertura.html.dir}">
        <fileset dir="${src.java.dir}">
            <include name="**/*.java"/>
        </fileset>
    </cobertura-report>
</target>
           

 注:因為我将Java代碼和測試用例分别放在不同的包中,是以如果你的代碼都放在一個包中的話,應該使用<exclude name="**/*Test.java" />剔除測試用例; coverage.cobertura.html.dir是存放report的地方。生成XML報表的方式将在完成的build.xml檔案中給出。

報表如下圖:

使用Cobertura統計單元測試覆寫率

到此我們已經完成了生成測試覆寫率報表的全部工作,如果還想驗證一下測試覆寫率,可以通過以下方式

<target name="coverage-check">
    <cobertura-check branchrate="34" totallinerate="100" />
</target>
           

現在給出完成的build.xml檔案,僅供參考:

<?xml version="1.0" encoding="UTF-8"?>
<project name="study-cobertura" default="coverage" basedir=".">
    <description>The ant file for study-cobertuna</description>
		
    <property name="src.java.dir" value="${basedir}/java" />
    <property name="src.test.dir" value="${basedir}/test" />
    <property name="build.dir" value="${basedir}/build" />
    <property name="src.java.classes.dir" value="${build.dir}/src-java-classes" />
    <property name="src.test.classes.dir" value="${build.dir}/src-test-classes" />
    <property name="instrumented.classes.dir" value="${build.dir}/instrumented-classes" />
    <property name="reports.dir" value="${basedir}/reports" />
    <property name="reports.junit.xml.dir" value="${reports.dir}/junit-xml" />
    <property name="reports.junit.html.dir" value="${reports.dir}/junit-html" />
    <property name="coverage.cobertura.xml.dir" location="${reports.dir}/cobertura-xml"/>
    <property name="coverage.cobertura.summary.dir" location="${reports.dir}/cobertura-summary-xml"/>
    <property name="coverage.cobertura.html.dir" location="${reports.dir}/cobertura-html"/>
    <property name="lib.dir" location="${basedir}/lib"/>

    <path id="lib.classpath">
        <fileset dir="${lib.dir}">
            <include name="**/*.jar"/>
        </fileset>
    </path>
    
    <taskdef classpathref="lib.classpath" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  resource="tasks.properties" />
    
    <target name="init">
        <mkdir dir="${src.java.classes.dir}"/>
        <mkdir dir="${src.test.classes.dir}"/>
        <mkdir dir="${instrumented.classes.dir}"/>
        <mkdir dir="${reports.dir}"/>
        <mkdir dir="${reports.junit.html.dir}"/>
        <mkdir dir="${reports.junit.xml.dir}"/>
        <mkdir dir="${coverage.cobertura.xml.dir}"/>
        <mkdir dir="${coverage.cobertura.summary.dir}"/>
        <mkdir dir="${coverage.cobertura.html.dir}"/>
    </target>
    
    <target name="compile" depends="init">
        <javac srcdir="${src.java.dir}" destdir="${src.java.classes.dir}" debug="yes">
            <classpath refid="lib.classpath" />
        </javac>
        <javac srcdir="${src.test.dir}" destdir="${src.test.classes.dir}" debug="yes">
            <!-- This is very import to include the src.java.classes.dir here -->
            <classpath location="${src.java.classes.dir}" />
            <classpath refid="lib.classpath" />
        </javac>
    </target>
    
    <target name="instrument">
        <!-- Remove the coverage data file and any old instrumentation classes -->
        <delete file="cobertura.ser" />
        <delete dir="${instrumented.classes.dir}" />
        
        <!-- Instrument the application classes, writing the instrumented classes into ${instrumented.classes.dir} -->
        <cobertura-instrument todir="${instrumented.classes.dir}">
            <!-- The following line causes instrument to ignore any source line containing a reference to log4j, for the purpose of coverage reporting -->
            <ignore regex="org.apache.log4j.*"/>
            <fileset dir="${src.java.classes.dir}">
                <include name="**/*.class"/>
            </fileset>
        </cobertura-instrument>
    </target>
    
    <target name="test" depends="init,compile">
        <junit fork="yes" dir="${basedir}" failureProperty="test.failed">
            <!-- Note: the classpath order: instrumented classes are before the original (uninstrumented) classes. This is important!!! -->
            <classpath location="${instrumented.classes.dir}" />
            <classpath location="${src.java.classes.dir}" />
            <classpath location="${src.test.classes.dir}" />
            <classpath refid="lib.classpath" />
            
            <formatter type="xml"/>
            <test name="${testcase}" todir="${reports.junit.xml.dir}" if="testcase" />
            <batchtest todir="${reports.junit.xml.dir}" unless="testcase">
                <fileset dir="${src.test.dir}">
                    <include name="**/*.java"/>
                </fileset>
            </batchtest>
        </junit>
        
        <junitreport todir="${reports.junit.xml.dir}">
            <fileset dir="${reports.junit.xml.dir}">
                <include name="TEST-*.xml"/>
            </fileset>
            <report format="frames" todir="${reports.junit.html.dir}"/>
        </junitreport>
    </target>
    
    <target name="coverage-check">
        <cobertura-check branchrate="34" totallinerate="100" />
    </target>
    
    <!-- ================================= 
              target: coverage-report              
         ================================= -->
    <target name="coverage-report">
        <!-- Generate an XML file containing the coverage data using the 'srcdir' attribute -->
        <cobertura-report srcdir="${src.java.dir}" destdir="${coverage.cobertura.xml.dir}" format="xml" />
    </target>

    <!-- ================================= 
              target: summary-coverage-report              
         ================================= -->
    <target name="summary-coverage-report">
        <!-- Generate an summary XML file containing the coverage data using the 'srcidir' attribute -->
        <cobertura-report srcdir="${src.java.dir}" destdir="${coverage.cobertura.summary.dir}" format="summaryXml" />
    </target>
    
    <!-- ================================= 
              target: alternate-coverage-report              
         ================================= -->
    <target name="alternate-coverage-report">
        <!-- Generate a series of HTML files containing the coverage data in a user-readable form using nested source filesets -->
        <cobertura-report destdir="${coverage.cobertura.html.dir}">
            <fileset dir="${src.java.dir}">
                <include name="**/*.java"/>
            </fileset>
        </cobertura-report>
    </target>

    <!-- ================================= 
              target: clean              
         ================================= -->
    <target name="clean" description="Remove all files created by the build/test process">
        <delete dir="${src.java.classes.dir}" />
        <delete dir="${src.test.classes.dir}" />
        <delete dir="${instrumented.classes.dir}" />
        <delete dir="${reports.dir}" />
        <delete file="cobertura.log" />
        <delete file="cobertura.ser" />
    </target>

    <!-- ================================= 
              target: coverage              
         ================================= -->
    <target name="coverage" depends="clean, compile, instrument, test, coverage-report, summary-coverage-report, alternate-coverage-report" description="Compile, instrument ourself, run the tests and generate JUnit and coverage reports." />

</project>
           

工程目錄結構如下圖:

使用Cobertura統計單元測試覆寫率

如果還想使用指令行方式生成覆寫率報表,可以參照以下指令行生成報表

指令行方式如下:

//@rem Go to the cobertura folder
cd C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\tool\cobertura

//@rem list all java source to javalist.txt
dir C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\java\*.java/s/b > javalist.txt

//@rem list all test case source to testlist.txt
dir C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\test\*.java/s/b > testlist.txt

//@rem compile all java sources
javac -d C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-java-classes @javalist.txt

//@rem compile all test case sources
javac -classpath "$CLASSPATH;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\lib\junit-4.8.2.jar;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-java-classes" -d C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-test-classes @testlist.txt

//@rem instrument the java class file
cobertura-instrument.bat --destination C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\instrumented-classes --ignore org.apache.log4j.* --datafile ../../cobertura.ser C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-java-classes

//@rem go to the java src folder
cd C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-java-classes

//@rem run test case through JUnit4
java -cp C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\lib\junit-4.8.2.jar;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\lib\cobertura.jar;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\instrumented-classes;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-java-classes;C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\build\src-test-classes -Dnet.sourceforge.cobertura.datafile=C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\cobertura.ser org.junit.runner.JUnitCore  com.javaeye.terrencexu.cobertura.CalculatorTest

//@rem go to cobertura folder
cd C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\tool\cobertura

//@rem generate coverage report
cobertura-report.bat --format html --datafile C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\cobertura.ser --destination C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\reports\cobertura-html C:\eclipse\workspace1\Terrence-JavaStudy\study-cobertura\java
           

指令行模式用的比較少,不再贅述,感興趣,可以按上述順序執行一遍。

原文轉過來是友善到公司後能找到文章做指導,布置項目裡的覆寫率工具。