java:Junit测试 反射 注解

本文详细介绍了Java中的Junit测试,包括如何定义和运行测试类,使用断言,以及@Before和@After注解。接着讨论了反射的概念,如何获取和操作类的成员变量、构造方法和成员方法。还提到了通过配置文件和反射实现动态调用。此外,文章阐述了注解的基本概念、用途,以及如何自定义注解,包括注解的元注解和属性。最后,通过示例展示了如何利用注解进行方法运行状态的检查。

Junit

1什么是Junit测试:是白盒测试的一种(白盒测试更高级一些)

                          

 2怎么使用Junit类:

        1先定义一个测试类:

                        

                                         

        代码:

                                :

         2运行测试类:

                          

         代码:注意@Test后要添加依赖(这里我们测试的是类中的减法)

                            

         3查看判定结果(断言):使用assertEquals方法,用断言结果和实际结果进行比较

                        

                结果为绿色就是成功

                                

                结果为红色就是失败

                                

         4补充知识点:@Before和@After,无论测试是否出现异响都会执行

                @Before修饰的方法会在所有测试方法之前执行,通常用来申请资源

                 @After修饰的方法会在所有测试方法之前执行,通常用来释放资源

反射

1什么是反射:反射是对类的属性(成员变量,构造方法,成员方法)的封装,每个部分通过类加载器分别封装成为fields,constructor,method这些class对象,方便我们从内存中对这些方法进行使用。反射是框架设计的灵魂。

String字符串所能调用的这些个方法,都是之前封装好,存在java的method类对象中的,当我们.出这些方法的时候,就启用了反射机制。

 

 2获取字节码Class对象的三种方式

        ​​​​​​​        

package com.bed.java.Junit.reflect;
//使用三种方式来导出class文件
public class ReflectDemo01 {
    public static void main(String[] args) throws Exception {
        //第一种方式:直接用class静态方法查看类对象
        Class aClass = Class.forName("com.bed.java.Junit.reflect.Person");
        System.out.println(aClass);
        //第二种方式,通过类名导出class对象
        Class aClass1 = Person.class;
        System.out.println(aClass1);
        //第三种方式通过对象来获取class对象
        Person p=new Person();
        Class aClass2 = p.getClass();
        System.out.println(aClass2);

        System.out.println(aClass==aClass1);
        System.out.println(aClass==aClass2);
    }
}

         最后两行为了证明,不论用哪种方式获取,同一个类的.class文件的地址是不变的

3class对象的功能

        ​​​​​​​        1.获取成员变量:获取所有成员变量.getfields

                                            获取指定成员变量.getfield("指定名称")

        Class<Person> personClass = Person.class;
        //获取Person对象的所有public变量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            /*遍历显示的结果只有a成员变量,因为a是public修饰的,
             除了public修饰的,其他作用域都不行*/
            System.out.println(field);
        }
        //获取Person对象的指定public变量
        Field a = personClass.getField("a");
        Person p=new Person();

                                                                                   注意:获取的只能是public修饰的成员变量

        那为了获取所有的成员变量,我们可以使用getDeclaredFields方法:

 //使用getDeclaredFields();方法来获取所有(public-private)成员变量
            Field[] declaredFields = personClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField);
            }
        }

         2获取成员变量的值,给成员变量赋值:get/set

//获取Person对象的指定public变量
        Field a = personClass.getField("a");
        Person p=new Person();
        //使用field获取成员变量的值:这个步骤与平常相反,是用成员变量a,
        // 传递a所在的类Person的对象p进去,来获取a成员变量在Person类里面的值
        Object o = a.get(p);
        System.out.println(o);//null
        //设置成员变量a的值
        a.set(p,"bed");
        System.out.println(p);//Person{name='null', age=0, a='bed'}

        如果该成员变量为私有成员变量,可以使用:getDeclaredField("name"),来获取任何指定

        成员变量,如果要对私有变量进行读取或者设定,要使用setAccessible(true)方法;

        这一步通常称为:忽略访问权限的安全检查(暴力反射)

//获取Person对象的指定private变量
Field name = personClass.getDeclaredField("name");//获取Person中的name私有成员变量
name.setAccessible(true);//忽略访问权限的安全检查(暴力反射)
Object o1 = name.get(p);
System.out.println(o1);

        3获取类的构造方法:获取空参构造对象,获取有参构造对象

package com.bed.java.Junit.reflect;

import java.lang.reflect.Constructor;

