Junit单元测试

  • 测试分类:

    1. 黑盒测试:不需要写代码,输入给定的值,看程序是否能够输出期望的值
    2. 白盒测试:需要写代码,关注程序具体的执行流程
  • Junit使用

    • 步骤

      1. 定义一个测试类(测试用例)

        • 建议:

          • 测试类名:被测试的类名Test
          • 包名:***.***.test
      2. 定义测试方法:可以独立运行

        • 建议:

          • 方法名:方法名test
          • 返回值:void
          • 参数列表:空参
      3. 给方法加@Test
      4. 导入Junit依赖环境
    • 判断结果:

      • 红色:失败
      • 绿色:成功
      • 一般我们会使用断言操作来处理结果

        • Assert.assertEquals(期望的结果,实际的结果)
    • 补充:

      • @Before

        • 修饰的方法会在测试方法之前被自动执行
      • @After

        • 修饰的方法会在测试方法执行之后被执行

Calculator

/**
 * 计算器类
 */
public class Calculator {

  /**
   * 加法
   *
   * @param a
   * @param b
   * @return
   */
  public int add(int a, int b) {
    return a + b;
  }

  /**
   * 减法
   *
   * @param a
   * @param b
   * @return
   */
  public int sub(int a, int b) {
    return a - b;
  }
}

CalculatorTest

/**
 * @author YQHP-YuKi
 * @create 2021-12-01 11:06
 */
public class CalculatorTest {

  /**
   * 初始化方法,用于资源申请,所有测试方法在执行之前都会先执行该方法
   */
  @Before
  public void init() {
    System.out.println("我先被执行了");
  }

  /**
   * 释放资源的方法,在所有测试方法执行完之后,都会自动执行该方法
   */
  @After
  public void close() {
    System.out.println("最后我被执行了");
  }

  /**
   * 测试add方法
   */
  @Test
  public void addTest() {
    //1.创建计算器对象
    Calculator c = new Calculator();
    //2.调用add方法
    int result = c.add(1, 3);
    System.out.println(result);
    //3.断言,相当于我断定这个结果为多少
    Assert.assertEquals(4, result);
  }

  /**
   * 测试减法
   */
  @Test
  public void subTest() {
    //1.创建计算器对象
    Calculator c = new Calculator();
    int result = c.sub(3, 7);
    System.out.println(result);
    Assert.assertEquals(0, result);
  }
}

反射:框架设计的灵魂

  • 框架:半成品软件,可以在框架的基础上进行软件开发,简化编码
  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制

    • 好处:

      1. 可以在程序运行过程中,操作这些对象
      2. 可以解耦,提高程序的可扩展性
  • 获取Class对象的方式:

    1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象

      • 多用于配置文件,将类名定义在配置文件中,读取文件,加载类
    2. 类名.class:通过类名的属性class获取

      • 多用于参数的传递
    3. 对象.getClass():getClass()方法在Object类中定义着

      • 多用于对象的获取字节码的方式
    • 结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个

/**
   * 三种不同情况获取Class对象
   */
  public static void main(String[] args) throws ClassNotFoundException {
    //1.Class.forName("全类名")
    Class cls1 = Class.forName("domain.Person");
    System.out.println(cls1);
    //2.类名.class
    Class<Person> cls2 = Person.class;
    System.out.println(cls2);
    //3.对象.getClass()
    Person p = new Person();
    Class cls3 = p.getClass();
    System.out.println(cls3);
    //比较这三个对象是否相等
    //true
    //true
    System.out.println(cls1 == cls2);
    System.out.println(cls1 == cls3);
  }
  • Class对象功能

    • 获取功能:

      1. 获取成员变量们

        • Field[] getFields():获取所有public修饰的成员变量
        • Field getField(String name):获取指定名称的public修饰的成员变量
        • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
        • Field getDeclaredField(String name)
      2. 获取构造方法们

        • Constructor<?>[] getConstructors()
        • Constructor<T> getConstructor(Class<?>... parameterTypes)
        • Constructor<?>[] getDeclaredConstructors()
        • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
      3. 获取成员方法们

        • Method[] getMethods()
        • Method getMethod(String name, Class<?>... parameterTypes)
        • Method[] getDeclaredMethods()
        • Method getDeclaredMethod(String name, Class<?>... parameterTypes)
      4. 获取类名

        • String getName()
    • Field:成员变量

      • 操作:

        1. 设置值

          • set(Object obj, Object value)
        2. 获取值

          • get(Object obj)
        3. 忽略访问权限修饰符的安全检测

          • setAccessible(boolean flag):暴力反射
    • Constructor:构造方法

      • 创建对象:

        • T newInstance(Object ... initargs)
        • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
    • Method:方法对象

      • 执行方法:

        • Object invoke(Object obj, Object... args)
      • 获取方法名称

        • String getName():获取方法名

获取成员变量

