Junit还是比较简单易用的。
首先,需要导入1个包,就是junit.jar,这个在www.junit.org上应该有下载。我用的是junit3.8.1。
另外,还有一个包junit-addons-1.4.jar也很有用。主要是PivateAccessor这个类用处比较大,续后会介绍。我的工程目录(Eclipse3.1)如下:
注:
测试类的位置没有特殊的要求,不过最好不要和源代码放在一起。但是如果另外建一个包放置测试类的话,在测试的时候就不得不把被测试的类import进来。为了避免这种情况,可以如上图所示建立工程目录,这样,被测试代码和测试代码在2个文件夹中,但是包路径是相同的,也就不用import了。
示例用代码(被测试类):
package com.mycompany.junittest;
public class Example {
private int Foo;
public Example(){
Foo = 0;
}
public int getFoo(){
return Foo;
}
private void increment(int i){
if (i < 1) {
throw new IllegalArgumentException();
}
Foo = Foo + i;
}
}
Junit的使用很简单。新建一个类只要使它继承junit.framework.TestCase类就行了。
测试类的命名一般都是 被测试类名+Test。
e.g.
对于public方法的测试:
直接new 一个被测试类的实例,调用该方法就可以了。
察看Junit的文档可以看到TestCase类提供了很多assertXXX方法,它们是用来确认测试结果的。根据测试的不同结果,选择不同的assertXXX方法。这一点很容易理解。
另外,比如上面的assertEquals("testIncrement_Success() failed",expected,actual),也可以使用assertEquals(expected,actual)。不过,建议使用前者,并且总是提供一些有用的描述信息,这样不但在测试失败的时候更容易调试,也增加了程序的易读性,易于维护。
对于private方法的测试:
对于类中的私有成员变量和私有方法,从外部是无法访问的。
对于私有方法的测试,一种方法是声明一个内部类,不过这种方法会使测试代码比较复杂,而且在被测试类被改动的时候,不得不把改动后的类重新拷贝到测试类中。另一种方法就是使用PrivateAccessor类。它的setField(),getField()方法可以设置或取得私有成员变量;invoke()方法可以调用被测试类的私有方法。
对于异常分支的测试:
有时候方法会抛出异常,需要对这些异常能否在应该抛出的时候抛出进行测试。
如果抛出异常的话,那么程序会跳到catch处,执行其后的语句;assertTrue(true)语句并没有实际的意义,因为它总是会成功执行。之所以在这里添加是为了增加程序的可读性。
Fail()方法的作用是把这个测试fail掉。如果测试代码按照预想的抛出异常的话,就不会执行到fail()语句。当程序没有抛出异常的时候,说明这次测试失败了,必须手动fail掉这次测试,因为Junit并不知道什么样是测试成功,什么样是失败;如果没有fail()语句的话,这次测试会正常完成,但是实际上这次测试是失败的,因为并没有达到要测试异常的目的。
关于setUp()和tearDown():
在每个testXXX()方法执行之前Junit都会调用setUp()方法;
在每个testXXX()方法执行之后Junit都会调用tearDown()方法。
这样就保证了不同的测试之间不会相互影响。另外,不要使测试结果之间产生依赖;即不要把一次测试产生的结果,作为另一次测试的条件。这样会增加测试代码的复杂性,而且Junit并不保证所有测试的执行顺序。
附:完整代码
被测试代码:
package com.mycompany.junittest;
public class Example {
private int Foo;
public Example(){
Foo = 0;
}
public int getFoo(){
return Foo;
}
private void increment(int i){
if (i < 1) {
throw new IllegalArgumentException();
}
Foo = Foo + i;
}
}
测试代码:
package com.mycompany.junittest;
import junit.framework.TestCase;
import junitx.util.PrivateAccessor;
public class ExampleTest extends TestCase {
Example ex;
protected void setUp() throws Exception{
super.setUp();
ex = new Example();
}
protected void tearDown() throws Exception{
super.tearDown();
}
/*
* Test public method
*/
public void testGetFoo() throws Throwable{
int ret = ex.getFoo();
int expected = ((Integer)PrivateAccessor.getField(ex,"Foo")).intValue();
assertEquals("testGetFoo() failed",expected,ret);
}
/*
* Test private method
*/
public void testIncrement_Success() throws Throwable{
int expected = 1;
PrivateAccessor.invoke(ex,"increment",new Class[]{int.class},new Object[]{new Integer(expected)});
int actual = ((Integer)PrivateAccessor.getField(ex,"Foo")).intValue();
assertEquals("testIncrement_Success() failed",expected,actual);
}
/*
* Test exceptions
*/
public void testIncrement_Exception() throws Throwable{
try {
PrivateAccessor.invoke(ex,"increment",new Class[]{int.class},new Object[]{new Integer(0)});
fail("testIncrement_Exception() failed: IllegalArgumentException not thrown!!!");
} catch (IllegalArgumentException e) {
assertTrue(true);
e.getMessage();
}
}
}
===============> update on 19th May <==================
package com.company;
import junit.extensions.RepeatedTest;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class RepeatTest extends TestCase {
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public static Test suite(){
TestSuite suite = new TestSuite();
TestSuite testSuite = new TestSuite( MoneyTest.class );
//repeat MoneyTest for 10 times
RepeatedTest repeatedTest = new RepeatedTest(testSuite, 10);
suite.addTest(repeatedTest);
return suite;
}
}