day25_Junit测试丶反射丶注解

隐身守侯 提交于 2020-02-11 22:36:40

软件测试

查询百度百科:软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测试是一种实际输出与预期输出之间的审核或者比较过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。

测试的分类

  • 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
  • 白盒测试:需要写代码的。关注程序具体的执行流程。Junit测试属于白盒测试中的一种。

现在我们拿到一个需求,需要测试一下Junit包下的Calculator类代码编写是否正确。代码如下

package Junit;

/**
 * 计算器类
 */
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;
    }
}

按照以前的知识,我们需要在测试类中,创建Calculator类的对象,在调用Calculator类中编写的方法,查看方法的运行结果和我们期望的结果一样。但是这么做很多的缺点:如果我们要测试多个方法,必须把前面测试好的代码注释掉,以免造成影响。步骤麻烦等等。下面我们就是要Junit单元测试的知识来测试这个类。

步骤如下

 1:创建一个包,存放测试用例。包名建议以.text结尾,并且与要测试的代码存放的包平级。

2:在MyTest包中,创建类(测试用例)。建议类名以Test结尾

 

3:在测试用例中定义测试方法,建议为test要测试的方法名称,例如要测试add方法,测试方法名称可以为testAdd()。测试方法建议空参和void返回值。

 4:给测试方法加@Test,导入junit依赖环境

 

 

代码完成之后,方法代码如下

package MyTest;

import org.junit.Test;

public class CalculatorTest {
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){

    }
}

5:在测试用例中,创建对象,调用要测试的方法进行测试

package MyTest;

import Junit.Calculator;
import org.junit.Test;

public class CalculatorTest {
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){
        //创建对象
        Calculator c = new Calculator();
        //调用方法
        int result = c.add(1, 3);
    }
}

6:使用会使用断言操作来处理结果, Assert.assertEquals(期望的结果,运算的结果);

package MyTest;

import Junit.Calculator;
import org.junit.Assert;
import org.junit.Test;

public class CalculatorTest {
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){
        //创建对象
        Calculator c = new Calculator();
        //调用方法
        int result = c.add(1, 3);
        //断言操作
        Assert.assertEquals(4,result);
    }
}

7:查看下面,如果是绿色的则代码要测试的内容没有问题,红色则说明代码有错误

常用的注解

@Before

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

@After

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

代码举例

package MyTest;

import Junit.Calculator;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

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

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


    /**
     * 测试add方法
     */
    @Test
    public void testAdd() {
        //1.创建计算器对象
        System.out.println("testAdd...");
        Calculator c = new Calculator();
        //2.调用add方法
        int result = c.add(1, 2);
        //System.out.println(result);

        //3.断言  我断言这个结果是3
        Assert.assertEquals(3, result);

    }

    @Test
    public void testSub() {
        //1.创建计算器对象
        Calculator c = new Calculator();
        int result = c.sub(1, 2);
        System.out.println("testSub....");
        Assert.assertEquals(-1, result);
    }
}

代码执行之后的代码

反射

框架

半成品软件。可以在框架的基础上进行软件开发,简化编码 。反射:框架设计的灵魂

反射的概念

将类的各个组成部分封装为其他对象,这就是反射机制

好处

  • 可以在程序运行过程中,操作这些对象。
  • 可以解耦,提高程序的可扩展性。

Java代码在计算机中经历的阶段

阶段1:Source源代码阶段

我们编写好代码,经过Javac编译生成字节码文件。字节码文件中保存了Java的成员变量丶构造方法丶成员方法等等等。他分别对应着Java代码中的成员变量丶构造方法,成员方法等等..如下图所示

阶段2:Class类对象阶段

我们通过类加载器(ClassLoader对象)把字节码文件加载进入内存中去,此时就进入Java代码的第二个阶段。在Java中"万物皆对象",因此有一个对象专门描述进入内存中的字节码文件,这个对象的名字是Class类对象。他是用来描述所有字节码文件共同的特征和行为

阶段3 Runtime运行时阶段

当我们通过类对象的一些行为就创建真正的对象,此时就是进入到了运行时阶段

 

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

如果Java代码处于阶段一的时候,我们可以使用方式一获取Class对象

