先来说一下理论:JUnit4是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。
本文介绍了如何利用反射和注解去简单的模拟JUnit4单元测试框架,之所以选择JUnit4是因为4.0以后最大的改进就是使用了注解。需要注意的是这里并不是完全的模拟,只是简单实现了一下Runner类和JUnit注解相关的工作流程。所以本文的主要目的是介绍反射和注解的使用。废话不多说,直接进入正文。
看一个Junit单元测试的小例子:
先定义一个简单的类,里面只有一个add计算加法的方法和一个divide计算除法的方法,divide方法需要判断除数不能为0否则抛出异常。
[java] view plaincopy
public class calculate {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) throws Exception {
if (0 == b) {
throw new Exception("除数不能为0");
}
return a / b;
}
}
接着写一个简单的JUnit测试类,对他进行单元测试
[java] view plaincopy
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class calulateTest {
private calculate cal;
@Before //使用JUint提供的注解标注此方法在执行测试方法前执行
public void before() throws Exception {
cal = new calculate();
System.out.println("------------------");
System.out.println("before test");
}
@After //使用JUint提供的注解标注此方法在执行测试方法后执行
public void after() throws Exception {
System.out.println("after test");
}
@Test //使用JUint提供的注解标注此方法为需要测试的方法
public void addTest() {
System.out.println("do add test");
int result = cal.add(10, 20);
//判断result和预期的值是否相等,在此例中如果result等于30则测试通过
assertEquals(30, result);
}
@Test(expected = Exception.class) //使用JUnit的Test注解,并且判断预期值是否是Exception
public void div() throws Exception {
System.out.println("do divide test");
cal.divide(1, 0); //调用1除以0,抛出异常
}
}
执行结果为:
before test
do add test
after test
------------------
before test
do divide test
after test
下面我们就用反射和注解的知识来模拟JUnit对于上面例子的实现。
这里先不着急看代码,先看梳理一下思路。
1.JUnit只可以知道一件事,那就是待测试类的名字,其他的一概不知。所以我们只能利用测试类的名字作为切入口
2.通过测试类的名字,使用反射去获取他的Class对象
3.然后通过该Class对象获得当前类中所有方法的Method数组
4.遍历这个Method数组,取得每一个Method对象
5.调用每一个Method对象的isAnnotationPresent(Annotation.class)方法,判断该方法是否被指定注解所修饰
6.本例中根据不同的注解,来判断调用方法的顺序。
7.如果Test注解有属性的话,则判断方法执行后的返回值,如果返回值等于预期的注解属性也就是expected = Exception.class则测试通过。
8.最后还有一个assertEquals方法,他去判断预期值和实际值是否相等来决定测试是否通过。
大致的思路有了,我们就可以开始模拟它了。
首先定义3个注解,分别是Before,Test,After。如果对于定义注解不清楚的同学请看我之前写的文章。
[java] view plaincopy
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
}
[java] view plaincopy
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
Class<? extends Object> expected() default String.class;
}
[java] view plaincopy
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
}
三个很简单的注解,都标注只能修饰方法,保留策略为运行时,这样可以被反射读取到。
只有Test注解中定义了一个属性,类型可以为任何类型的Class对象,默认值为String类型的Class对象。