public static void main(String[] args) throws Exception {
    //获取Person的Class对象
    Class<Person> personClass = Person.class;
    //1.获取成员变量们
    Field[] fields = personClass.getFields();
    for (Field field : fields) {
      //public java.lang.String domain.Person.a
      System.out.println(field);
    }
    Field a = personClass.getField("a");
    //获取成员变量a的值
    Person p = new Person();
    Object value = a.get(p);
    //null
    System.out.println(value);
    //设置a的值
    a.set(p, "YuKi");
    //Person{name='null', age=0, a='YuKi', b='null', c='null', d='null'}
    System.out.println(p);
    System.out.println("----------");
    Field[] declaredFields = personClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
      System.out.println(declaredField);
    }
    Field d = personClass.getDeclaredField("d");
    //这里直接访问private的成员变量会出现警报:java.lang.IllegalAccessException
    //所以我们需要忽略访问权限修饰符的安全检查,我们也称之为暴力反射
    d.setAccessible(true);
    Object value2 = d.get(p);
    System.out.println(value2);
  }

获取构造方法

public static void main(String[] args) throws Exception {
    //获取Person的Class对象
    Class<Person> personClass = Person.class;
    Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
    //public domain.Person(java.lang.String,int)
    System.out.println(constructor);
    //创建对象
    Person person = constructor.newInstance("YuKi", 20);
    //Person{name='YuKi', age=20, a='null', b='null', c='null', d='null'}
    System.out.println(person);
    System.out.println("-----------------");
    Constructor constructor1 = personClass.getConstructor();
    //public domain.Person()
    System.out.println(constructor1);
    //创建对象
    Object person1 = constructor1.newInstance();
    //Person{name='null', age=0, a='null', b='null', c='null', d='null'}
    System.out.println(person1);
    System.out.println("----------------");
    Object o = personClass.newInstance();
    //Person{name='null', age=0, a='null', b='null', c='null', d='null'}
    System.out.println(o);
  }

获取成员方法

public static void main(String[] args) throws Exception {
    /**
     * 获取成员方法们*/
    //获取指定名称的方法
    Class<Person> personClass = Person.class;
    Method eat_method = personClass.getMethod("eat");
    Person p = new Person();
    //执行方法eat...
    eat_method.invoke(p);
    Method eat_method2 = personClass.getMethod("eat", String.class);
    eat_method2.invoke(p, "meat");
    System.out.println("---------------");
    //获取所有public修饰的方法
    Method[] methods = personClass.getMethods();
    for (Method method : methods) {
      System.out.println(method);
      String name = method.getName();
      System.out.println(name);
    }
    //获取类名
    String className = personClass.getName();
    System.out.println(className);
  }
  • 案例:

    • 需求:写一个"框架",不能改变该类的任意代码,可以帮我们创建任意类的对象,并且执行其中任意方法

      • 实现:

        1. 配置文件
        2. 反射
      • 步骤:

        1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
        2. 在程序中加载读取配置文件
        3. 使用反射技术来加载类文件进内存
        4. 创建对象
        5. 执行方法

代码

public static void main(String[] args) throws Exception {
    //可以创建任意类的对象,可以执行任意方法
    //1.创建Properties对象
    Properties pro = new Properties();
    //2.获取class目录下的配置文件
    InputStream is = ReflectTest.class.getClassLoader()
        .getResourceAsStream("pro.properties");
    //3.加载内存文件,转换为一个集合
    pro.load(is);
    //4.获取配置文件中定义的数据
    String className = pro.getProperty("className");
    String methodName = pro.getProperty("methodName");
    //4.加载该内进内存
    Class<?> cls = Class.forName(className);
    //4.创建对象
    Object obj = cls.newInstance();
    Method mth = cls.getMethod(methodName);
    mth.invoke(obj);
  }

配置文件

className=domain.Person
methodName=eat

注解

  • 概念:说明程序的,给计算机看的
  • 注释:用文字描述程序的,给程序员看的
  • 定义:注解(Annotation),也叫元数据,一种代码级别的说明,它是JDK1.5以及以后版本引入的一个特征,与类,接口,枚举是在同一个层次,它可以声明在包,类,字段,方法,局部变量,方法参数等的前面,用来对这些元素进行说明,注释
  • 概念描述:

    • JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称
  • 作用分类:

    1. 编写文档:通过代码里面标识的注解生成doc文档[生成doc文档]
    2. 代码分析:通过代码里标识的注解对代码进行分析[使用反射]
    3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查[Override]
  • JDK中预定义的一些注解

    • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容表示已经过时
    • @SuppressWarnings:禁止警告

      • 一般传递参数all

//类里面所有警报全部消失了
@SuppressWarnings("all")
public class Demo2 {

  @Override
  public String toString() {
    return super.toString();
  }

  @Deprecated
  public void show1() {
    //有缺陷
  }