方式1:

  • Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。 多用于配置文件,将类名定义在配置文件中。读取文件,加载类

如果Java代码处于阶段二,我们除了使用方式1获取字节码Class对象还可以使用方式2获取

方式2

  • 类名.class:通过类名的属性class获取   多用于参数的传递

如果Java代码处于阶段三,我们除了使用方式1丶2获取还可以使用方式3获取Class对象

方式3

  • 对象.getClass():getClass()方法在Object类中定义着。  多用于对象的获取字节码的方式

举例:

定义Person类

package Demo;

public class Person {
    private String name;
    private  int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

定义测试类

package reflect;

import Demo.Person;

public class DemoReflect {
    public static void main(String[] args) throws Exception {
        /*
        方式1:Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
        全类名就是包名.类名全路径
         */
        Class personClass1 = Class.forName("Demo.Person");
        System.out.println(personClass1);//class Demo.Person

        //方式2:类名.class:通过类名的属性class获取 多用于参数的传递
        Class personClass2 = Person.class;
        System.out.println(personClass2);//class Demo.Person

        // 方式3:对象.getClass():getClass()方法在Object类中定义着。 多用于对象的获取字节码的方式
        Person p = new Person();
        Class personClass3 = p.getClass();
        System.out.println(personClass3);//class Demo.Person

        // == 比较对象
        System.out.println(personClass1 == personClass2 && personClass1 == personClass3);//true

        // 因此我们知道不论通过哪一种方式获取的Class对象都是同一个。
    }
}

结论:

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

Class对象的功能(方法)

获取成员变量的功能

方法中带有Declared单词的就是私有的,需要暴力反射

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

操作获取到的成员变量

设置值

  • void set(Object obj, Object value)

获取值

  • get(Object obj)

如果要操作私有的成员变量则必须使用暴力反射

  • setAccessible(true):暴力反射。忽略访问权限修饰符的安全检查

举例:

定义TestField类

package Demo;

public class TestField {
    private String a;
    String b;
    protected String c;
    public String d;

    @Override
    public String toString() {
        return "TestField{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }
}

定义测试类

package reflect;


import Demo.TestField;

import java.lang.reflect.Field;

public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //方式1 获取class对象
         /*
        方式1:Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
        全类名就是包名.类名全路径
         */
        Class DemoClass = Class.forName("Demo.TestField");

        //Field[] getFields() :获取所有public修饰的成员变量
        Field[] fields = DemoClass.getFields();
        //遍历数组
        for (Field field : fields) {
            System.out.println(field);//public java.lang.String Demo.Test.d
        }

        //Field getField(String name) 获取指定名称的 public修饰的成员变量
        Field d = DemoClass.getField("d");
        System.out.println(d);//public java.lang.String Demo.Test.d

        //我们拿到了成员变量之后可以获取其值
        TestField t = new TestField();
        Object o = d.get(t);// get 参数为获取哪个对象的成员变量值
        System.out.println(o);//null

        //我们也可以设置值,set的第一个参数是为哪个对象,第二个参数设置什么值
        d.set(t, "张三");
        System.out.println(t);//TestField{a='null', b='null', c='null', d='张三'}

        //Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
        Field[] declaredFields = DemoClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
            /*
            遍历后的结果
            ----------------------------------
            private java.lang.String Demo.Test.a
            java.lang.String Demo.Test.b
            protected java.lang.String Demo.Test.c
            public java.lang.String Demo.Test.d
             */
        }

        //Field getDeclaredField(String name)获取指定名称的成员变量,不考虑修饰符
        Field a = DemoClass.getDeclaredField("a");
        System.out.println(a);//private java.lang.String Demo.Test.a
        //要想获取和设置私有的必须暴力反射
        a.setAccessible(true);
        //获取私有变量
        System.out.println(a.get(t));//null
        //设置私有变量的值
        a.set(t, "李四");
        System.out.println(t);//TestField{a='李四', b='null', c='null', d='张三'}

    }
}

获取构造方法的功能