public class Demo03Constructor {
    public static void main(String[] args) throws Exception {
        Person p=new Person();
        Class pClass = p.getClass();//获取类对象
        //获取类的构造器(空参)
        Object person = pClass.newInstance();
        //获取类的构造器(有参):构造方法是按照构造参数来区分的,所以输入构造参数的类型来找构造方法
        //Person对象的有参构造方法一个是String类型的name 一个是int类型的age
        Constructor constructor = pClass.getConstructor(String.class, int.class);//获取构造器
        System.out.println(constructor);
        //使用构造器来创建对象,调用构造器的newInstance方法来创建Person对象
        Object person1 = constructor.newInstance("bed", 23);
        System.out.println(person1);
    }
}

                        getDeclaredConstructor的使用方法同Field对象相同,在此不做赘述

        4获取类的成员方法:getMethod(方法三要素)

package com.bed.java.Junit.reflect;

import java.lang.reflect.Method;

public class Demo04Method {
    public static void main(String[] args) throws Exception {
        Person p=new Person();
        Class pClass = p.getClass();
        //里面可以传入三个参数1方法名称2方法返回值3成员方法需要的参数
        //也就是方法的三要素
        Method eat = pClass.getMethod("eat", String.class);//这里传入方法名称和方法参数
        //参数传入Person对象和成员方法需要的参数,然后调用invoke执行方法
        eat.invoke(p, "汉堡");
    }
}

        注意:1、如果要查看所有成员方法getMethods,里面显示的结果会有一些隐藏方法,

                   也就是Object类的成员方法

                   2、getDeclaredMethods使用与Field相同。

                   3、获取Method对象后也可以用.getName再去返回获得成员方法名称(来回折腾)

练习:使用配置文件和反射,实现通过配置外部txt文件,来显示不同类中的不同方法

        1:首先先了解什么是配置文件,可以通俗的理解为,通过对一个文件(txt格式)内容的

              操纵,来实现运行结果的改变。注意:本案例中这个文件是用来控制类文件的,新建的

              时候要与.class文件平级,也就是和src在同一级。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

         2使用配置文件:在配置文件中我们要存储两个变量,1 类名称 2 类方法。存储的形式我们

           使用Properties(因为是在txt文件里)。注意:类名称要写绝对路径

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​

         3代码实现

package com.bed.java.Junit.reflect;

import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

//在不动源代码的前提下,实现两个类的方法由操作者自由切换
public class Demo05PeiZhiWenJian {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //创建一个Properties集合,用来接收类名称和类方法,这里我们把类基础属性作为键,类的真实名称为值
        Properties pro=new Properties();
        FileReader fr=new FileReader("D:\\study\\high level code\\day10-code test\\class.txt");//新建一个配置文件的读取路径
        pro.load(fr);//资源通过路径开始加载
        fr.close();//关闭流
        //通过txt文档中classname和classmethodname两个键,获取当前的对应值
        String className = pro.getProperty("className");
        String classmethodname = pro.getProperty("classmethodname");
        //使用反射来执行方法
        //1、创建一个类对象,参数传递上面获得的类名称
        Class<?> classname1 = Class.forName(className);
        //获取该类的成员方法,参数传递上面获得的类方法名称
        Method method = classname1.getMethod(classmethodname);
        //获取该类的无参构造方法
        Constructor<?> constructor = classname1.getConstructor();
        //通过获得的构造方法来创建一个对象
        Object o = constructor.newInstance();
        //执行方法
        method.invoke(o);
    }
}

 注解:

1、什么是注解:api里的说明,@overide @functionalinterface  这些都是注解。

2、注解的作用:

        ​​​​​​​        

         1里面所说的是自己制作一个index说明文档,如下图,先手动在代码里面写好注解

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​          

             然后用cmd  去运行拿出来的这个类AnnoDemo01

        ​​​​​​​        ​​​​​​​        

             出现如下结果

                                        

3 jdk中自定义的一些注解:

        1 、 

        2 、functionalinterface:判断是否为函数式接口 

        3、

                     举例,当你想用新的show02方法代替show01方法而又不想删除老方法时

                                                 

         4、

                 注意图中红圈部分就是java自动提示的一些警告,此时没有压制警告

        使用@SuppressWarnings压制警告,将此注解写在类名称前面,写上all,所有黄色条条消失

4注解的本质:本质上就是一种Annotation接口,接口里面的抽象方法就叫做注解的属性

5如何自定义注解:注解格式如下

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        注解的属性可以是如下种类:

                                                 

         如果我们顶定义了注解属性,接下来在使用注解的时候就要给注解赋值,需要注意如下几点

        ​​​​​​​        

         举例:同时使用  1基本数据类型2枚举3注解4字符组  作为属性的自定义注解

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

