Skip to content
junit coverage.
Java
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.settings
jacocolib
src/java/com/ubuntuvim/coverage
test/java/com/ubuntuvim/coverage 增加项目说明 Dec 5, 2018
.gitignore
.project
README.md
build-test.xml
build.xml

README.md

Github无法显示博客图片,请移步:

准备工作

所需软件、jar

  • Junit,单元测试依赖Junit
  • jacoco,用于检测覆盖率,官方网站https://www.jacoco.org/jacoco/,下载jar包
  • eclipse,在elipse上安装覆盖率运行的插件EclEmma,在eclipse的 应用商店直接搜索EclEmma,第一个就是。安装成功之后在java类右键有一个选项是Coverage As

创建java项目

项目结构

src/javatest/java都是资源目录。

增加依赖jar

在项目根目录下新建目录jacocolib。复制从Junit、jacoco官网下载的jar文件。文件如下:

  • hamcrest-core-1.3.jar
  • jacocoant.jar
  • junit-4.12.jar

这些jar可以直接从我的项目中下载,地址https://github.com/ubuntuvim/coverage/tree/master/jacocolib

编写简单的java代码

如下是我的业务代码,一些简单的判断。

package com.ubuntuvim.coverage;

/**
 * 覆盖率
 * 
 * @author ubuntuvim
 */
public class Coverage {
	
	public String normal(int type) {
		if (type == 1) {
			return "one";
		} else {
			return "other";
		}
	}

	public int exceptMethod(int p) {
		p = (p + 10) / p;
		return p;
	}

	public int throwExcept(int p) throws MyException {
		try {
			p = (p + 10) / p;
		} catch (Exception e) {
			throw new MyException("0不能做分母。");
		}
		return p;
	}
}

自定义的异常类。

package com.ubuntuvim.coverage;

public class MyException extends Exception {

	private static final long serialVersionUID = 1L;
	
	public MyException(String message) {
        super(message);
    }
	
}

代码没什么好说的了,非常简单。关键看单元测试方法。

编写单元测试

下面的单元测试是针对Coverage.java类中三个方法normalexceptionMethodthrowException的单元测试。

package com.ubuntuvim.coverage;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class CoverageTest {

	@Rule
	public ExpectedException thrown = ExpectedException.none();

	private Coverage c = null;

	@Before
	public void setUp() {
		c = new Coverage();
	}
	
	@Test  // 测试type为1的情况
	public void testNormalOne() {
		String s = c.normal(1);
		assertEquals("one", s);
	}

	@Test  // 测试type为2的情况
	public void testNormalOther() {
		String s = c.normal(2);
		assertEquals("other", s);
	}

	@Test  // 测试正常的情况 (try语句块里的代码)
	public void testExceptMethodP() {
		int i = c.exceptMethod(10);
		assertEquals(2, i);
	}
	
	// 测试出现异常的情况(catch语句块里的代码)
	@Test(expected = ArithmeticException.class)
	public void testExceptMethod() {
		c.exceptMethod(0);
	}

	@Test  // 测试正常的情况
	public void testThrowExceptP() throws MyException {
		int i = c.throwExcept(10);
		assertEquals(2, i);
	}
	
	@Test  // 测试出现异常的情况
	public void testThrowExcept() throws MyException {
		// 出现异常的情况会返回MyException异常
		thrown.expect(MyException.class);
		thrown.expectMessage("0不能做分母。");
		c.throwExcept(0);
	}

}

有关Junit的使用请自行学习。 为了达到100%覆盖,需要对normalexceptionMethodthrowException这三个方法中每一种情况都编写单元测试方法。具体请看上述代码。

单元测试代码编写好之后可以先运行。在CoverageTest.java上右键 -> 选择Coverage As -> 选择Junit Test。 执行完毕之后会有如下截图结果:

运行结果

打开Coverage视图,可以看到我们的代码达到了100%的覆盖率。也就是说我们的代码中所有分子都已经被测试通过。

运行结果2

自动执行单元测试

上述右键使用Coverage As运行一个测试类,如果有多个测试的情况要如何处理呢?可以借助于ant。使用ant自动编译java代码。

