Java 1.5之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,JUnit测试框架原本要求用户一定要用test作为测试方法名称的开头。
命名模式的缺点:
- 文字拼写错误导致失败,测试方法没有执行,也没有报错
- 无法确保它们只用于相应的程序元素上,如希望一个类的所有方法被测试,把类命名为test开头,但JUnit不支持类级的测试,只在test开头的方法中生效
- 没有提供将参数值与程序元素关联起来的好方法。
注解能解决命名模式存在的问题,下面定义一个注解类型指定简单的测试,它们自动运行,并在抛出异常时失败
(注意,下面的Test注解是自定义的,不是JUnit的实现)
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
@Retention注解有三种
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
@Target(ElementType.METHOD)表明只有在方法声明中Test注解才是合法的
下面的Sample类使用Test注解,如果拼错Test或者将Test注解应用到除方法外的其他地方,
编译不会通过
public class Sample {
@Test public static void m1() {}
public static void m2() {}
@Test public static void m3() {
throw new RuntimeException("Boom");
}
public static void m4() {}
@Test public void m5() {}
public static void m6() {}
@Test public static void m7() {
throw new RuntimeException("Crash");
}
public static void m8() {}
}
下面是测试运行类:
public class RunTests {
public static void main(String[] args) throws ClassNotFoundException {
//测试的个数
int tests = 0;
//测试通过的个数
int passed = 0;
Class testClass = Class.forName("ex_35.Sample");
for(Method m : testClass.getDeclaredMethods()) {
//如果方法上存在@Test注解
if(m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
//如果测试方法抛出异常
} catch(InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " failed: " + exc);
//如果方法是无效的Test用法,
} catch(Exception e) {
System.out.println("INVALID @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
}
就完成了对@Test注解方法的测试,结果如下:
public static void ex_35.Sample.m3() failed: java.lang.RuntimeException: Boom
INVALID @Test: public void ex_35.Sample.m5()
public static void ex_35.Sample.m7() failed: java.lang.RuntimeException: Crash
Passed: 1, Failed: 3
针对只有在抛出异常才成功的注解(有参数注解的方法):
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Exception> value();
}
使用:
public class Sample2 {
@ExceptionTest(ArithmeticException.class)
public static void m1() {
int i = 1/0;
}
测试工具方法也很容易写,我们知道方法抛出异常时会进入InvocationTargetException,所以在catch那里判断一下抛出异常的类型就好了。
其他地方与上一个测试类一样:
catch(InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
//得到注解上的参数
Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value();
if(excType instanceof(exc)){
pass++;
}
看懂了测试工作类的写法,其实离Junit的理解也就很近了,后面我会分析分析Junit的源代码。
这里主要介绍注解比命名模式的优点,但是实际上我们写JavaEE用的命名模式很少,几乎都让Spring注解给替代了。如果看到必须要特殊进行命名的类或者方法,甚至是文件,我们都要留个心眼。