6注解的元注解:也就是解释注解的注解,通常写在注解接口上面,里面通常有四要素

                          

         1Target:大括号里面传递字符串组,用来限定该注解可用在哪里,如下图所示,说明:

                        该注解可以用在(TYPE类,FILED成员变量,METHOD成员方法)上

        ​​​​​​​        

         2Retention:解释当前注解保留在哪个阶段,通常我们都会定义在RUNITIME这个阶段。

        3Documented:让当前注解可以出现在api文档上

        4Inherited :子类继承父类注解

实例:用注解来替代配置文件。其中的关键点:如何获取注解接口中的属性(方法)

package com.bed.java.Junit.Annotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/*用注解来代替配置文件*/
//我们在注解中写好注解中需要的两参数的数据
@Peizhiwenjian(classname = "com.bed.java.Junit.Annotation.eat",methodname = "chifan")
public class AnnoReflect {
    public static void main(String[] args) throws Exception {
        //第一步,把注解中的参数数据拿到主程序中(解析注解)
            //1.1先获取当前类的字节码对象
            Class<AnnoReflect> annoReflectClass = AnnoReflect.class;
            //用字节码对象获取当前类的注解对象(参数传递你想要的那个注解)
            //注意这个anno 是一个接口的子类实现类
            Peizhiwenjian anno = annoReflectClass.getAnnotation(Peizhiwenjian.class);
            //用注解对象获取当前注解的属性(就是注解的抽象方法)
            //此时的返回值就是最上面注解的配置值
            String classname = anno.classname();//com.bed.java.Junit.Annotation.eat
            String methodname = anno.methodname();//chifan
            System.out.println(classname);

            //使用反射来执行方法
            //1、创建一个类对象,参数传递上面获得的类名称
            Class<?> classname1 = Class.forName(classname);
            //获取该类的成员方法,参数传递上面获得的类方法名称
            Method method = classname1.getMethod(methodname);
            //获取该类的无参构造方法
            Constructor<?> constructor = classname1.getConstructor();
            //通过获得的构造方法来创建一个对象
            Object o = constructor.newInstance();
            //执行方法
            method.invoke(o);
    }
}

实例 2  自定义注解来实现:类中共有4个成员方法  要分别查出4个成员方法是否都正常运行了

        (注意  还要新创建一个错误日志 用来接收错误信息)注意:错误日志的抓取就是在invoke

           关键字出现时,catch自动抓取的Exception可以自定义。后续的bug细致分析用

                get.class  get.massage  get.cause  get.simplename即可(不懂看视频吧)

测试类
package com.bed.java.Junit.Annotation.CalcTest;
//定义一个计算器类,里面有四个成员方法,检查这四个方法是否能正常执行
public class Calculator {
    @Check
    public void add(){
        int num=1/0;
        System.out.println("1+0="+(1+0));
    }
    @Check
    public void sub(){
        System.out.println("1-0="+(1-0));
    }
    @Check
    public void mult(){
        System.out.println("1*0="+1*0);
    }
    @Check
    public void div(){
        System.out.println("1/0="+1/0);
    }
    public void noPro(){
        System.out.println("no problem");
    }

}

注解接口
package com.bed.java.Junit.Annotation.CalcTest;

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 com.bed.java.Junit.Annotation.CalcTest;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CalcTest {
    public static void main(String[] args) throws IOException {
        //第一步,建立一个txt文档用来记录bug
        int num=0;//这个是用来记录bug出现的次数
        BufferedWriter bw=new BufferedWriter(new FileWriter("bug.txt"));
        //第二步,我们要把Calculator中的所有成员方法都找出来
        //判断该方法有没有Check注解
        //1、获取Calculator字节码文件对象
        Class<Calculator> calculatorClass = Calculator.class;
        //2、获取字节码文件的所有方法
        Method[] methods = calculatorClass.getMethods();
        //3、将获得的方法组来遍历
        for (Method method : methods) {
            //看看每一条是否有注解
            if (method.isAnnotationPresent(Check.class)){
                //如果有Check注解,就执行该方法,此时invoke会出现异常,这个异常就是运行时期的异常
                //我们使用trycatch来捕获异常,并记录到日志中
                try {
                    method.invoke(new Calculator());
                } catch (Exception e) {//捕获异常并记录到日志,此处是自定义的catchException
                    num++;//每有一个bug就+1
                    bw.write("出现bug的方法名称:"+method.getName());
                    bw.newLine();//换行
                    //获取异常的简短类名
                    bw.write("该异常名为:"+e.getCause().getClass().getSimpleName());
                    bw.newLine();//换行
                    bw.write("异常的原因:"+e.getCause().getMessage());
                    bw.newLine();//换行
                    bw.write("=====================");
                    bw.newLine();//换行
                }
            }
        }
        bw.write("本次共出现异常次数:"+num);
        bw.flush();
        bw.close();
    }
}

                日志结果:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值