JUnit 4使用手册
笔者此前使用过JUnit 3,工作关系很长时间没再碰Java了。最近重新接触了一下,发现JUnit 4和3有较大区别,特总结一下JUnit 4的基本用法,供自己查阅也供朋友们参考。
一、JUnit简介
JUnit由Kent Beck和ErichGamma开发,几乎毫无疑问是迄今所开发的最重要的第三方Java库,它也成为了Java语言事实上的标准单元测试库。正如Martin Fowler所说,“在软件开发领域,从来就没有如此少的代码起到了如此重要的作用”。JUnit引导并促进了测试先行的编程和测试驱动的开发。
JUnit 4是该库三年以来最具里程碑意义的一次发布。它的新特性主要是通过采用Java 5中的标记annotation)而不是利用子类、反射或命名机制来识别测试,从而简化测试代码。用Kent的话来说,“JUnit 4 的主题是通过进一步简化 JUnit,鼓励更多的开发人员编写更多的测试。”
二、JUnit4实践
选择一款IDE(Eclipse, NetBean, Idea等),基本上它们都全面支持JUnit了。创建工程后,为了避免代码混乱,建议为单元测试代码与被测试代码分别创建单独的目录。首先,写一个很简单的方法,判断输入的邮箱地址是否符合规范:
public static boolean checkEmail(String email) {
if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
return false;
}
return true;
}
OK,一切准备就绪,我们开始编写这个方法的单元测试用例。
1、测试由@Test注释开始
@Test
public void checkEmail(){
assertEquals(true, RegexUtil.checkEmail("[email protected]"));
}
以前版本的JUnit通过命名约定和反射来定位测试用例,要求测试方法以”test”开头+方法名,并且测试类需要继承TestCase。JUnit 4中简化了这个操作,只需要在测试类中引入org.junit.Test,在测试方法前使用注解@Test,JUnit就可以侦测该测试方法了,保持了代码的简洁。
注:在JUnit4中仍然可以以原来的方式进行测试(继承TestCase并在方法前加test)。但是如果这样就最好不要使用注解。因为一旦继承了TestCase,注解会失效,如果没有test前缀,会报:AssertionFailedError: No tests found…异常。
2、Fixture
它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等。JUnit专门提供了设置公共Fixture的方法,同一测试类中的所有测试方法都可以共用它来初始化Fixture和注销Fixture。在以前的版本,JUnit使用SetUp和TearDown方法。在JUnit4中,使用注解org,junit.Before和org.junit.After。例如:
@Before
public void initialize (){……}
@After
public void dispose (){……}
这样,在每一个测试方法执行之前,JUnit会保证注解了Before的方法提前执行。当测试方法执行完毕后,JUnit调用注解了After的方法注销测试环境。
注:@Before和@After修饰的是方法级别的。它们都可以修饰多个方法,但是方法执行的顺序不能保证。
在JUnit 4中还引入了类级别的Fixture设置方法,即使用注解 BeforeClass和AfterClass。这两种方法都使用 public static void 修饰,且不能带有任何参数。类级别的Fixture仅会在测试类中所有测试方法执行之前和之后执行。
3、异常和测试时间
JUnit 4中引入了异常的测试,以前版本中异常测试是在抛出异常的代码中放入try块,然后在try块的末尾加入fail语句。在JUnit 4中,可以使用@Test中的expected参数,它表示测试方法期望抛出的异常,如果运行测试并没有抛出这个异常,则JUnit会认为这个测试没有通过。例如:
@Test(expected= IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(1);
}
@Test的另一个参数timeout,用来指定被测试方法被允许运行的最长时间。如果测试方法运行时间超过了指定的毫秒数,则JUnit认为测试失败。例如:
@Test(timeout = 10)
public void checkEmail(){
assertEquals(true, RegexUtil.checkEmail("[email protected]"));
}
4、忽略测试方法
JUnit 4提供注解org.junit.Ignore用于暂时忽略某个测试方法。例如:
@Ignore(“db is down”)
@Test(expected=UnsupportedDBVersionException.class)
public void unsupportedDBCheck(){ …… }
5、测试用例的执行
JUnit中所有的测试用例都是由测试运行器执行的。JUnit提供了默认的测试运行器,但并没有限制我们必须使用默认的运行器(所有的运行器都继承自Runner)。相反,我们不仅可以定制自己的运行器,而且还可以为每个测试类指定使用某个运行器(使用@RunWith)。例如JUnit的自测代码:
public class RunWithTest {
private static String log;
public static class ExampleRunner extends Runner {
public ExampleRunner(Class<?> klass) {
log+= "initialize";
}
@Override
public void run(RunNotifier notifier) {
log+= "run";
}
@Override
public Description getDescription() {
log+= "plan";
return Description.createSuiteDescription("example");
}
}
@RunWith(ExampleRunner.class)
public static class ExampleTest {
}
@Test public void run() {
log= "";
JUnitCore.runClasses(ExampleTest.class);
assertTrue(log.contains("plan"));
assertTrue(log.contains("initialize"));
assertTrue(log.contains("run"));
}
}
显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit 会启动默认的测试运行器执行测试类。一般情况下,默认测试运行器可以应对绝大多数的测试要求;当使用 JUnit 提供的一些高级特性或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。
6、测试套件Suite
正如JUnit以前版本中提供的Suite一样,JUnit4提供了一种批量运行测试类的方法,以方便我们在每次进行系统测试时,只需执行若干测试套件而不是执行无数测试用例。我们只需创建一个空类,并填写两个注解。例如:
@RunWith(Suite.class)
@Suite.SuiteClasses({TestCheckEmail.class, TestTimeUtil.class})
public class CustomizeRunner{
}
测试套件中不仅可以包含基本的测试类,而且可以包含其它的测试套件。但是,一定要保证测试套件之间没有循环包含关系,否则将出现死循环。
7、参数化测试
当我们编写了大量的单元测试方法后,我们发现这些方法其实大同小异,只是参数不同(测试边界值或者测试异常值)。在以前的 JUnit版本上,并没有好的解决方法,而现在我们可以使用JUnit提供的参数化测试方式解决这个问题。
首先在测试类中指定参数运行期@RunWith(Parameterized.class)。例如:
@RunWith(Parameterized.class)
public class TestWithParam {
@Parameterized.Parameters
public static List<Object[]> data() {
return Arrays.asList(new Object[][]{
{0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8},{10,55}
});
}
private int fInput;
private int fExpected;
public testWithParam(int input, int expected) {
fInput = input;
fExpected = expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
private static class Fibonacci{
public static int compute(int input){
if(input ==0)
return 0;
else if (input==1)
return 1;
else if (input==2)
return 1;
else return compute(input-1)+compute(input-2);
}
}
}
在静态方法data中,我们使用二维数组来构建测试所需要的参数列表,其中每个数组中的元素的放置顺序只要和构造函数中的顺序保持一致就可以了。
参考文献:
1.单元测试利器 JUnit 4:http://www.ibm.com/developerworks/cn/java/j-lo-junit4/
2.JUnit 4 JavaDoc