Java自定义测试框架测试对象中相应的成员方法

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java自定义测试框架测试对象中相应的成员方法


目标:已知有一个Calculators类,该类中包含加减乘除等运算方法。

  1. 编写Check注解对需要进行测试的成员方法进行标记;
  2. 设计一个框架对该类中的任意public修饰的、带有@Check注解标记的成员方法进行测试;
  3. 捕捉该类中各个成员方法的异常并记录到日志文件中;
  4. 要求后续如果要测试其他类中相应的成员方法,无需修改代码,而仅修改配置文件即可直接调用该测试框架。

首先定义一个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.SOURCERetentionPolicy.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 周二 下午
==================================


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1天前
|
IDE Java 机器人
如何在Java中进行单元测试:JUnit 5的使用指南
如何在Java中进行单元测试:JUnit 5的使用指南
|
1天前
|
Java
java线程之分支合并框架
java线程之分支合并框架
10 1
|
1天前
|
Java 测试技术 数据库
【单文件版本】java SpringBoot 切换不同的运行环境(生产环境、开发环境、测试环境)SpringBoot配置多个不同运营环境
【单文件版本】java SpringBoot 切换不同的运行环境(生产环境、开发环境、测试环境)SpringBoot配置多个不同运营环境
8 0
|
1天前
|
存储 消息中间件 算法
Java中的集合框架详解:List、Set、Map的使用场景
Java中的集合框架详解:List、Set、Map的使用场景
|
1天前
|
Java 机器人 编译器
如何在Java中使用注解:自定义注解的实现
如何在Java中使用注解:自定义注解的实现
|
1天前
|
Java 测试技术 数据库
java SpringBoot 切换不同的运行环境(生产环境、开发环境、测试环境)SpringBoot配置多个不同运营环境【多文件版本】
java SpringBoot 切换不同的运行环境(生产环境、开发环境、测试环境)SpringBoot配置多个不同运营环境【多文件版本】
6 0
|
1天前
|
存储 安全 Java
如何在Java中实现自定义数据结构:从头开始
如何在Java中实现自定义数据结构:从头开始
|
1天前
|
Java 开发者
java开发者工具IDEA自定义设置主题/字体/字号大小
java开发者工具IDEA自定义设置主题/字体/字号大小
7 0
|
1天前
|
存储 算法 Java
Java中的集合框架使用技巧
Java中的集合框架使用技巧
|
1天前
|
IDE Java 机器人
如何在Java中进行单元测试?
如何在Java中进行单元测试?