  public void show2() {
    //替换show1方法
  }

  public void demo() {
    show1();
    //Date方法中就有许许多多过时的方法
    Date date = new Date();
  }
}
  • 自定义注解

    • 格式:

    元注解

    public @interface注解名称{}

    • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口

      • 例:public interface TestAnno extends java.lang.annotation.Annotation { }
    • 属性:接口中可以定义的成员方法

      • 要求:

        1. 属性的返回值类型有下列取值

          1. 基本数据类型
          2. String
          3. 枚举
          4. 注解
          5. 以上类型的数组
        2. 定义了属性,在使用时需要给属性赋值

          1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
          2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可,例如@SuppressWarnings里面就定义了一个value
          3. 数组赋值时,值使用{}包裹,例:{"abc", "def"},如果数组中只有一个值,则{}可以省略,例:"abc"

public @interface TestAnno {

  int value();

  String show2() default "YuKi";

  //枚举类型
  Person per();

  //注解类型
  TestAnno2 anno2();

  String[] strs();
}
@TestAnno(value = 10, per = Person.p1, anno2 = @TestAnno2, strs = {"abc", "def"})
public class Worker {

}
  • 元注解:用于描述注解的注解

    • @Target:描述注解能够作用的位置

      • ElementType取值:

        • TYPE:可以作用于类上
        • METHOD:可以作用于方法上
        • FIELD:可以作用于成员变量上
    • @Retention:描述注解被保留的阶段

      • @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
    • @Documented:描述注解是否被抽取到api文档中
    • @Inherited:描述注解是否被子类继承

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestAnno3 {

}
  • 在程序中使用(解析)注解:获取注解中定义的属性值

    1. 获取注解定义的位置的对象(Class,Method,Field)
    2. 获取指定的注解
    3. 调用注解中的抽象方法获取配置的属性值

例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoTest {
  /**
   * 描述需要执行的类名与方法名
   */
  String className();

  String methodName();
}
@DemoTest(className = "domain.Person", methodName = "eat")
public class Demo3 {

  public static void main(String[] args) throws Exception {
    //前提:不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法
    //1.解析注解
    //1.1获取该类的字节码文件对象
    Class<Demo3> demo3Class = Demo3.class;
    //2.获取上边的注解对象
    //其实就是在内存中生成了一个该注解接口的子类实现对象
    DemoTest annotation = demo3Class.getAnnotation(DemoTest.class);
    //3.调用注解对象中定义的抽象方法,获取返回值
    String className = annotation.className();
    String methodName = annotation.methodName();
    System.out.println(className);
    System.out.println(methodName);
    Class<?> name = Class.forName(className);
    Object obj = name.newInstance();
    Method mth = name.getMethod(methodName);
    mth.invoke(obj);
  }
}

:简单的测试框架 当主方法执行后,会自动去执行被检查的所有方法(加了@Check注解的方法) 判断方法是否有异常,并记录到文件中

/**
 * 简单的测试框架 当主方法执行后,会自动去执行被检查的所有方法(加了@Check注解的方法) 判断方法是否有异常,并记录到文件中
 */
public class TestCheck {

  public static void main(String[] args) throws IOException {
    //1.创建计算器对象
    Calculator c = new Calculator();
    //2.获取字节码文件对象
    Class cls = c.getClass();
    //3.获取所有的方法
    Method[] methods = cls.getMethods();
    //出现异常的次数
    int number = 0;
    BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
    for (Method method : methods) {
      //4.判断方法上是否有@Check注解
      if (method.isAnnotationPresent(Check.class)) {
        //5.有注解则执行
        try {
          method.invoke(c);
        } catch (Exception e) {
          //6.捕获异常
          //7.记录文件信息
          number++;
          bw.write(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("本次测试一共出现:" + number + "次异常");
    bw.flush();
    bw.close();
  }
}
/**
 * 计算器类
 */
public class Calculator {

  /**
   * 加法
   */
  @Check
  public void add() {
    //这里有一个空指针异常
    String str = null;
    str.toString();
    System.out.println("1+0=" + (1 + 0));
  }

  /**
   * 减法
   */
  @Check
  public void sub() {
    System.out.println("1-0=" + (1 - 0));
  }

  /**
   * 乘法
   */
  @Check
  public void mul() {
    System.out.println("1*0=" + (1 * 0));
  }

  /**
   * 除法
   */
  @Check
  public void div() {
    System.out.println("1/0=" + (1 / 0));
  }

  public void show() {
    System.out.println("Never bug...");
  }
}
  • 小结

    1. 以后大多数时候,我们会使用注解,而不是自定义注解
    2. 注解给谁用?

      1. 编译器
      2. 给解析程序用
    3. 注解不是程序的一部分,可以理解为注解就是一个标签
Last modification:December 14, 2021
If you think my article is useful to you, please feel free to appreciate