欢迎大家光临我的空间,希望能跟大家成为好朋友。

发布新日志

  • 【转】EMMA:测试覆盖率工具(2)

    2008-06-26 10:54:20

    本文主要通过一个示例项目介绍如何在集成了 Ant 和 Junit 的基础上,利用 EMMA 来收集单元测试对代码的覆盖率。

    介绍测试代码覆盖率的重要性

    测试驱动开发(TDD)是极限编程的一个重要特点,它具有很多优点,并被越来越多的开发人员所接受。在测试驱动开发过程中,程序员经历了编写测试用例,实现功能,重构代码这个不断迭代的过程。实践证明,这个过程能显著提高我们的生产效率,并产生高质量的代码。它还能给我们以自信,让我们放心的重构自己的代码。

    测试代码确实能够保证代码的质量,但如果你以为自己已经写了一堆测试用例,并都能运行通过时,就能高枕无忧了,那么你错了。隐藏的 Bug 也许只是在等待时机让你的系统崩溃。这是什么原因呢?聪明的你肯定已经想到,测试代码是用来保证功能代码的质量的,但测试代码的质量如何,我们不得而知。我们需要知道,我们辛苦编写的测试代码到底覆盖了多少功能代码,这就是我写这篇文章的出发点,我将介绍一种测试代码覆盖率的工具 - EMMA。


    介绍 EMMA

    EMMA 是一个用于检测和报告 JAVA 代码覆盖率的开源工具。它不但能很好的用于小型项目,很方便得得出覆盖率报告,而且适用于大型企业级别的项目。

    EMMA 有许多优点,首先你能免费得到它,并把它用于自己项目的开发。它支持许多种级别的覆盖率指标:包,类,方法,语句块(basic block)和行,特别是它能测出某一行是否只是被部分覆盖,如条件语句短路的情况。它能生成 text,xml,html 等形式的报告,以满足不同的需求,其 html 报告提供下钻功能,我们能够从 package 开始一步步链接到我们所关注的某个方法。EMMA 能和 Makefile 和 Ant 集成,便于应用于大型项目。特别还须指出的一点是,EMMA 的效率很高,这对于大型项目来说很重要。

    EMMA 是通过向 .class 文件中插入字节码的方式来跟踪记录被运行代码信息的。EMMA 支持两种模式:On the fly 和 Offline 模式。

    On the fly 模式往加载的类中加入字节码,相当于用 EMMA 实现的 application class loader 替代原来的 application class loader。

    Offline 模式在类被加载前,加入字节码。

    On the fly 模式比较方便,缺点也比较明显,如它不能为被 boot class loader 加载的类生成覆盖率报告,也不能为像 J2EE 容器那种自己有独特 class loader 的类生成覆盖率报告。这时,我们能求助于 Offline 模式。

    EMMA 也支持两种运行方式:Command line 和 Ant。

    本文后面提供的实例主要是演示如何集成 EMMA 和 Ant,通过 Offline 模式产生覆盖率报告。

    示例工程 SampleProject 是个小型的项目,有一个类 NumberParser,主要功能是把一个字符串解析成 float 型。下面是整个工程的目录结构。

    图1. 示例项目的目录结构
    图1. 示例项目的目录结构 

    下面,我们开始来为我们的工程编写 Ant 脚本。


    清单1设置一些属性,包括源文件,二进制文件,JUnit 报告,覆盖率报告等的路径
                <!-设置Java类被注入字节码后存放的路径-->
                <property name="bin.instrument.dir" location="../instrbin" />
                <!-设置覆盖率元数据和报告的路径-->
                <property name="coverage.dir" location="../coverage" />
                <!--设置junit报告的路径 -->
                <property name="junitReport.dir" location="../junitReport" />
                <!-设置主题代码bin路径-->
                <property name="bin.main.dir" location="../srcbin" />
                <!-设置测试代码bin路径-->
                <property name="bin.test.dir" location="../testbin" />
                <!--设置主题代码源路径-->
                <property name="src.main.dir" location="../../SampleProject/src" />
                <!--设置测试代码源路径-->
                <property name="src.test.dir" location="../../SampleProjectTest/test"
                />
                <!-指示需要注入字节码的Java类的路径-->
                <path id="classpath.main">
                <pathelement location="${bin.main.dir}" />
                </path>
                <!-指示 emma.jar 和emma_ant.jar 的路径-->
                <path id="emma.lib">
                <pathelement location="${libs}/emma.jar" />
                <pathelement location="${libs}/emma_ant.jar" />
                </path>
                <!-允许emma-->
                <property name="emma.enabled" value="true" />
                

    其中目录${ bin.instrument.dir }存放被注入字节码的类,"emma.lib" 指向 emma 资源所在的位置。
    清单2为 ANT 定义 EMMA 任务

    	<!-为ANT添加EMMA任务-->
                <taskdef resource="emma_ant.properties" classpathref="emma.lib" />
                

    清单3编译源代码和测试代码
    		<target name="compile-src.main">
                <mkdir dir="${bin.main.dir}" />
                <javac destdir="${bin.main.dir}" debug="on">
                <src path="${src.main.dir}" />
                </javac>
                <copy todir="${bin.main.dir}">
                <fileset dir="${src.main.dir}">
                <exclude name="**/*.java" />
                </fileset>
                </copy>
                </target>
                <target name="compile-src.test">
                <mkdir dir="${bin.test.dir}" />
                <javac destdir="${bin.test.dir}" debug="on">
                <src path="${src.test.dir}" />
                <classpath location="${bin.main.dir}" />
                </javac>
                <copy todir="${bin.test.dir}">
                <fileset dir="${src.test.dir}">
                <exclude name="**/*.java" />
                </fileset>
                </copy>
                </target>
                

    编译分两阶段,先编译源代码,然后再编译测试用例代码。

    清单4在所要测试类的代码中插入字节码
    		<!-对编译在路径bin.main.dir中的Java类注入字节码,
                并且把注入字节码的新Java类存放到路径bin.instrument.dir-->
                <!-覆盖率的元数据存放在路径coverage.dir中-->
                <target name="instrument">
                <mkdir dir="${bin.instrument.dir}" />
                <mkdir dir="${coverage.dir}" />
                <emma enabled="${emma.enabled}">
                <instr instrpathref="classpath.main"
                destdir="${bin.instrument.dir}"
                metadatafile="${coverage.dir}/metadata.emma"
                merge="true">
                </instr>
                </emma>
                <copy todir="${bin.instrument.dir}">
                <fileset dir="${bin.main.dir}">
                <exclude name="**/*.java" />
                </fileset>
                </copy>
                </target>
                

    当${emma.enabled}为 true 时,才生成插入字节码的类。<instr>中指定了要 instrument 的类的地址, instrumented 后类存放的地址,以及 metadata 存放的地址。
    清单5运行测试用例,得到一些生成报告的元数据

                <!-执行测试用例同时生成junit测试报告和emma代码覆盖率报告-->
                <target name="test">
                <mkdir dir="${junitReport.dir}" />
                <junit fork="true" forkmode="once"
                printsummary="withOutAndErr"
                errorproperty="test.error"
                showoutput="on">
                <!-指明代码覆盖率的元数据的存放位置-->
                <jvmarg
                value="-Demma.coverage.out.file=${coverage.dir}/metadata.emma" />
                <jvmarg value="-Demma.coverage.out.merge=true" />
                <classpath location="${bin.instrument.dir}" />
                <classpath location="${bin.test.dir}" />
                <classpath refid="emma.lib" />
                <formatter type="xml" />
                <!-执行所有以Test结尾的junit测试用例-->
                <batchtest todir="${junitReport.dir}" haltonfailure="no">
                <fileset dir="${bin.test.dir}">
                <include name="**/*Test.class" />
                </fileset>
                </batchtest>
                </junit>
                </target>
                

    在运行测试用例前,需要设置 jvmarg。所有的测试用例都跑在 instrumented 的类上面。
    清单6生成 JUnit 报告
     

                <target name="gen-report-junit">
                <!-生成junit测试报告-->
                <junitreport todir="${junitReport.dir}">
                <fileset dir="${junitReport.dir}">
                <include name="*" />
                </fileset>
                <report format="frames" todir="${junitReport.dir}" />
                </junitreport>
                </target>
                

     清单7生成覆盖率报告
    		<!-生成代码覆盖率报告-->
                <target name="gen-report-coverage">
                <!-如果属性emma.enabled的值是true,就生成代码覆盖率报告 -->
                <emma enabled="${emma.enabled}">
                <report sourcepath="${src.main.dir}"
                sort="+block,+name,+method,+class"
                metrics="method:70,block:80,line:80,class:100">
                <fileset dir="${coverage.dir}">
                <include name="*.emma" />
                </fileset>
                <html ōutfile="${coverage.dir}/coverage.html"
                depth="method" columns="name,class,method,block,line" />
                </report>
                </emma>
                </target>
                

    指明源代码所在的位置,以便能够显示每行代码的覆盖情况。Sort指明生成列表的排列顺序,"+"表示升序,"-"表示降序。Metrics 可为每个度量指明一个覆盖率阈值,若未达到该阈值,则该行会被标记出来(前提是报告的形式支持这个功能,如 HTML)。<html>指明以 HTML 形式生成报告,Depth 指明报告的详细程度,columns 指明生成列表列名的排列顺序。


    显示报告

    我们已经写好了Ant脚本,接下来你就可以运行该脚本了。这里假设你已经搭好了运行 Ant 和 JUnit 的环境,直接到脚本所在目录,在命令行敲入 Ant 即可。
    图2整个项目层次的报告
    图2整个项目层次的报告 
    图3包层次的报告
    图3包层次的报告
    图4类层次的报告
    图4类层次的报告 图5用颜色标记的源代码

    你会发现有三种颜色,绿色,红色和黄色,它们分别表示该行:被测试到,未被测试到,以及部分被测试到。红色或黄色的部分是需要引起你注意的,bug 也许就隐藏在这部分代码中,你所需做的就是设计一些测试用例,使它们运行以前未被执行到的语句。如上面那张图给出了我们一些信息,String 中含有"+"号的情况未被测试到,还有"isPositive"只被测试到 true 或 false 的一种情况,你需要相应的增加一些测试用例。运行新加的测试用例,你也许会发现一些新的 bug,并修正这些 bug。TestAge 中国软件测试时代

    藏在报告背后的问题

    对于这个简单的例子,你会发现,我们很容易达到 100% 的测试覆盖率,你也许会松口气说:啊,我把所有情况都测试到了,这下放心了。在这里很遗憾的告诉你,EMMA 的功能是有限的,它不支持决策覆盖和路径覆盖。事实上,对于一个稍复杂的工程进行穷尽的测试是不可能的。


    清单8决策覆盖和路径覆盖的代码示例
                /**
                * Parses the given string to a float number
                *
                * @param number
                *            the given string
                * @return the float number related with the string
                *
                * @throws IllegalArgumentException
                *             if the string is empty, null or can not parse to a float
                */
                public float parse(String number) {
                if (number.equals("")||number == null ) {
                throw new IllegalArgumentException(
                "Number string should not be empty or null");
                }
                StringIterator stringIterator = new StringIterator(number);
                getSign(stringIterator);
                int integer = getInteger(stringIterator);
                float fraction = getFraction(stringIterator);
                float total = integer + fraction;
                return isPositive ? total : (-1) * total;
                }
                

     清单9决策覆盖和路径覆盖的测试用例
                public void test_parse () {
                NumberParser np = new NumberParser();
                String number ="";
                try {
                np.parse(number);
                fail("should throw IAE");
                } catch (IllegalArgumentException e) {
                // pass
                }
                number = "22.010";
                float parsedNumber = np.parse(number);
                assertEquals((float) 22.010, parsedNumber);
                number = "-22.010";
                parsedNumber = np.parse(number);
                assertEquals((float) 22.010, parsedNumber);
                }
                

    运行 Ant 脚本,生成报告,你会发现,测试用例都运行通过了,测试覆盖报告也表明代码所有的行都被执行到了。但细心的读者肯定早已看到上面代码存在 Bug。若传进 parse 的 string 为 null 的话,并不是如我们所愿,得到 IllegalArgumentException,而是抛出了 NullPointerException。

    虽然下面那行是绿色的,但它只表明每个条件语句都被执行到了,并不能说明每个条件都取到true和false两种情况。在我们设计的测试用例中,"null == number"只取到 false 一种情况。我们需要在我们的测试用例中加入对 string 情况是 null 的测试。


    图6 决策覆盖和路径覆盖率报告
    图6 决策覆盖和路径覆盖率报告 
    清单10 修正代码的 Bug
     
                    if (null == number || "".equals(number)) {
                

    为你的项目生成覆盖率报告,EMMA 是个不错的选择。通过覆盖率报告,我们能发现并修复一些隐藏的 bug,我们的软件会变得更强壮。

  • 【转】EMMA:测试覆盖率工具(1)

    2008-06-26 10:40:46

    以前在做过的一个项目上面应用过EMMA,不过当时不是很清楚原理,今天又重新学习了一下。找了两篇不错的帖子跟大家共享一下,呵呵。

    在讨论EMMA的使用之前,我首先简要介绍几个相关的概念。

     测试覆盖率(Code Coverage)
      测试覆盖率,简单的说,就是评价测试活动覆盖产品代码的指标。测试的目的,是确认产品代码按照预期一样工作,也可以看作是产品代码工作方式的说明文档。进一步考虑,测试覆盖率可以看作是产品代码质量的间接指标--之所以说是间接指标,因为测试覆盖率评价的是测试代码的质量,并不是产品代码的质量。

     代码覆盖率是一种白盒测试,因为测试覆盖率是评价产品代码类内部的指标,而不是评价系统接口或规约。测试覆盖率尤其用于评价测试代码是否已经覆盖了产品代码所有的路径。

     衡量测试覆盖率的指标很多,常用的指标有:

     Statement coverage,也称作Line coverage,用于评价测试的代码语句覆盖率。

     Basic block coverage,是Statement coverage的一个变种,它把没有一个分支的代码区域作为一个计量单位,而不是简单的代码行,用于一个if-else分支代码行数远远大于另一个的情况,在这种情况下,statement coverage指标并不适用。

     Decision coverage(也称作Branch coverage),用于评价代码分支地测试覆盖率。

     Path coverage,和Decision coverage相似,用于评价代码从开始到结束所有路径的测试覆盖率。

     Function coverage,用于评价代码方法的测试覆盖率。

     EMMA目前支持四种Coverage类型:class、method、line和basic block。

     测试覆盖率的实现方式

      实现测试服务覆盖率的技术通常分为两种:

     1、Instrumentation

     Instrumentation技术在产品代码的关键位置插入统计代码。事实上,Instrumentation技术可以分为两种方式:Class Instrumentation和Source Instrumentation。前者把统计代码插入编译好的.class文件,而后者则把统计代码插入源代码并编译成新的.class文件。大多数测试覆盖率工具采用这两种Instrumentation技术。

       2、Custom JVM

     另一种方式是在JVM中把统计代码插入.class。测试覆盖率分析可以在JVM执行测试代码的过程中完成。

     测试覆盖率工具的典型特性
      1、和Ant集成
      2、多种报告输出格式
      3、源代码链接
      4、覆盖率历史报告

     EMMA的特点

     Instrumentatiton方式:EMMA使用两种模式来实现覆盖率的统计,它称作“offline”和“on-the-fly”。EMMA使用Instrumentation .class文件的方式。EMMA通过byte instrumentation生成.class文件的增强版本,加入了统计测试覆盖率的代码Hook。对于“offline”模式,它从硬盘读入.class文件,然后输出经过Instrumented的增强版本;对于“on-the-fly”模式,这些操作发生在JVM内部(即增强版本的.class文件不写入硬盘)。前者是通用的模式,而后者用于简单的Java应用程序。

      支持的覆盖率指标:EMMA支持class,method,line和basic block coverage指标。
      优越的性能和可伸缩性。
      Java平台支持:EMMA支持Java 1.2或更高版本的JVM,不依赖于任何第三方类库。
      CPL License。
      使用EMMA:命令行方式

     安装EMMA的jar文件到类路径-最简单的方法是,把emma.jar复制到/lib/ext/。注意,不要复制emma_ant.jar,否则使用Ant脚本会出错。
    on-the-fly模式:使用emmarun-使用-g选项编程java源代码,javac -g -d out ,然后执行java emmarun -cp out 。本方法把instrumentation和执行过程合而为一。
    offline模式:分开instrumentation过程和执行过程-首先使用-g选项编译java源代码,javac -g -d out ;然后是instrumentation,java emma instr -d outinstr -ip out,注意,经过instrumentation的class目标目录是outinstr;最后是执行过程,java -cp outinstr;out ,注意,把经过instrumentation的类路径放在前面,并在后面加上原来的类路径,因为instr命令没有处理properties文件和interface,这些都是执行过程需要的。
    使用EMMA:Ant

     设置instrumentation属性

    <property name="coverage.dir" value="${basedir}/coverage" />
    <property name="out.instr.dir" value="${basedir}/outinstr" />
    <property name="emma.enabled" value="true" />
    <property name="javac.debug" value="on" />
    <!-- path element used by EMMA taskdef below: -->
    <path id="emma.lib" >
    <pathelement location="${libs}/emma.jar" />
    <pathelement location="${libs}/emma_ant.jar" />
    </path>

    在Ant脚本中加入EMMA task
    <!-- this loads <emma> and <emmajava> custom tasks: -->
    <taskdef resource="emma_ant.properties" classpathref="emma.lib" />

    在编译Task中打开debug选项
    <target name="compile">
    <mkdir dir="${classes.main}" />
    <javac srcdir="${src.main}" destdir="${classes.main}" debug="${javac.debug}">
    <classpath refid="classpath.lib" />
    </javac>
    <copy todir="${classes.main}">
    <fileset dir="${src.main}" includes="**/*.xml, **/*.vm" />
    </copy>
    </target>

    instrumentation task
    <target name="instrument" depends="compile">
    <mkdir dir="${out.instr.dir}" />
    <mkdir dir="${coverage.dir}" />
    <emma enabled="${emma.enabled}" >
    <instr instrpathref="classpath.main"
    destdir="${out.instr.dir}"
    metadatafile="${coverage.dir}/metadata.emma"
    merge="true"
    >
    <filter excludes="com.talent.fw.formula.test.*Test*,com.talent.fw.esb.*Test*,com.talent.fw.message.test.*,com.
    talent.fw.entityengine.*Test*,com.talent.fw.integration.*Test*,com.talent.fw.security.impl.*Test*,testdomain.*" />
    </instr>
    </emma>
    </target>

    JUnit测试
    <target name="test" depends="compile, instrument">
    <mkdir dir="${reports.junit.data}" />
    <mkdir dir="${classes.main}/maps" />
    <mkdir dir="${classes.main}/scrīpts" />

    <copy todir="${classes.main}/maps">
    <fileset dir="${src.main}/maps" />
    </copy>
    <copy todir="${classes.main}/scrīpts">
    <fileset dir="${src.main}/scrīpts" />
    </copy>
    <copy todir="${classes.main}">
    <fileset dir="${src.main}" includes="*.xml, *.properties, **/*.vm, **/*.dtd" />
    </copy>

    <rmic classname="com.talent.fw.message.test.RMITestServer" base="${classes.main}"/>

    <junit printsummary="yes" haltonfailure="no" failureproperty="tests.failed">
    <classpath location="${out.instr.dir}" />
    <classpath location="${classes.main}" />
    <classpath location="${src.main}" />
    <classpath refid="classpath.lib" />
    <jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma" />
    <jvmarg value="-Demma.coverage.out.merge=true" />
    <formatter type="xml" />
    <batchtest fork="yes" todir="${reports.junit.data}" failureproperty="tests.failed">
    <fileset dir="${classes.main}">
    <include name="**/*Test.class" />
    <exclude name="**/AllTests.class" />
    <exclude name="**/Base*Test.class" />
    </fileset>
    </batchtest>
    </junit>
    </target>

    生成报告并复制到Tomcat的发布目录下
    <target name="coverage.report" depends="instrument">
    <!-- if enabled, generate coverage report(s): -->
    <emma enabled="${emma.enabled}" >
    <report sourcepath="${src.main}"
    sort="+block,+name,+method,+class"
    metrics="method:70,block:80,line:80,class:100"
    >
    <fileset dir="${coverage.dir}" >
    <include name="*.emma" />
    </fileset>

    <!-- <xml ōutfile="${coverage.dir}/coverage.xml" depth="package" /> -->
    <html ōutfile="${coverage.dir}/coverage.html" depth="method"
    columns="name,class,method,block,line"
    />
    </report>
    </emma>

    <mkdir dir="${coverage.publish.dir}" />
    <copy todir="${coverage.publish.dir}">
    <fileset dir="${coverage.dir}">
    <include name="**/*.html" />
    </fileset>
    </copy>
    </target>

      和CruiseControl集成
      最简单的集成方式是在cruisecontrol的navigation.jsp文件下方加入EMMA测试覆盖率报告的超链接。如下图:



      进一步的集成方式是,在生成HTML报告的同时,生成XML格式的报告,并为XML格式报告编写XSL文件,并加入到CruiseControl的buildresults.jsp文件中。

     

  • 【转】EMMA:测试覆盖率工具(1)

    2008-06-26 10:40:40

    以前在做过的一个项目上面应用过EMMA,不过当时不是很清楚原理,今天又重新学习了一下。找了两篇不错的帖子跟大家共享一下,呵呵。

    在讨论EMMA的使用之前,我首先简要介绍几个相关的概念。

     测试覆盖率(Code Coverage)
      测试覆盖率,简单的说,就是评价测试活动覆盖产品代码的指标。测试的目的,是确认产品代码按照预期一样工作,也可以看作是产品代码工作方式的说明文档。进一步考虑,测试覆盖率可以看作是产品代码质量的间接指标--之所以说是间接指标,因为测试覆盖率评价的是测试代码的质量,并不是产品代码的质量。

     代码覆盖率是一种白盒测试,因为测试覆盖率是评价产品代码类内部的指标,而不是评价系统接口或规约。测试覆盖率尤其用于评价测试代码是否已经覆盖了产品代码所有的路径。

     衡量测试覆盖率的指标很多,常用的指标有:

     Statement coverage,也称作Line coverage,用于评价测试的代码语句覆盖率。

     Basic block coverage,是Statement coverage的一个变种,它把没有一个分支的代码区域作为一个计量单位,而不是简单的代码行,用于一个if-else分支代码行数远远大于另一个的情况,在这种情况下,statement coverage指标并不适用。

     Decision coverage(也称作Branch coverage),用于评价代码分支地测试覆盖率。

     Path coverage,和Decision coverage相似,用于评价代码从开始到结束所有路径的测试覆盖率。

     Function coverage,用于评价代码方法的测试覆盖率。

     EMMA目前支持四种Coverage类型:class、method、line和basic block。

     测试覆盖率的实现方式

      实现测试服务覆盖率的技术通常分为两种:

     1、Instrumentation

     Instrumentation技术在产品代码的关键位置插入统计代码。事实上,Instrumentation技术可以分为两种方式:Class Instrumentation和Source Instrumentation。前者把统计代码插入编译好的.class文件,而后者则把统计代码插入源代码并编译成新的.class文件。大多数测试覆盖率工具采用这两种Instrumentation技术。

       2、Custom JVM

     另一种方式是在JVM中把统计代码插入.class。测试覆盖率分析可以在JVM执行测试代码的过程中完成。

     测试覆盖率工具的典型特性
      1、和Ant集成
      2、多种报告输出格式
      3、源代码链接
      4、覆盖率历史报告

     EMMA的特点

     Instrumentatiton方式:EMMA使用两种模式来实现覆盖率的统计,它称作“offline”和“on-the-fly”。EMMA使用Instrumentation .class文件的方式。EMMA通过byte instrumentation生成.class文件的增强版本,加入了统计测试覆盖率的代码Hook。对于“offline”模式,它从硬盘读入.class文件,然后输出经过Instrumented的增强版本;对于“on-the-fly”模式,这些操作发生在JVM内部(即增强版本的.class文件不写入硬盘)。前者是通用的模式,而后者用于简单的Java应用程序。

      支持的覆盖率指标:EMMA支持class,method,line和basic block coverage指标。
      优越的性能和可伸缩性。
      Java平台支持:EMMA支持Java 1.2或更高版本的JVM,不依赖于任何第三方类库。
      CPL License。
      使用EMMA:命令行方式

     安装EMMA的jar文件到类路径-最简单的方法是,把emma.jar复制到/lib/ext/。注意,不要复制emma_ant.jar,否则使用Ant脚本会出错。
    on-the-fly模式:使用emmarun-使用-g选项编程java源代码,javac -g -d out ,然后执行java emmarun -cp out 。本方法把instrumentation和执行过程合而为一。
    offline模式:分开instrumentation过程和执行过程-首先使用-g选项编译java源代码,javac -g -d out ;然后是instrumentation,java emma instr -d outinstr -ip out,注意,经过instrumentation的class目标目录是outinstr;最后是执行过程,java -cp outinstr;out ,注意,把经过instrumentation的类路径放在前面,并在后面加上原来的类路径,因为instr命令没有处理properties文件和interface,这些都是执行过程需要的。
    使用EMMA:Ant

     设置instrumentation属性

    <property name="coverage.dir" value="${basedir}/coverage" />
    <property name="out.instr.dir" value="${basedir}/outinstr" />
    <property name="emma.enabled" value="true" />
    <property name="javac.debug" value="on" />
    <!-- path element used by EMMA taskdef below: -->
    <path id="emma.lib" >
    <pathelement location="${libs}/emma.jar" />
    <pathelement location="${libs}/emma_ant.jar" />
    </path>

    在Ant脚本中加入EMMA task
    <!-- this loads <emma> and <emmajava> custom tasks: -->
    <taskdef resource="emma_ant.properties" classpathref="emma.lib" />

    在编译Task中打开debug选项
    <target name="compile">
    <mkdir dir="${classes.main}" />
    <javac srcdir="${src.main}" destdir="${classes.main}" debug="${javac.debug}">
    <classpath refid="classpath.lib" />
    </javac>
    <copy todir="${classes.main}">
    <fileset dir="${src.main}" includes="**/*.xml, **/*.vm" />
    </copy>
    </target>

    instrumentation task
    <target name="instrument" depends="compile">
    <mkdir dir="${out.instr.dir}" />
    <mkdir dir="${coverage.dir}" />
    <emma enabled="${emma.enabled}" >
    <instr instrpathref="classpath.main"
    destdir="${out.instr.dir}"
    metadatafile="${coverage.dir}/metadata.emma"
    merge="true"
    >
    <filter excludes="com.talent.fw.formula.test.*Test*,com.talent.fw.esb.*Test*,com.talent.fw.message.test.*,com.
    talent.fw.entityengine.*Test*,com.talent.fw.integration.*Test*,com.talent.fw.security.impl.*Test*,testdomain.*" />
    </instr>
    </emma>
    </target>

    JUnit测试
    <target name="test" depends="compile, instrument">
    <mkdir dir="${reports.junit.data}" />
    <mkdir dir="${classes.main}/maps" />
    <mkdir dir="${classes.main}/scrīpts" />

    <copy todir="${classes.main}/maps">
    <fileset dir="${src.main}/maps" />
    </copy>
    <copy todir="${classes.main}/scrīpts">
    <fileset dir="${src.main}/scrīpts" />
    </copy>
    <copy todir="${classes.main}">
    <fileset dir="${src.main}" includes="*.xml, *.properties, **/*.vm, **/*.dtd" />
    </copy>

    <rmic classname="com.talent.fw.message.test.RMITestServer" base="${classes.main}"/>

    <junit printsummary="yes" haltonfailure="no" failureproperty="tests.failed">
    <classpath location="${out.instr.dir}" />
    <classpath location="${classes.main}" />
    <classpath location="${src.main}" />
    <classpath refid="classpath.lib" />
    <jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma" />
    <jvmarg value="-Demma.coverage.out.merge=true" />
    <formatter type="xml" />
    <batchtest fork="yes" todir="${reports.junit.data}" failureproperty="tests.failed">
    <fileset dir="${classes.main}">
    <include name="**/*Test.class" />
    <exclude name="**/AllTests.class" />
    <exclude name="**/Base*Test.class" />
    </fileset>
    </batchtest>
    </junit>
    </target>

    生成报告并复制到Tomcat的发布目录下
    <target name="coverage.report" depends="instrument">
    <!-- if enabled, generate coverage report(s): -->
    <emma enabled="${emma.enabled}" >
    <report sourcepath="${src.main}"
    sort="+block,+name,+method,+class"
    metrics="method:70,block:80,line:80,class:100"
    >
    <fileset dir="${coverage.dir}" >
    <include name="*.emma" />
    </fileset>

    <!-- <xml ōutfile="${coverage.dir}/coverage.xml" depth="package" /> -->
    <html ōutfile="${coverage.dir}/coverage.html" depth="method"
    columns="name,class,method,block,line"
    />
    </report>
    </emma>

    <mkdir dir="${coverage.publish.dir}" />
    <copy todir="${coverage.publish.dir}">
    <fileset dir="${coverage.dir}">
    <include name="**/*.html" />
    </fileset>
    </copy>
    </target>

      和CruiseControl集成
      最简单的集成方式是在cruisecontrol的navigation.jsp文件下方加入EMMA测试覆盖率报告的超链接。如下图:



      进一步的集成方式是,在生成HTML报告的同时,生成XML格式的报告,并为XML格式报告编写XSL文件,并加入到CruiseControl的buildresults.jsp文件中。

     

  • [转]在Eclipse中使用JUnit

    2008-06-13 11:16:49

    看到一篇很不错的Junit的帖子,打算先转到自己空间来,有时间的时候自己去试试,呵呵。
     
    这篇文章将向你介绍Junit,一个用来在项目中进行测试和调试的工具。在介绍完TDD(以测试驱动开发)理论后,将进一步讲解怎样在流行的Eclipse中建立你自己的JUnit测试。向你展示如何测试Hello World这样简单的程序。

       

        许多书上都讨论了自动测试,但是只有很少的著作注意到这么一个问题,那就是怎样把这些测试组织起来。随着测试的增加,放置和调用这些测试却变得更加麻烦。这将成为一个重要问题,以至于出现了TDD,极限编程(XP)使TDD得以普及。另外,你可以这样理解TDD:通过测试来开发。

       

        TDD的主要规范:

       

        在编写程序代码之前,与之对应的自动测试必须被写好。甚至程序代码并不存在,那也要看见一个失败的测试结果。

        在测试通过后,副本代码必须被丢弃。

       

        有一个具体步骤(可能指的是《Extreme Programming》)可以被任何一个程序员来参考,而不需要特殊的其他方法。在我们开始写测试之前,这些步骤(章节)应该被首先阅读——怎样组织自动测试。

       

        讲解一下不同种类的测试:

       

        单元测试检测模块(也就是类)的正确性。如果对象需要访问外部的数据资源,例如数据库,就需要模拟一个mock objects,但在实际中真实数据与测试环境是不同的。

        客户测试:这是功能性、系统、和验收测试。用来测试整体的系统特性。在XP中,这些测试由用户编写。

        综合测试:介于用户测试和单元测试之间的桥梁。综合测试帮助测试应用程序的交互性。一般情况下,mock objects不被用于综合测试,它会增加测试时间。同样,综合测试经常依赖特殊的测试环境,例如数据库送来的测试数据。综合测试也需要用到外部类库。例如为J2EE应用程序进行综合测试的类库Cactus。解释这些测试超出了本文的范围,需要更加详细的信息请参考http://jakarta.apache.org/cactus/

        开发人员测试:这是用来让开发人员检验自己代码或新函数的。对于每一个开发人员,只要有可能,就需要有更多的测试来检验代码。组织这些测试和组织程序代码一样重要。

       

        在以下章节,只要提到“测试”,那就指的是开发人员测试。

        

        我们几乎准备好开始建立测试了,先应该为我们的测试选择名字。你也许会说,“这不是问题:把‘Test’这个字放在类名前面,就好了!”不会这么快!让我来说一下这个步骤存在的问题:

       

        在TDD中,被测试的类或者方法还不存在。

        一个测试能够覆盖多个方法,甚至多个类,这是可能的。

       

        以上只是一些普遍问题;还存在更多的问题。

       

        让我来提一个建议,在测试命名时:测试类的名字应该让人一眼就知道这是一个测试类,且能说明它要测试什么,注意是否和其他类重名。按照以上建议做,就很简单了,也不用担心名字太长或难听。

       

        即将在Eclipse中用JUnit工具创建我们第一个测试了。假设你已经下载了一个最新的Eclipse版本。如果还没有,你应该去官方站点http://www.eclipse.org下载。还需要JUnit,也可以从http://www.junit.org/下载。

       

        运行Eclipse。新建一个workplace项目,点击文件->新建->项目,选择Java项目,点击下一步。起一个项目名称,例如ProjectWithJUnit。点击完成。这样就完成新项目的建立了。再来配置一下Eclipse,在构建路径中添加JUnit类库。在工具条上点击项目->属性,选择Java构建路径,选择添加外部JAR,浏览Junit被存储的目录,选择junit.jar,点击打开。你将会看见JUnit出现在库的列表中。点击确定,让Eclipse重建路径。

       

        现在开发我们的“Hello World”例子。按照TDD的规则,应该在代码建立以前先把测试写好。为了能够在某出开始,我们假设未来的类名是HelloWorld,并且有一个方法Say(),这个方法返回String的值(例如“Hello World!”)。

       

        建立测试,在ProjectWithJUnit的标题上面点击右键,选择新建->其他,展开“Java”选项,选择JUnit。在右边的栏目对话框中选择测试案例,然后下一步。参考图1。

     

           

                        1. 在Eclipse中建立JUnit测试

       

        在测试类这一栏中,写上将要被测试的类名HelloWorld。选择一个测试案例的名字,例如TestThatWeGetHelloWorldPrompt(是的,看上去很长,但是很清楚它的行为。)点击完成

       

        TestThatWeGetHelloWorldPrompt的代码如下:

     

        import junit.framework.TestCase;

     

        public class TestThatWeGetHelloWorldPrompt

        extends TestCase {

            public TestThatWeGetHelloWorldPrompt(

                String name) {

                super(name);

            }

            public void testSay() {

                HelloWorld hi = new HelloWorld();

                assertEquals("Hello World!", hi.say());

            }

            public static void main(String[] args) {

                junit.textui.TestRunner.run(

                    TestThatWeGetHelloWorldPrompt.class);

            }

        }

     

        代码并不复杂;只是有点与众不同。然而,让我们考察一下细节。我们继承了JUnit的TestCase类,它在JUnit的javadocs定义为“运行众多测试的夹具。”JUnit也有TestSuite类,它是一组测试案例的集合,但在本文中不做讨论。

       

        建立测试案例的步骤如下:

       

        1、建立一个junit.framework.TestCase的实例。

        2、定义一些以“test”开头的无返回方法(例如testWasTransactionSuccessful(),testShow(),等等)。

       

        TestThatWeGetHelloWorldPrompt.java包含这些:TestCase的子类和一个叫做testSay()的方法。这个方法调用了assertEquals()函数,它用来比较我们预期的值和由say()返回的值。

       

        main()方法用来运行测试和显示输出的。JUnit的TestRunner处理测试,提供基于图像和文本的输出表现形式。我们使用基于文本的版本,因为Eclipse支持它,且也适合我们。当开始运行后,基于文本的版本测试会以文本形式输出,Eclipse会把这些输出自动变成图像界面的输出。

       

        按照TDD规范,首次运行测试,应该故意让它失败。点击运行->运行为->Junit测试(记住TestThatWeGetHelloWorldPrompt.java应该被突出的显示在包资源管理器中)。在左边窗口,应该看见JUnit窗口而不是包资源管理器,它显示一个红条,一次失败的测试,具体的失败原因参看图2。如果没有自动显示这些内容,点击JUnit标签(在底部的左边)。

     

              

                        2. JUnit中失败的测试

       

        很好!的却失败了。现在我们来建立被测试代码:在包资源管理器窗口的ProjectWithJUnit标题上右击,选择新建->。选择类名,我们已经假设了它叫HelloWorld,然后直接点击完成。为HelloWorld.java填入下列代码:

     

            public class HelloWorld {

                public String say() {

                    return("Hello World!");

                }

            }

           

        这段代码很简单,甚至不需要注解,我们再来看看结果。按照上面描述过的方式,在JUnit的窗口中显示了一个绿条,参看图3。绿条证明测试成功。

     

              

                         3. JUnit中成功的测试

                                  

        现在,我们想再让测试失败一次,但原因不同。这有助于展示JUnit测试中不同的报错信息。修改assertEquals()代码,把“Hello World!”变成“Hello Me!”。当再次运行JUnit时,结果变成了红条,在JUnit窗口的底部输出了失败原因,参看图4。

     

               

                        4. JUnit中的ComparisonError

                                 

        最后,我想说一下关于测试是开发过程中的必要部分的话题。测试代码一直是开发中的重要部分。经过近几年的发展,已得到了很大的提高,这要归功于强大的理论研究(比如“expectations-based development”等等),和快速发展的测试工具包,还有测试过程的改进。如果你对这篇文章感兴趣,那请你花一些时间来正式的学习一下测试理论吧,这对你的工作很有用。

       

        关于作者:

        Alexander Prohorenko 一名UNIX系统管理员、网络安全管理员。

        Olexiy Prohorenko    一名Java开发者居住在乌克兰的Dniepropetrovsk。

  • 【原创】系统接口测试有感

    2008-01-24 22:47:37

        最近忙于两个系统间的测试。现在将近测试尾期了,自己觉得在测试过程中收获了很多,也有一些值得改进的地方。所以把感触写出来,希望给自己和别人以后的测试工作有所帮助。

        接口是基于两个不同的系统,进行的信息交互。因为是不同的系统,所以各个系统间的很多的设置和业务逻辑是不一样的。可以肯定的一点是系统间还是有部分相同的操作和信息的,要不然也不用做接口了。

        首先,业务分析人员会给出系统间接口的业务逻辑和需求,以及两个系统间的一些字段的mapping表。(就是A系统的某个字段对应到B系统的某个字段,都应该在这个文档中清晰正确的给出)

        QA在拿到需求和匹配文档之后,就应该结合自己所测系统的逻辑跟接口逻辑比较(有些系统的接口逻辑和应用软件的逻辑不一样,接口会增加或放开某些逻辑限制 )。QA应该仔细比较异同处,因为文档当中也有可能有错误。

        在编写测试用例初期,QA应仔细分析需求和进行静态测试。对于mapping表里面的字段,如果是真正系统实施的时候,有些字段是需要做同步的。mapping表里面的系统下拉菜单的字段要特别留意,像下拉菜单里面的value是不是完全匹配的,而且不能只是凭UI上的值去判断,一定要用DB里面的code去判断。因为有些字段,虽然在两个系统UI上的值是一样的,但是在DB里面的code是不一样的,而接口测试肯定是用code来传递参数值的。(我在测试初期就犯了这个错误,只在UI上判断两个系统里面的菜单value。忽略了code,导致在测试过程中很多defect是因为这种原因产生的,而这些其实是可以在测试前发现并提出的。)测试用例的设计基本上采用"End-To-End" 流程模式。

       一般在测试的第一,二个版本上系统都是不稳定的,可能只是实现了需求中的部分功能。所以在测试初期我们不需要去测试很具体的东西,只需要走大概的流程。去确认一下在这个测试版本上实现了哪些功能,哪些功能还没有实现。

       在第三、四个版本上功能基本上都实现了,这个时候我们就需要仔细的测试各个"End-To-End"的flow了,一般还会有挺多的defect的。这两个版本测试过程中,我们应该注意要插入对于mapping表里面所有字段的测试,看是不是所有的字段都能正常显示,某些特殊字段我们还需要去xml文件里面核对一些code。(在这次测试过程中,我的失误就是对于字段的测试进行的太晚,以至于到测试后期出现某个字段的defect导致function flow走不下去的情况。) 这个阶段还有一个值得注意的问题就是,有些字段在第一次从接口传送的时候是可以成功发送的。但是一旦修改后,即使没有修改的一些字段再次发送的时候都有可能被miss掉的。

      在第五、六个版本上,基本上就比较稳定了。但是此时也不能掉以轻心,还是要把所有的flow都仔细的走一遍,可能还会有漏网之鱼呢,呵呵。

      以上就是我这次接口测试的心得,希望对大家的测试有帮助。^_^

      

     

     

     

     

     

     

Open Toolbar