05.单元测试、注解和反射

发布时间 2023-04-16 16:25:54作者: 哈哈嗨

1、单元测试

  1. 什么是单元测试?单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。

  2. 目前测试方法是怎么进行的,存在什么问题?

    • 只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响

    • 无法得到测试的结果报告,需要程序员自己去观察测试是否成功。

    • 无法实现自动化测试。

  3. Junit单元测试框架,JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。此外,几乎所有的IDE工具都集成了IUnit,这样我们就可以直接在IDE中编写并运行IUnit测试,JUnit目前最新版本是5。

  4. JUnit优点

    • JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。

    • Junit可以生成全部方法的测试报告。

    • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。

  5. Junit快速入门

    • 需求:使用单元测试进行业务方法预期结果、正确性测试的快速入门

    • 分析:

      • 将Unit的iar包导入到项目中,IDEA通常整合好了Junit框架,一般不需要导入。如果IDEA没有整合好,需要自己手工导入如下2个IUnit的iar包到模块

      • 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。

      • 在测试方法上使用@Test注解,标注该方法是一个测试方法

      • 在测试方法中完成被测试方法的预期正确性测试。选中测试方法,选择“JUnit运行”,如果测试良好则是绿色;如果测试失败,则是红色

    • 使用示例:

      //有待测试方法的类
      public class TestDemo {
          //返回 1 + 2.....+100的值。
          public int sum(){
              int result = 0;
              for (int i = 0; i < 100; i++) {
                  result += i;
              }
              return result;
          }
      
          //一个会出问题的方法。
          public void div(){
              System.out.println(5/0);
          }
      }
      //测试
      public class Test {
      
          @org.junit.Test
          public void  testSum(){
              TestDemo demo = new TestDemo();
              int sum = demo.sum();
              //通常会使用Assert.assertEquals()方法来测试有返回值的方法。
              //第一个参数表示返回值有误的提示信息。
              //第二个参数表示期待的返回值
              //第三个表示实际上返回的值。
              Assert.assertEquals("返回值有误",5050,sum);
          }
      
          @org.junit.Test
          public void testDiv(){
              TestDemo demo = new TestDemo();
              demo.div();
          }
      }
      
  6. Junit常用方法

    • @Test:测试方法

    • @Before:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。

    • @After:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。

    • @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。

    • @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。

    • 开始执行的方法,用于初始化资源。执行完之后的方法,用于释放资源。