在项目根目录下创建build.xml,文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project name="AntTestReporting" basedir="." default="compile">
 
    <!-- general -->
    <property name="DEBUG" value="true" />
    <property name="VERBOSE" value="true" />
    <property name="TARGET" value="1.8" />
 
    <!-- folder -->
    <property name="build.dir" value="build" />
    <property name="src.dir" value="src/java" />
    <property name="src.classess.dir" value="${build.dir}/classess" />
    <property name="test.dir" value="test/java" />
    <property name="test.classess.dir" value="${build.dir}/test-classess" />
    <property name="third-party-lib" value="jacocolib" />
 
    <!-- classpath -->
    <path id="classpath">
        <fileset dir="${third-party-lib}">
            <include name="**/*.jar" />
        </fileset>
    </path>
 
    <!-- targets -->
    <target name="clean">
        <delete dir="${build.dir}" />
    </target>
    <target name="init">
        <mkdir dir="${build.dir}" />
        <mkdir dir="${src.classess.dir}" />
        <mkdir dir="${test.classess.dir}" />
    </target>
 
    <target name="compile" depends="clean, init">
        <!-- Create the time stamp -->
        <tstamp>
            <format property="lastUpdated" pattern="yyyy-MM-dd HH:mm:ss" />
        </tstamp>
 
 		<!-- 编译java代码 -->
        <javac target="${TARGET}" debug="${DEBUG}" verbose="${VERBOSE}" classpathref="classpath"
        	optimize="true" destdir="${src.classess.dir}" srcdir="${src.dir}">
        </javac>
        <echo>======== java代码编译完毕 ========</echo>
        
        <path id="classpath.src.dir">
	        <pathelement location="${src.classess.dir}" />
	        <!-- 引入第三方jar -->
	        <fileset dir="${third-party-lib}">
	            <include name="**/*.jar" />
	        </fileset>
	    </path>
 		<!-- 编译test代码 -->
        <javac target="${TARGET}" debug="${DEBUG}" verbose="${VERBOSE}"
        	optimize="true" destdir="${test.classess.dir}" srcdir="${test.dir}">
        	<!-- test代码依赖src的代码和第三方jar -->
        	<classpath refid="classpath.src.dir" />
        </javac>
 		<echo>======== test代码编译完毕 ========</echo>
    </target>
 
</project>

这个脚本主要做了两件事:

  • 编译src/java目录下面的java代码到目录build/classess下。
  • 编译test/java目录下面的java代码到目录build/test-classess下。

在这个文件上右键 -> Run As -> Ant Build。执行完毕后,刷新项目。可以看到在项目根目录下自动创建了一个名为build的目录。展开之后可以看到项目的所有class文件。

编译结果1

自动生成覆盖率报告

编译之后得到你的业务代码class文件和单元测试的class文件。那么如何批量运行这些class呢? 同样的也是使用Ant。

在项目根目录下创建build-test.xml,文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project name="AntTestReporting-test" basedir="." default="all-test" xmlns:jacoco="antlib:org.jacoco.ant">
 
    <import file="build.xml" />
 
    <!-- Java Code Coverage -->
    <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
        <classpath path="jacocolib/jacocoant.jar" />
    </taskdef>
 
    <property name="reports.dir" value="${basedir}/reports" />
    <property name="test.data.dir" value="${reports.dir}/testResults" />
    <property name="coverage.reports.dir" value="${reports.dir}/coverage" />
 
    <property name="xms" value="-Xms256m" />
    <property name="xmx" value="-Xmx1024m" />
    <!-- <property name="log4j.config" value="-Dlog4j.configuration=file:/${base.dir}/test/log4j-test.properties" /> -->
	
	<!-- test代码类路径,包括src代码、测试代码本身、jacoco jar -->    
 	<path id="classpath.test.dir">
 		<!-- 测试代码依赖src代码,需要作为test代码的运行classpath -->
        <path refid="classpath.src.dir"/>
        <pathelement location="${test.classess.dir}" />
    </path>
    
    <target name="init-report-dir">
    	<delete dir="${test.data.dir}" />
    	<delete dir="${coverage.reports.dir}" />
    	<delete dir="${reports.dir}" />
    	
    	<mkdir dir="${reports.dir}" />
    	<mkdir dir="${test.data.dir}" />
    	<mkdir dir="${coverage.reports.dir}" />
    </target>
	    
    <target name="test" depends="init-report-dir, compile">
        <echo>========= 运行所有test类 =========</echo>
        <jacoco:coverage destfile="${test.data.dir}/jacoco.exec">
            <junit printsummary="true" haltonfailure="false" fork="yes" forkmode="once">
                <jvmarg value="${xms}" />
                <jvmarg value="${xmx}" />
                <classpath refid="classpath.test.dir" />
                <formatter type="xml" />
                <!-- 执行 *Test类 -->
                <batchtest todir="${test.data.dir}">
                    <fileset dir="${test.classess.dir}">
                        <!-- Exclude inner classes -->
                        <exclude name="**/*$*.class" />
                        <include name="**/*Test.class" />
                    </fileset>
                </batchtest>
            </junit>
        </jacoco:coverage>
 
        <!-- Generate HTML report
            - junit-noframes.html -> Single page HTML-report
            - index.html -> HTML-report using frames (several files, but more comfortable to read)-->
        <echo>========= 生成Junit报告 =========</echo>
        <junitreport todir="${test.data.dir}">
            <fileset dir="${test.data.dir}">
                <include name="TEST-*.xml" />
            </fileset>
        </junitreport>
 
 
        <!-- Generate Code Coverage report
            See: http://www.eclemma.org/jacoco/trunk/doc/ant.html -->
        <echo>========= 生成覆盖率报告 =========</echo>    
        <jacoco:report>
            <executiondata>
                <file file="${test.data.dir}/jacoco.exec" />
            </executiondata>
 
            <structure name="AntTestReporting">
                <classfiles>
                    <fileset dir="${src.classess.dir}">
                        <include name="**/*.class" />
                        <!-- 生成的报告中排除test类 -->
                        <!-- <exclude name="**/*Test*.class" /> -->
                        <!-- Exclude inner classes -->
                        <exclude name="**/*$*.class" />
                    </fileset>
                </classfiles>
            </structure>
 
            <html destdir="${coverage.reports.dir}" />
        </jacoco:report>
    </target>
 
    <target name="all-test" depends="test" />
