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日志并进行多维度分析。
目录
相关文章
|
2月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
84 1
|
2月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
14天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
34 3
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
2月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
46 1
|
6天前
|
Java
java代码优化:判断内聚到实体对象中和构造上下文对象传递参数
通过两个常见的java后端实例场景探讨代码优化,代码不是优化出来的,而是设计出来的,我们永远不可能有专门的时间去做代码优化,优化和设计在平时
24 15
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
92 34
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
50 4
|
2月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
81 2
|
2月前
|
前端开发 Java 数据库连接
你不可不知道的JAVA EE 框架有哪些?
本文介绍了框架的基本概念及其在编程领域的应用,强调了软件框架作为通用、可复用的软件环境的重要性。文章分析了早期Java EE开发中使用JSP+Servlet技术的弊端,包括可维护性差和代码重用性低等问题,并阐述了使用框架的优势,如提高开发效率、增强代码规范性和可维护性及提升软件性能。最后,文中详细描述了几种主流的Java EE框架,包括Spring、Spring MVC、MyBatis、Hibernate和Struts 2,这些框架通过提供强大的功能和支持,显著提升了Java EE应用的开发效率和稳定性。
158 1