2、反射

  1. 反射概述

    • 反射是指对于任何一个Class类,在"运行的时候"都可以直接得到这这个类全部成分。

    • 反在运行时,可以直接得到这个类的构造器对象:Constructor

    • 反在运行时,可以直接得到这个类的成员变量对象:Field

    • 反在运行时,可以直接得到这个类的成员方法对象:Method

    • 反这种运行时动态获取类信息以及动态调用类中成分的能力称为java语言的反射机制。

  2. 反射的关键:反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。

  3. 获取反射类对象

    • 获取反射类对象一般有三种方式

    • 使用示例:三个打印结果都为class shh.People,也就是说一个类只有一个class对象。

      public static void main(String[] args) throws Exception {
      
          //方式一:Class.forName(全类名),参数为包名.包名....类名,完整的类名
          Class<?> class01 = Class.forName("shh.People");
          System.out.println(class01);
      
          //方式二:类名.class
          Class<People> class02 = People.class;
          System.out.println(class02);
      
          //方式三:对象.getClass(),getClass()方法为老祖宗Object对象的方法。
          People people = new People();
          Class<? extends People> class03 = people.getClass();
          System.out.println(class03);
      }
      
  4. 反射获取构造器对象

    • 使用反射可以获取构造器对象

    • Class类中用于获取构造器的方法

      • Constructor<?>[] getConstructors():返回所有构造器对象的数组(只能拿public的)

      • Constructor<?>[] getDeclaredConstructors():返回所有构造器对象的数组,存在就能拿到

      • Constructor getConstructor(Class<?>... parameterTypes):返回单个构造器对象(只能拿public的)

      • Constructor getDeclaredConstructor(Class<?>... parameterTypes):返回单个构造器对象,存在就能拿到

    • 示例

      public static void main(String[] args) throws Exception{
      
          //获取People类的class对象。
          Class<People> peopleClass = People.class;
      
          //getConstructors(),获取所有的构造器对象,private方法无法获取。
          Constructor<?>[] constructors = peopleClass.getConstructors();
          
          //getDeclaredConstructors(),获取所有的构造器对象,私有方法也可以获取到。
          Constructor<?>[] declaredConstructors = peopleClass.getDeclaredConstructors();
          
          //getConstructor(类型.class),获取指定参数类型的一个构造器。私有方法无法获取
          Constructor<People> constructor = peopleClass.getConstructor(String.class);
          
          //getDeclaredConstructor(类型.class),获取指定参数类型的一个构造器,私有方法也可以获取到,
          Constructor<People> declaredConstructor = peopleClass.getDeclaredConstructor(String.class);
      
      }
      
  5. 使用构造器对象创建类对象

    • Constructor类中用于创建对象的方法

      • T newlnstance(Object... initargs):根据指定的构造器创建对象

      • public void setAccessible(boolean flag):设置为true表示取消访问检查,进行暴力反射,也就是说私有构造器对象也可以创建对象。

    • 示例

      public class People {
      
          private String name;
          private int age;
          //私有构造器
          private People(){}    
          public People(String name) {
              this.name = name;
          }
          public People(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
      //测试
      public static void main(String[] args) throws Exception{
      
          //获取People类的class对象。
          Class<People> peopleClass = People.class;
      
          //获取空参数的私有构造器
          Constructor<People> privateConstructor = peopleClass.getDeclaredConstructor();
          //People people = privateConstructor.newInstance(); 执行失败,私有构造器不能创建对象。
          privateConstructor.setAccessible(true);  //暴力反射,让私有构造器也可以创建对象
          People people = privateConstructor.newInstance(); //执行成功
      
          //获取两个参数的构造器
          Constructor<People> constructor = peopleClass.getConstructor(String.class, Integer.class);
          People people1 = constructor.newInstance("小明", 10);
      
      }
      
  6. 反射获取成员变量对象

    • 使用反射可以获取成员变量对象

    • 获取成员变量对象的方法

      • Field getField(String name):根据成员变量名获得对应Field对象,只能获得public修饰的成员变量

      • FField getDeclaredField(String name):根据成员变量名获得对应Field对象,只要申明了就可以得到(可以获取任何访问权限的成员变量)

      • Field[] getFields():获得所有的成员变量对应的Field对象,只能获得public的成员变量

      • Field[] getDeclaredFields():获得所有的成员变量对应的Field对象,只要申明了就可以得到

    • Field类中操作成员变量的方法,给成员变量赋值和取值

      • void set(Object obj, object value):给对象注入某个成员变量数据

      • Object get(Object obj):获取对象的成员变量的值。

      • void setAccessible(true):暴力反射,设置为可以直接访问私有类型的属性。

      • Class getType0):获取属性的类型,返回Class对象。

      • String getName():获取属性的名称。

    • 使用示例

      public class People {
      
          private String name;
          public int age;
      
          public People(String name, Integer age) {
              this.name = name;
              this.age = age;
          }
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public int getAge() {
              return age;
          }
          public void setAge(int age) {
              this.age = age;
          }
      }
      //测试
      public static void main(String[] args) throws Exception{
      
          //获取People类的class对象。
          Class<People> peopleClass = People.class;
      
          //Field name = peopleClass.getField("name");//执行失败,getField不能获取私有成员变量
          //使用getField()获取public修饰的成员变量。
          Field age = peopleClass.getField("age");
          //使用getDeclaredField()获取私有成员变量。
          Field name = peopleClass.getDeclaredField("name");
      
          //使用set()方法修改变量内容。
          People people = new People("小明",10);
          //name.set(people,"小红"); 执行失败,私有的成员变量不允许修改变量内容
          name.setAccessible(true);
          name.set(people,"小红");  //先使用setAccessible暴力反射,就可以修改变量内容了。
      
          //获取某个对象的变量信息
          Class<?> type = age.getType(); //获取变量类型
          String name1 = age.getName(); //获取变量名
      }
      
  7. 反射获取成员方法对象

    • 使用反射可以获取成员方法对象

    • 获取成员方法对象的方法

      • Method[] getMethods():返回所有成员方法对象的数组(只能拿public的)

      • Method[] getDeclaredMethods():返回所有成员方法对象的数组,存在就能拿到

      • Method getMethod(String name,Class<?>... parameterTypes):返回单个成员方法对象(只能拿public的)

      • Method getDeclaredMethod(String name,Class<?>... parameterTypes):返回单个成员方法对象,存在就能拿到

    • 执行方法的方法:Object invoke(Object obj,Object... args)

      • 参数一:用obj对象调用该方法,指定要调用方法的对象

      • 参数二:调用方法的传递的参数(如果没有就不写)

      • 返回值:方法的返回值(如果没有就不写)

    • 使用示例

      public class People {
      
          private String name;
          public People(String name) {
              this.name = name;
          }
      
          //一个公共的方法,会吃饭,无返回值
          public void eat(String thing){
              System.out.println(this.name + "会吃"+thing);
          }
          //一个私有的方法,返回名字
          private String getName(){
              return this.name;
          }
      }
      //测试
      public static void main(String[] args) throws Exception {
          Class<People> p = People.class;
      
          //获取公共方法
          Method eat = p.getMethod("eat",String.class);
          //getDeclaredMethod可以 获取私有方法。
          //Method getName = p.getMethod("getName"); 执行失败,不能获取私有方法
          Method getName = p.getDeclaredMethod("getName");
      
          //invoke方法,用于执行某个对象的方法。
          People people = new People("小明");
          eat.invoke(people,"冰淇淋");
          //String invoke = (String)getName.invoke(people); 执行失败,私有方法不可被执行
          getName.setAccessible(true);
          //暴力反射之后,私有方法就可以被执行了
          String invoke = (String)getName.invoke(people);
          System.out.println(invoke);
      }
      
  8. 反射的应用

    • 反射的作用-绕过编译阶段为集合添加数据

      • 反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。

      • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了。所以就可以使用反射,来为集合添加数据

      • 演示

        public static void main(String[] args) throws Exception {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            //list.add("你好");   编译报错
            Class<? extends ArrayList> c = list.getClass(); //获取class对象
            Method add = c.getMethod("add", Object.class); //获取add方法对象
            add.invoke(list,"你好");   //执行成功
            System.out.println(list);  //输出:[1, 2, 你好]
        }
        
      • 实际上有另一种方式可以突破泛型的约束

        public static void main(String[] args) throws Exception {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            //list.add("你好");   编译报错
            ArrayList list2 = list;  //将list2指向list对象。
            list2.add("你好");     //此时list2对象是没有被泛型约束的
            System.out.println(list);  //输出:[1, 2, 你好]
        }
        
    • 反射的作用- 反射做通用框架

      • 需求:给你任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文
        件中去。

      • 分析

      • 代码实现:

        //使用反射获取对象信息并打印到文件上
        public static void getMeg(Object obj){
            PrintStream ps = null;
            try {
                //使用打印流,指定要打印到哪个文件中去。
                ps = new PrintStream(new FileOutputStream("C:\\Users\\86158\\Desktop\\test.txt",trued));
                Class<?> c = obj.getClass();
                String className = c.getName(); //获取类名
                ps.println("================="+className+"==============");
                Field[] fields = c.getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    Object value = field.get(obj);
                    ps.println(fieldName + "="+value);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                if(ps != null){
                    ps.close();
                }
            }
        }
        
        // 测试  
        public static void main(String[] args) throws Exception {
            getMeg(new Teacher("小红",465));
            getMeg(new Student("小明",45,'男',180,"王者荣耀"));
        }
        

        结果:

3、注解

  1. 注解概述

    • Java注解(Annotation)又称Java标注,是JDK50引入的一种注释机制。Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。

    • 注解的作用:对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定。例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行。

  2. 自定义注解

    • 定义注解的基本形式:default 默认值 可以省略。

    • 注意:

      • value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的。

      • default 默认值 可以省略。设置默认值之后的属性,在使用注解时,可以不为其赋值,直接使用默认值即可。若不设置默认值,则必须为其赋值,除了特殊属性value之外,赋值语句必须为“属性名=属性值”

    • 自定义注解示例:

      //自定义的注解
      public @interface MyAnnotation {
          public String value();
          //public 修饰符可以省略,因为默认就是public修饰
          String name() default "shh";
          //设置默认值之后,在使用注解时,可以省略该属性值的赋值过程
          public int age() default 18;
      }
      
      //使用自定义注解
      public class Main {
      
          //由于MyAnnotation注解中,除了value属性之外,其他的属性都有默认值
          //所以此处可以省略 “value=” 以及 其他属性值赋值的语句。
          @MyAnnotation("shh")
          public void aMethod01(){}
      
          //必须为所有属性值赋值,除非它有默认值。赋值语句必须为 “属性名=值”。
          @MyAnnotation(value = "shh",name = "sfaf",age = 16)
          public void aMethod02(){}
      }
      
  3. 元注解

    • 概述:元注解就是注解注解的注解,也就是对注解使用的注解。

    • 元注解有两个:

      • @Target:约束自定义注解只能在哪些地方使用

      • @Retention:申明注解的生命周期

    • @Target中可使用的值定义在 ElementType枚举类中,常用值如下

      • TYPE,类,接口

      • FIELD 成员变量

      • METHOD 成员方法

      • PARAMETER 方法参数

      • CONSTRUCTOR 构造器

      • LOCAL VARIABLE 局部变量

    • @Retention中可使用的值定义在 RetentionPolicy 枚举类中,常用值如下

      • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在

      • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值。

      • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

    • target使用示例

      //定义了一个注解,使用target元注解标注它只能被用于方法和成员变量中。
      @Target({ElementType.FIELD,ElementType.METHOD})
      public @interface MyAnnotation {
          public String value();
      }
      
      //测试
      @Target({ElementType.FIELD,ElementType.METHOD})
      //@MyAnnotation("shh") 报错,该注解不能注解类。
      public class Main {
          //注解成员变量、方法都没有异常。
          @MyAnnotation("shh")
          private String name;
          @MyAnnotation("shh")
          public void aMethod02(){}
      }
      
  4. 注解解析 (?)

    • 注解解析?注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

    • 与注解解析相关的接口

      • Annotation:注解的顶级接口,注解都是Annotation类型的对象

      • AnnotatedElement:该接口定义了与注解解析相关的解析方法

      • 所有的类成分ClassMethod,Field,Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力:

    • 解析注解的技巧

      • 注解在哪个成分上,我们就先拿哪个成分对象。

      • 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解

      • 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解

      • 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

    • 注解解析的案例

    • 注解解析案例实现

      //定义注解 book
      @Target({ElementType.METHOD,ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface Book {
          String value();//书名
          double price() default 100; //价格,默认值为100
          String[] authors();  //多位作者
      }
      
      //将注解book用于BookStore类上
      @Book(value ="《情深深雨濛濛》",price =999,authors ={"琼瑶","dlei"})
      class BookStore {
          @Book(value = "《三少爷的剑》", price = 3999, authors = {"古龙", "熊耀华"} )
          public void test() {}
      }
      
      //解析注解
      public static void main(String[] args) throws Exception{
      
          Class<BookStore> bc = BookStore.class;
          //解析类上的注解
          Book annotation = bc.getAnnotation(Book.class);
          System.out.println(annotation.value());
          System.out.println(annotation.price());
          System.out.println(Arrays.toString(annotation.authors()));
          //解析方法上的注解
          Method test = bc.getMethod("test");
          Book book = test.getAnnotation(Book.class);
          System.out.println(book.value());
          System.out.println(book.price());
          System.out.println(Arrays.toString(book.authors()));
      }