天天看点

Java单元测试(Junit+Mock+代码覆盖率)

单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的。

单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。

一般来说,单元测试任务包括

接口功能测试:用来保证接口功能的正确性。

局部数据结构测试(不常用):用来保证接口中的数据结构是正确的

比如变量有无初始值

变量是否溢出

边界条件测试

变量没有赋值(即为null)

变量是数值(或字符)

主要边界:最小值,最大值,无穷大(对于double等)

溢出边界(期望异常或拒绝服务):最小值-1,最大值+1

临近边界:最小值+1,最大值-1

变量是字符串

引用“字符变量”的边界

空字符串

对字符串长度应用“数值变量”的边界

变量是集合

空集合

对集合的大小应用“数值变量”的边界

调整次序:升序、降序

变量有规律

比如对于math.sqrt,给出n^2-1,和n^2+1的边界

所有独立执行通路测试:保证每一条代码,每个分支都经过测试

代码覆盖率

语句覆盖:保证每一个语句都执行到了

判定覆盖(分支覆盖):保证每一个分支都执行到

条件覆盖:保证每一个条件都覆盖到true和false(即if、while中的条件语句)

路径覆盖:保证每一个路径都覆盖到

相关软件

cobertura:语句覆盖

emma: eclipse插件eclemma

各条错误处理通路测试:保证每一个异常都经过测试

junit是java单元测试框架,已经在eclipse中默认安装。目前主流的有junit3和junit4。junit3中,测试用例需要继承<code>testcase</code> 类。junit4中,测试用例无需继承 <code>testcase</code> 类,只需要使用@test等注解。

如果采用默认的testsuite,则测试方法必须是 <code>public void testxxx() [throws exception] {}</code> 的形式,并且不能存在依赖关系,因为测试方法的调用顺序是不可预知的。

上例执行后,控制台会输出

从中,可以猜测到,对于每个测试方法,调用的形式是:

运行测试方法

在eclipse中,可以直接在类名或测试方法上右击,在弹出的右击菜单中选择 <code>run as -&gt; junit test。</code>

在 maven 中,可以直接通过 <code>mvn test</code> 命令运行测试用例。

也可以通过java方式调用,创建一个 <code>testcase</code> 实例,然后重载 <code>runtest()</code> 方法,在其方法内调用测试方法(可以多个)。

更加便捷地,可以在创建 <code>testcase</code> 实例时直接传入测试方法名称,junit会自动调用此测试方法,如

junit testsuite

testsuite是测试用例套件,能够运行过个测试方法。如果不指定testsuite,会创建一个默认的testsuite。默认testsuite会扫描当前内中的所有测试方法,然后运行。

如果不想采用默认的testsuite,则可以自定义testsuite。在testcase中,可以通过静态方法 <code>suite()</code> 返回自定义的suite。

允许上述方法,控制台输出

并且只运行了testmathpow测试方法,而没有运行testmathmin测试方法。通过显式指定测试方法,可以控制测试执行的顺序。

也可以通过java的方式创建testsuite,然后调用 <code>testcase</code>,如:

testresult中保存了很多测试数据,包括运行测试方法数目(runcount)等。

与junit3不同,junit4通过注解的方式来识别测试方法。目前支持的主要注解有:

<code>@beforeclass</code> 全局只会执行一次,而且是第一个运行

<code>@before</code> 在测试方法运行之前运行

<code>@test</code> 测试方法

<code>@after</code> 在测试方法运行之后允许

<code>@afterclass</code> 全局只会执行一次,而且是最后一个运行

<code>@ignore</code> 忽略此方法

下面举一个样例:

如果细心的话,会发现junit3的package是<code>junit.framework</code>,而junit4是<code>org.junit</code>。

执行此用例后,控制台会输出

可以看到,执行次序是<code>@beforeclass -&gt; @before -&gt; @test -&gt; @after -&gt; @before -&gt; @test -&gt; @after -&gt;@afterclass</code>。<code>@ignore</code>会被忽略。

与junit3类似,可以在eclipse中运行,也可以通过 <code>mvn test</code> 命令运行。

junit3和junit4都提供了一个assert类(虽然package不同,但是大致差不多)。assert类中定义了很多静态方法来进行断言。列表如下:

asserttrue(string message, boolean condition) 要求condition == true

assertfalse(string message, boolean condition) 要求condition == false

fail(string message) 必然失败,同样要求代码不可达

assertequals(string message, xxx expected,xxx actual) 要求expected.equals(actual)

assertarrayequals(string message, xxx[] expecteds,xxx [] actuals) 要求expected.equalsarray(actual)

assertnotnull(string message, object object) 要求object!=null

assertnull(string message, object object) 要求object==null

assertsame(string message, object expected, object actual) 要求expected == actual

assertnotsame(string message, object unexpected,object actual) 要求expected != actual

assertthat(string reason, t actual, matcher matcher) 要求matcher.matches(actual) == true

mock和stub是两种测试代码功能的方法。mock测重于对功能的模拟。stub测重于对功能的测试重现。比如对于list接口,mock会直接对list进行模拟,而stub会新建一个实现了list的testlist,在其中编写测试的代码。

强烈建议优先选择mock方式,因为mock方式下,模拟代码与测试代码放在一起,易读性好,而且扩展性、灵活性都比stub好。

比较流行的mock有:

<a href="http://jmock.org/">jmock</a>

<a href="http://www.easymock.org/">easymock</a>

<a href="http://blog.thihy.info/post/mockito.googlecode.com">mockito</a>

<a href="http://code.google.com/p/powermock/">powermock</a>

其中easymock和mockito对于java接口使用接口代理的方式来模拟,对于java类使用继承的方式来模拟(也即会创建一个新的class类)。mockito支持spy方式,可以对实例进行模拟。但它们都不能对静态方法和final类进行模拟,powermock通过修改字节码来支持了此功能。

easymock把测试过程分为三步:录制、运行测试代码、验证期望。

录制过程大概就是:期望method(params)执行times次(默认一次),返回result(可选),抛出exception异常(可选)。

验证期望过程将会检查方法的调用次数。

一个简单的样例是:

easymock还支持严格的检查,要求执行的方法次序与期望的完全一致。

这里从官方样例中摘录几个典型的:

验证调用行为

对mock对象进行stub

比较流行的工具是emma和jacoco,ecliplse插件有eclemma。eclemma2.0之前采用的是emma,之后采用的是jacoco。这里主要介绍一下jacoco。eclmama由于是eclipse插件,所以非常易用,就不多做介绍了。

jacoco可以嵌入到ant、maven中,也可以使用java agent技术监控任意java程序,也可以使用java api来定制功能。

jacoco会监控jvm中的调用,生成监控结果(默认保存在jacoco.exec文件中),然后分析此结果,配合源代码生成覆盖率报告。需要注意的是:监控和分析这两步,必须使用相同的class文件,否则由于class不同,而无法定位到具体的方法,导致覆盖率均为0%。

java report

可以使用ant、mvn或eclipse来分析jacoco.exec文件,也可以通过api来分析。

继续阅读