JUnit简介
JUnit是一个Java语言的单元测试框架,也是程序员使用的白盒测试框架。
JUnit4在JUnit3的基础上引入了JDK1.5的注解特性,同时兼容JUnit3的测试用例写法,本文只介绍JUnit4,尤其是平常用得很少的一些高级用法。
基础注解
- @Test测试方法
- @Ignore被忽略的测试方法
- @Before每一个测试方法之前执行一次的方法
- @After每一个测试方法之后执行一次的方法
- @BeforeClass所有测试方法之前执行一次,方法必须为static修饰
- @AfterClass所有测试方法之后执行一次,方法必须为static修饰
以上注解,只可作用于方法,而且方法必须是public void修饰。
基本上了解到以上注解,就可以使用junit4进行编写测试用例了,简单例子如下:
package jtest.samples.money;
/**
*
* @author guor
* @version 2014/6/3
*/
public class Money {
private int fAmount;
/**
* Constructs a money from the given amount
*/
public Money(int amount) {
fAmount = amount;
}
public int amount() {
return fAmount;
}
}
package jtest.samples.money;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
/**
* @author guor
* @version 2014/6/3
*/
public class MoneyTest {
private Money money;
@Before
public void setUp() throws Exception {
money = new Money(26);
}
@After
public void tearDown() throws Exception {
money = null;
}
@Test
public void testSimplify1() {
Assert.assertEquals(26, money.amount());
}
@Test
@Ignore
public void testSimplify2() {
Assert.assertEquals(26, money.amount());
}
}
正如上述示例,基本上掌握好以上注解,就可以编写普通的测试用例了。下面我将介绍JUnit4的一些高级用法。
高级特性
@Rule注解
@Rule注解用于标记属性或方法,其必须为public,不能为static,而且属性的类型和方法的返回值必须是org.junit.rules.TestRule或org.junit.rules.MethodRule的子类。
有了@Rule属性之后,测试用例执行的顺序为:@BeforeClass -> TestRule/MethodRule -> @Before -> @Test -> @After
从执行流程中可以看出,@Rule相当于对测试用例进行了一次拦截处理,知道这点之后,建议看看junit4为我们提供的TestRule四种默认实现,分别是ExternalResource(资源处理型)、Verifier(通知型)、TestWatcher(监听型)、Timeout(超时型),这里面也基本上就是设计模式里面的模版方法模式的应用,实际使用时,我们只需要对这四种实现进行实现即可。
下面给出Timeout的使用方法
package jtest.samples.money;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
/**
* @author guor
* @version 2014/6/3
*/
public class MoneyTest {
private Money money;
@Rule
public TestRule globalTimeout = new Timeout(1000); // 所有测试方法执行时间不能超过1s
@Before
public void setUp() throws Exception {
money = new Money(26);
}
@After
public void tearDown() throws Exception {
money = null;
}
@Test
public void testSimplify() {
Assert.assertEquals(26, money.amount());
}
}
例子中,定义了全局变量globalTimeout,并用@Rule注解标记,则所有的测试方法执行时间不能超过1s,否则程序会自动退出。
@RunWith注解
首先要知道JUnit中所有的测试用例,其最终都是由Runner调用运行的,默认的是BlockJUnit4ClassRunner,如果需要自定义一个Runner,除了需要继承Runner抽象类实现其抽象方法之外,还需要提供一个带参构造函数,其参数为Class,默认的Runner实现保证了测试类在测试用例执行之前初始化,而且不会持有这个测试类的对象,以确保JVM的垃圾回收机制正确执行,自定义Runner时,尤其需要注意这点,一般我们选择继承BlockJUnit4ClassRunner就可以了。
现在假设有测试需求,需要指定某些测试方法的执行次数,实现如下:
1. 自定义注解RunTimes
package jtest.samples;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 执行次数注解,作用在方法上,用于决定测试方法执行次数
* @author guor
* @version 2014/6/3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface RunTimes {
/**
* 测试用例执行次数,默认为1
*
* @return
*/
int value() default 1;
}
2. 自定义Runner,处理RunTimes注解
package jtest.samples;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
/**
* 自定义Runner,主要处理RunTimes注解的逻辑
* @author guor
* @version 2014/6/3
*/
public class CustomRunner extends BlockJUnit4ClassRunner {
public CustomRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
RunTimes annotation = method.getAnnotation(RunTimes.class);
if (annotation == null) {
super.runChild(method, notifier);
} else {
int runTimes = annotation.value();
for (int i = 0; i < runTimes; i++) {
super.runChild(method, notifier);
}
}
}
}
3. 简单测试用例
package jtest.samples;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* 简单测试用例
*
* @author guor
* @version 2014/6/3
*/
@RunWith(CustomRunner.class)
public class SimpleTest {
@Test
@RunTimes(10)
// 注解说明该测试方法执行次数
public void testSimplify() {
System.out.println(System.currentTimeMillis());
}
}