目标:已知有一个
Calculators
类,该类中包含加减乘除等运算方法。
- 编写
Check
注解对需要进行测试的成员方法进行标记;- 设计一个框架对该类中的任意public修饰的、带有
@Check
注解标记的成员方法进行测试;- 捕捉该类中各个成员方法的异常并记录到日志文件中;
- 要求后续如果要测试其他类中相应的成员方法,无需修改代码,而仅修改配置文件即可直接调用该测试框架。
首先定义一个TestProperty
类创建.properties
配置文件:
package annotation.testframe; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; /** * 创建测试所需的配置文件 */ public class TestProperty { public static void main(String[] args) throws IOException { //创建一个properties集合用于存储要写入文件的数据 Properties prop = new Properties(); //往prop集合中写入数据 prop.setProperty("className","annotation.testframe.Calculators"); //将prop集合中的数据存入配置文件 prop.store(new FileOutputStream("annotation/testframe/testProp.properties"), "该文件存放用于测试的类的名字,目的是测试该类中所有用public修饰的成员方法。"); } }
创建的配置文件testProp.properties
内容如下:
#该文件存放用于测试的类的名字,目的是测试该类中所有用public修饰的成员方法。 #Tue Jan 10 12:23:03 CST 2023 className=annotation.testframe.Calculators
后续只需修改不同的类名className
,即可调用此测试框架对不同的类进行测试。
当然,不同的类的不同的成员方法所需传入的参数不同,实际测试时仍需要在测试类中修改待测试成员方法所需传入的参数。
定义Calculators
类如下:
该类中强行造了一个空指针异常。
package annotation.testframe; public class Calculators { public Calculators() { } @Check public int add(int a, int b) { //造一个异常 String str = null; str.toString(); return a + b; } @Check public int sub(int a, int b) { return a - b; } @Check public int multiply(int a, int b) { return a * b; } @Check public int devide(int a, int b) { return a / b; } public void test(){ System.out.println("看看我被选中了嘛"); } }
定义Check
注解:
package annotation.testframe; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }
定义测试类:
package annotation.testframe; import junit.Calculator; import java.io.*; import java.lang.annotation.Retention; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Properties; public class TestFrame { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //创建prop集合对象以接收加载的配置文件中的数据 Properties prop = new Properties(); //加载配置文件进内存 prop.load(new FileInputStream("annotation/testframe/testProp.properties")); //获取配置文件中所存放的值,即获取要测试的类的名字 String className = prop.getProperty("className"); //获取该类的class对象 Class testClass = Class.forName(className); //System.out.println(testClass);//class annotation.testframe.Calculators //获取该类所有用public修饰的成员方法 Method[] allMethods = testClass.getMethods(); //获取该类所有用public修饰的、带有Check注解的成员方法 ArrayList<Method> checkedMethod = new ArrayList<>(); for (Method tm : allMethods) { if (tm.isAnnotationPresent(Check.class)) { checkedMethod.add(tm); } } //创建该类的对象 Calculators cal = (Calculators) testClass.getConstructor().newInstance(); //对需要测试的成员方法挨个进行测试 int count = 0;//记录异常出现的次数 PrintWriter pw = new PrintWriter(new FileOutputStream("annotation/testframe/buglog.txt", true)); pw.println(Check.class.getAnnotation(Retention.class)); pw.println("本次测试的类:" + className); for (Method cm : checkedMethod) { try { cm.invoke(cal, 2, 3); cm.invoke(cal, 0, 2); cm.invoke(cal, 2, 0); cm.invoke(cal, 2, -3); cm.invoke(cal, -2, 3); } catch (Exception e) { count++; pw.println("异常的方法:"+cm.getName()); pw.println("异常的名称:" + e.getCause().getClass().getName()); pw.println("异常的原因:" + e.getCause().getMessage()); pw.println("-------------------------------"); } } //记录当时时间 ZonedDateTime now = ZonedDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EE a"); pw.println("本次测试一共出现 " + count + " 次异常。"); pw.println("测试时间:" + dtf.format(now)); pw.println("=================================="); pw.close(); } }
运行测试类,输出日志文件buglog.txt
内容为:
@java.lang.annotation.Retention(RUNTIME) 本次测试的类:annotation.testframe.Calculators 异常的方法:add 异常的名称:java.lang.NullPointerException 异常的原因:Cannot invoke "String.toString()" because "str" is null ------------------------------- 异常的方法:devide 异常的名称:java.lang.ArithmeticException 异常的原因:/ by zero ------------------------------- 本次测试一共出现 2 次异常。 测试时间:2023-01-10 14:32:32 周二 下午 ==================================
该日志文件中记录了2次异常,与实际结果相符合。
再次观察@Check
注解,它的元注解@Retention
设定了其生命周期,表示@Check
注解生效的时间,观察前文日志文件,此处的@Check
注解的元注解@Retention
参数设置为RetentionPolicy.RUNTIME
。
@Retention
:用于修饰注解,用于指定修饰的那个注解的生命周期,@Rentention
包含一个RetentionPolicy
枚举类型的成员变量,使用@Rentention
时必须为该value
成员变量指定值。
RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释,在.class
文件中不会保留注解信息;RetentionPolicy.CLASS
:在.class
文件中有效(即class
保留),保留在.class
文件中,但是当运行Java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解。如果注解没有加Retention
元注解,那么相当于默认的注解就是这种状态;RetentionPolicy.RUNTIME
:在运行时有效(即运行时保留),当运行 Java程序时,JVM会保留注释,加载在内存中了,那么程序可以通过反射获取该注释。
尝试将@Retention
元注解的参数依次设置为RetentionPolicy.SOURCE
、RetentionPolicy.CLASS
,再次运行,观察日志文件,均未能记录日志信息,说明**@Check
注解在主程序运行阶段并未生效,只有将其设置为RetentionPolicy.RUNTIME
,注解才生效,才会记录日志信息**。
@Retention
元注解的参数设置为RetentionPolicy.SOURCE
输出的日志文件:
@java.lang.annotation.Retention(SOURCE) 本次测试的类:annotation.testframe.Calculators 本次测试一共出现 0 次异常。 测试时间:2023-01-10 14:24:29 周二 下午 ==================================
@Retention
元注解的参数设置为RetentionPolicy.CLASS
输出的日志文件:
@java.lang.annotation.Retention(CLASS) 本次测试的类:annotation.testframe.Calculators 本次测试一共出现 0 次异常。 测试时间:2023-01-10 14:24:46 周二 下午 ==================================