构造方法的名字都是一样,我们通过参数来区分到底使用哪个构造方法,方法中带有Declared单词的就是私有的,需要暴力反射

  • Constructor<?>[ ] getConstructors()  获取所有的被public修饰的构造方法
  • Constructor<T> getConstructor(class<?>... parameterTypes) 获取指定参数被public修饰的构造方法
  • Constructor<T> getDeclaredConstructor(class<?>... parameterTypes)
  • Constructor<?>[] getDeclaredConstructors()

使用获取到的构造方法,创建对象

  • T newInstance(Object... initargs)  

举例

定义TestConstructor 类

package Demo;

public class TestConstructor {
    private String name;
    private int age;

    public TestConstructor() {
    }

    public TestConstructor(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "TestConstructor{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

定义测试类

package reflect;

import Demo.TestConstructor;

import java.lang.reflect.Constructor;

public class Demo02Reflect {
    public static void main(String[] args) throws Exception {
        //方式2:类名.class:通过类名的属性class获取 多用于参数的传递
        Class testConstructorClass = TestConstructor.class;
        // 获取指定参数构造方法,
        Constructor c1 = testConstructorClass.getConstructor(String.class, int.class);
        //使用指定参数的构造方法创建对象
        Object o1 = c1.newInstance("早上", 18);
        System.out.println(o1);//TestConstructor{name='早上', age=18}

        //获取无参的构造方法
        Constructor c2 = testConstructorClass.getConstructor();
        //使用指定参数的构造方法创建对象
        Object o2 = c2.newInstance();
        System.out.println(o2);//TestConstructor{name='null', age=0}
        
        //不推荐下面这种方式创建空参对象
        System.out.println(testConstructorClass.newInstance());//TestConstructor{name='null', age=0}

    }
}

获取成员方法的功能:

方法中带有Declared单词的就是私有的,需要暴力反射

  • Method[] getMethods()
  • Method getMethod(String name, class<?>... parameterTypes)
  • Method[ ] getDeclaredMethods()
  • Method getDeclaredMethod(String name, class<?>... parameterTypes)

获取到方法,执行方法:

  • Object invoke(Object obj, Object... args): 传递实际对象,和方法的参数

获取方法名称:

  • String getName:获取方法名

自定义类

package reflect;


public class Person {
    private String name;
    private int age;

    public String a;
    protected String b;
    String c;
    private String d;


    public Person() {
    }

    public Person(String name, int 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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }


    public void eat() {
        System.out.println("eat...");
    }

    public void eat(String food) {
        System.out.println("eat..." + food);
    }
}

定义测试类

package reflect;


import java.lang.reflect.Method;

public class ReflectDemo4 {

    public static void main(String[] args) throws Exception {

        //0.获取Person的Class对象
        Class personClass = Person.class;

        //获取指定名称的方法,参数为方法名称eat
        Method eat_method = personClass.getMethod("eat");
        Person p = new Person();
        //执行方法,参数为哪个对象的方法
        eat_method.invoke(p);//eat...
        //区分方法,可以通过方法名称和参数列表来区分
        Method eat_method2 = personClass.getMethod("eat", String.class);
        //执行方法
        eat_method2.invoke(p, "饭");//eat...饭


        //获取所有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);//reflect.Person

    }


}

注解

定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

概念描述:

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

作用分类:

  • ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
  • ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  • ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

JDK中预定义的一些注解

@Override :检测被该注解标注的方法是否是继承自父类(接口)的

@Deprecated:该注解标注的内容,表示已过时

@SuppressWarnings:压制警告  一般传递参数all ,压制所有的警告

压制类中所有的警告信息

自定义注解

格式:
元注解
public @interface 注解名称{
  属性列表;
}

什么是元注解

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

  • 描述注解能够作用的位置
  • 描述注解被保留的阶段
  • 描述注解是否被抽取到api文档中
  • 描述注解是否被子类继承

本质:

  • 注解本质上就是一个接口,该接口默认继承Annotation接口 。注解中的属性就是接口中的抽象方法

要求:

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

  • 基本数据类型
  • String
  • 枚举
  • 注解
  • 以上类型的数组

定义了属性(抽象方法),在使用时需要给属性赋值

  • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
  • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
  • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

小结:

  • 以后大多数时候,我们会使用注解,而不是自定义注解

注解给谁用?

  • 编译器
  • 给解析程序用
  • 注解不是程序的一部分,可以理解为注解就是一个标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!