Java中的异常测试框架JUnit使用上手指南
junit是由 erich gamma 和 kent beck 编写的一个回归测试框架(regression testing framework)。junit测试是程序员测试,即白盒测试。该项目主页:http://www.junit.org/
使用junit时,主要都是通过继承testcase类别来撰写测试用例,使用testxxx()名称来撰写单元测试。
用junit写测试真正所需要的就三件事:
1. 一个import语句引入所有junit.framework.*下的类。
2. 一个extends语句让你的类从testcase继承。
3. 一个调用super(string)的构造函数。
功能类mathtool
package com.zj.c01; public class mathtool { public static int gcd(int num1, int num2) { int r = 0; while (num2 != 0) { r = num1 % num2; num1 = num2; num2 = r; } return num1; } }
测试类mathtooltest
package com.zj.c01; import junit.framework.testcase; public class mathtooltest extends testcase { public mathtooltest(string name) { super(name); } public void testgcd() { assertequals(5, mathtool.gcd(10, 5)); } }
我们在用 junit 测试方法异常的时候,最容易想到的办法就是用 try…catch 去捕获异常,需要断言以下几个条件:
1. 确实抛出的异常
2. 抛出异常的 class 类型
3. 抛出异常的具体类型,一般检查异常的 message 属性中包含的字符串的断定
所以常用的代码你可能会这么写:
@test public void testbizexception() { try{ password.validate( "123" ); fail( "no exception thrown." ); }catch ( exception ex ) { asserttrue( ex instanceof bizexception ); asserttrue( ex.getmessage().contains( "error" ) ); } }
这里被测试的方法是 password.validate() 方法是否抛出了相应的异常,注意这里别漏 try 中的
fail(“no exception thrown.”)
代码行,不然如果被测试的方法如果没有抛出异常的话,这个用例是通过的,而你预期的是要抛出异常的。
在 junit 4 中,大可不必如此这般的去测试方法异常。虽然这样也能测定出是否执行出预期的异常来,但它仍有弊端,接下来会一对比就知道了,try…catch 的方法,junit 无法为你提示出详细的断言失败原因。
那么来看看自从 junit 4 后可以怎么去测试异常呢?用 @test(execpted=exception.class) 注解就行,参考如下代码:
@test( expected = bizexception.class ) public void testbizexception() { password.validate( null ); }
如果被测试的方法有抛出 bizexception类型便是断言成功,对了 @test(expected = bizexception.class) 只能判断出异常的类型,并无相应的注解能断言出异常的更具体的信息,即无法判定抛出异常的 message 属性。
那么,有时候我们会在一个方法中多次抛出一种类型的异常,但原因不同,即异常的 message 信息不同,比如出现 bizexception 时会有以下两种异常:
new bizexception(“password must contains at least 6 letters.”) new bizexception(“password length less than 15 letters”)
这就要有办法去断言异常的 message 了,针对于此,自 junit 4.7 之后又给了我们更完美的选择,就是下面的代码:
@rule public expectedexception expectedex = expectedexception.none(); @test public void testbizexception() throws invalidpasswordexception { expectedex.expect( bizexception.class ); expectedex.expectmessage( "required" ); password.validate( "" ); }
上面代码需重点关注几个:
1. @rule 注解的 expectedexception 变量声明,它必须为 public
2. @test 处,不能写成 @test(expected=bizexception.class),否则不能正确测试,也就是
@test(expected=bizexception.class) 和测试方法中的 expectedex.expectxxx() 方法是不能同时并存的
3. expectedex.expectmessage() 中的参数是 matcher 或 substring,就是说可用正则表达式判定,或判断是否包含某个子字符串
4. 再就是有一点很重,把被测试方法写在 expectedex.expectxxx() 方法后面,不然也不能正确测试的异常
5. 最后一个是,只要测试方法直接抛出被测试方法的异常即可,并不影响你所关心的异常
前面说到用 try…catch 的办法也能正确测试到异常,@test(expected=…) 或 @rule 与 try…catch 的方法对比有什么好处呢,显然用 junit 4 推荐的方法简洁明了。再来看测试失败时 junit 会为你提示什么呢?
try…catch 测试异常失败时,得到的提示:
无异常时:
java.lang.assertionerror: no exception thrown. at org.junit.assert.fail(assert.java:91) at cc.unmi.passwordtest.passwordlengthlessthan6lettersthrowsexception(passwordtest.java:20)
异常类型不对或异常的 message 不对时:
java.lang.assertionerror: at org.junit.assert.fail(assert.java:91) at org.junit.assert.asserttrue(assert.java:43) at org.junit.assert.asserttrue(assert.java:54) at cc.unmi.passwordtest.passwordlengthlessthan6lettersthrowsexception(passwordtest.java:22)
上面能提供给我们的定位错误的帮助不是特别大
再看 @test(expected=bizexception.class) 时测试失败时的提示:
java.lang.assertionerror: expected exception: cc.test.bizexception at org.junit.internal.runners.statements.expectexception.evaluate(expectexception.java:32) at org.junit.rules.expectedexception$expectedexceptionstatement.evaluate(expectedexception.java:110)
用 @rules expectedexception方式来测试异常,失败时的提示:
java.lang.assertionerror: expected: (exception with message a string containing “yes. required” and an instance of java.lang.nullpointerexception) got: at org.junit.assert.assertthat(assert.java:778) at org.junit.assert.assertthat(assert.java:736) at org.junit.rules.expectedexception$expectedexceptionstatement.evaluate(expectedexception.java:114)
特别是 @rules expectedexception 方法时为何测试失败提示的清清楚楚。期望什么异常,异常 message 中含何字符串,实际上确得到什么类型的异常,异常中 message 是什么。有了这,你一看到就知道怎么去修补你的程序。