</project>

这个脚本主要做如下三件事:

1. 运行所有test类

<jacoco:coverage destfile="${test.data.dir}/jacoco.exec">
    <junit printsummary="true" haltonfailure="false" fork="yes" forkmode="once">
        <jvmarg value="${xms}" />
        <jvmarg value="${xmx}" />
        <classpath refid="classpath.test.dir" />
        <formatter type="xml" />
        <!-- 执行 build/test-classess下面的所有class -->
        <batchtest todir="${test.data.dir}">
            <fileset dir="${test.classess.dir}">
                <include name="**/*Test.class" />
            </fileset>
        </batchtest>
    </junit>
</jacoco:coverage>

这段代码就是用于执行Junit方法的,有关Junit标签详细使用说明请移步https://ant.apache.org/manual/。 通过标签batchtest指定了执行的范围,dir属性指定运行的class文件放在build/test-classess。这个目录下有可能有其他的文件所以使用include标签指定只执行名字后缀是Test的class文件。 这段脚本执行完毕之后会在reports/testResults得到一个exec文件。这些文件和目录也是脚本自动创建的。

2. 生成Junit报告

经过第一点的脚本之后,根据执行结果生成Junit报告。报告保存在reports/testResults。这部分是可选的,如果你不想要Junit报告可以删除这段脚本。删除之后再执行在reports/testResults目录下面就不会有TESTS-TestSuites.xml

<junitreport todir="${test.data.dir}">
    <fileset dir="${test.data.dir}">
        <include name="TEST-*.xml" />
    </fileset>
</junitreport>

3. 生成覆盖率报告

经过前面两步之后,可以根据前面两步的结果生成覆盖率的报告。报告所依赖的数据就是<file file="${test.data.dir}/jacoco.exec" />

<jacoco:report>
    <executiondata>
        <file file="${test.data.dir}/jacoco.exec" />
    </executiondata>
 
    <structure name="AntTestReporting">
        <classfiles>
            <fileset dir="${src.classess.dir}">
                <include name="**/*.class" />
                <!-- 生成的报告中排除test类 -->
                <!-- <exclude name="**/*Test*.class" /> -->
                <!-- Exclude inner classes -->
                <exclude name="**/*$*.class" />
            </fileset>
        </classfiles>
    </structure>
 
    <html destdir="${coverage.reports.dir}" />
</jacoco:report>

最终,整个脚本执行完毕之后,在项目根目录下可以看到如下截图内容:

覆盖率报告

用浏览器打开index.html,在这里文件中可以看到项目所有代码的覆盖率情况。

覆盖率报告2

报告单结果和你直接使用Coverage As运行的结果是一致的。如果别人需要项目的结果你就可以把reports目录发给他了,这个就是项目的单元覆盖率结果。

You can’t perform that action at this time.