一篇文章学会Junit单元测试&反射&注解!

 

一、Junit单元测试

简介:JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。

什么是单元测试?

在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 程序单元是应用的最小可测试部件。简单来说,就是测试数据的稳定性是否达到程序的预期。

单元测试的重要性(好处)

  • 可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。
  • 启动后,自动化测试,并判断执行结果, 不需要人为的干预。
  • 只需要查看最后结果,就知道整个项目的方法接口是否通畅。
  • 每个单元测试用例相对独立,由Junit 启动,自动调用。不需要添加额外的调用语句。
  • 添加,删除,屏蔽测试方法,不影响其他的测试方法。 开源框架都对JUnit 有相应的支持。

黑盒测试与白盒测试

  1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
  2. 白盒测试:需要写代码的。关注程序具体的执行流程。(Junit单元测试属于白盒测试)

Junit使用

  1. 定义一个测试类(测试用例)
  2. 定义测试方法:可以独立运行
  3. 给方法加@Test注解
  4. 导入junit依赖环境

单元测试的编码规范

类名: 定义测试类,类名是由被测试类名Test构成。

包名: 定义的测试类需要放在xxx.xxx.xxx.test包中。

方法名: 测试方法的方法名有两种定义方式test测试方法测试方法

返回值: 因为我们的方法只是在类中测试,可以独立运行,所以不需要处理任何返回值,所以这里使用void

参数列表: 因为我们的方法是用来测试的,至于参数列表的传入是没有必要的。我们在测试的时候自行传入需要的参数测试即可。所以在此参数列表为

@Test注解: 测试是需要运行来完成的。如果我们只有一个main方法,显然在结构上还是需要我们去注释掉测试过的。解决此问题这里我们需要在测试方法上方加@Test注解来完成测试,只要是加该注解的方法,可以单独运行此方法来完成测试。

@Test注解jar包Junit4、5: @Test注解是需要我们导入jar包才能使用的。jar包有两个分别是:junit-4.13-rc-2和hamcrest-core-1.3。

断言assert

断言可以简单理解为判断。Junit所有的断言都包含在 Assert 类中。

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

断言方法 描述
assertNull(java.lang.Object object) 检查对象是否为空
assertNotNull(java.lang.Object object) 检查对象是否不为空
assertEquals(long expected, long actual) 检查long类型的值是否相等
assertEquals(double expected, double actual, double delta) 检查指定精度的double值是否相等
assertFalse(boolean condition) 检查条件是否为假
assertTrue(boolean condition) 检查条件是否为真
assertSame(java.lang.Object expected, java.lang.Object actual) 检查两个对象引用是否引用同一对象(即对象是否相等)
assertNotSame(java.lang.Object unexpected, java.lang.Object actual) 检查两个对象引用是否不引用统一对象(即对象不等)

Assert语句的方法可以既可以断言数组,也可以断言普通数据类型。

使用断言的时候尽量不要去断言Double对象。对于双精度数,绝对有必要使用增量进行比较,以避免浮点舍入的问题。

Junit注解

`注解 含义
@Test 依附在 JUnit 的 public void 方法可以作为一个测试案例。
@Before 修饰的方法会在测试方法之前被自动执行。
@After 修饰的方法会在测试方法执行之后自动被执行。
@BeforeClass 在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。
@AfterClass 它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。
@Ignore 这个注释是用来忽略有关不需要执行的测试的。

JUnit加注解执行过程

  • beforeClass(): 方法首先执行,并且只执行一次。
  • afterClass():方法最后执行,并且只执行一次。
  • before():方法针对每一个测试用例执行,但是是在执行测试用例之前。
  • after():方法针对每一个测试用例执行,但是是在执行测试用例之后
  • 在 before() 方法和 after() 方法之间,执行每一个测试用例。

JUnit时间测试和异常测试

时间测试

如果一个测试用例比起指定的毫秒数花费了更多的时间,那么 Junit 将自动将它标记为失败。

@Test(timeout=1000)

异常测试

Junit 用代码处理提供了一个追踪异常的选项。你可以测试代码是否它抛出了想要得到的异常。expected 参数和 @Test 注释一起使用。

@Test(expected = ArithmeticException.class)

代码实现

功能类

public class Count {
    public int add(int a, int b){
        return a+b;
    }
    public int sub(int a, int b){
        return a-b;
    }
    public int mul(int a, int b){
        return a*b;
    }
    public int div(int a, int b){
        return a/b;
    }
}

测试类

public class TestCount {
    @Before
    public void before(){
        System.out.println("before....");
    }

    @After
    public void after(){
        System.out.println("after....");
    }

    @Test
    public void testadd(){
        Count con = new Count();
        int result = con.add(6,12);
        // System.out.println(result); 通常在测试环境下不需要打印结果
        //一般我们会使用断言操作来处理结果:Assert.assertEquals(期望的结果,运算的结果);
        Assert.assertEquals(18,result);
    }

    @Test(timeout = 1) //测试用例比起指定的毫秒数花费了更多的时间,那么 Junit 将自动将它标记为失败
    public void testsub(){
        Count con = new Count();
        int result = con.sub(6,12);
        // System.out.println(result); 通常在测试环境下不需要打印结果
        //一般我们会使用断言操作来处理结果:Assert.assertEquals(期望的结果,运算的结果);
        Assert.assertEquals(-6,result);
    }
}

二、反射

反射的理解(框架设计的灵魂)

反射机制:将类的各个组成部分封装为其他对象。

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理
  • ......

反射相关的主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法
  • ......

Class

在Object类中定义了以下的方法,此方法将被所有子类继承:

public final Class getClass(){}

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。

获取Class类对象

  • 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高Class clazz = String.class;
  • 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象Class clazz = people.getClass();
  • 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundExceptionClass clazz = Class.forName(“java.lang.String”);

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

  • 其他方式
    ClassLoader cl = this.getClass().getClassLoader();
    Class clazz4 = cl.loadClass(“类的全类名”);
    

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

获取Class对象代码实现

/**
 * 获取Class对象的几种方式
 */
public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
        Class cls = People.class;
        System.out.println(cls);

        //2.已知某个类的实例,调用该实例的getClass()方法获取Class对象
        People people =new People();
        Class cls2 = people.getClass();
        System.out.println(cls2);

        //3.该类在类路径下,通过Class类的静态方法forName()获取
        Class cls3 = Class.forName("Reflextion.People"); //全类名   抛出ClassNotFoundException
        System.out.println(cls3);

        //4.
        ClassLoader cl = GetClass.class.getClassLoader();
        Class cls4 = cl.loadClass("Reflextion.People");
        System.out.println(cls4);

        System.out.println(cls == cls2); //true
        System.out.println(cls == cls3); //true
        System.out.println(cls == cls4); //true
        //同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

    }
}

 

关于Class类的注意

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个类在 JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过Class可以完整地得到一个类中的完整结构

Class类的常用方法

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

    Field类中:

    在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。

    public Object get(Object obj) //取得指定对象obj上此Field的属性内容
    public void set(Object obj,Object value) 	//设置指定对象obj上此Field的属性内容
        
    //注:在类中属性都设置为private的前提下,在使用set()和get()方法时,首先要使用Field类中的setAccessible(true)方法将需要操作的属性设置为可以被外部访问。
        
    public void setAccessible(true)	//访问私有属性时,让这个属性可见。 
    
    
    public int getModifiers()  //以整数形式返回此Field的修饰符
    public Class<?> getType()  //得到Field的属性类型
    public String getName()  //返回Field的名称。
    

     

  2. 获取构造方法
    Constructor<?>[] getConstructors()  //返回此 Class 对象所表示的类的所有public构造方法。
    Constructor<T> getConstructor(类<?>... parameterTypes)  
    
    Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  
    Constructor<?>[] getDeclaredConstructors()  //返回此 Class 对象表示的类声明的所有构造方法。
    

     

  3. 获取成员方法
    Method[] getMethods()  //返回此Class对象所表示的类或接口的public的方法
    Method getMethod(String name, 类<?>... parameterTypes)  
    
    Method[] getDeclaredMethods()  //返回此Class对象所表示的类或接口的全部方法
    Method getDeclaredMethod(String name, 类<?>... parameterTypes)  
    

    Method类中(通过反射,调用类中的方法,通过Method类完成。):

    /*
    步骤:
    1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
    */
    Object invoke(Object obj, Object …args)
    /*
    说明:
    	1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
        2.若原方法若为静态方法,此时形参Object obj可为null
        3.若原方法形参列表为空,则Object[] args为null
        4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方		法,将可访问private的方法。
    
    */
    public Class<?> getReturnType()//取得全部的返回值
    public Class<?>[] getParameterTypes()//取得全部的参数
    public int getModifiers()//取得修饰符
    public Class<?>[] getExceptionTypes()//取得异常信息
    

     

  4. 创建类的对象调用Class对象的newInstance()方法

    1)类必须有一个无参数的构造器。

    2)类的构造器的访问权限需要足够。

  5. 其他方法
方法名 说明
static Class forName(String name) 返回指定类名 name 的 Class 对象
Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Package getPackage() 获取类所在的包
Type getGenericSuperclass() 获取父类泛型类型

常用方法代码实现

//Person类代码实现省略
/**
 * Class 对象功能(方法)测试
 */
public class ClassDemo {
    /**
     * 1. 在运行时获取运行时类的实例
     */
    @Test
    public void test1() throws Exception{
        //获取Class对象
        Class cls = Class.forName("Reflextion.People");
        //使用Class对象newInstance()实例化
        People people = (People) cls.newInstance();
        people.setName("小白");
        people.setAge(18);
        System.out.println(people.toString());
    }

    /**
     * 2.  利用类加载器加载属性文件
     *     在项目src目录下创建文件pro.properties
     */
    @Test
    public void test2() throws IOException {
        //提前在src目录下新建pro.properties文件
        Properties pro = new Properties();
        //加载配置文件进内存
        pro.load(this.getClass().getClassLoader().getResourceAsStream("pro.properties"));
        //读取配置信息
        String className = pro.getProperty("ClassName");
        String methodName = pro.getProperty("MethodName");
        System.out.println(className  + methodName);
    }

    /**
     * 3.在运行时获取并操作运行时类对象的属性
     */
    @Test
    public void test3() throws Exception {
        //获取Class对象
        Class cls = People.class;
        //实例化People对象
        People o = (People) cls.newInstance();
        //获取属性Field[]
        Field[] declaredFields = cls.getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            System.out.println(declaredFields[i]);
        }
        //获取指定属性Field
        Field age = cls.getField("age");
        age.set(o,10);
        //获取属性值
        Object o1 = age.get(o);
        System.out.println(o1);

        System.out.println("-----------------------------");

        Field name = cls.getDeclaredField("name");
        name.setAccessible(true);//忽略访问权限
        name.set(o, "小白");
        Object o2 = name.get(o);
        System.out.println(o2);

    }

    /**
     * 4.在运行时获取并调用运行时类对象的方法
     */
    @Test
    public void test4() throws Exception {
        //1.获取Class对象
        Class cls = People.class;
        //2.使用Class对象实例化
        People o = (People) cls.newInstance();
        //3.获取指定Method
        Method eat = cls.getMethod("eat");
        //4.调用方法
        eat.invoke(o);

        //获取指定私有方法
        //Method sleep = cls.getMethod("sleep");error:getMethod()方法不能获取私有方法
        Method sleep = cls.getDeclaredMethod("sleep");
        //调用方法
        //sleep.invoke(o);  //报错!!java.lang.NoSuchMethodException
        sleep.setAccessible(true); //暴力反射
        sleep.invoke(o);


    }
}

 

反射案例

案例:

  • 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
    • 实现:
      1. 配置文件
        1. 反射
      • 步骤:
        1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
        2. 在程序中加载读取配置文件
        3. 使用反射技术来加载类文件进内存
        4. 创建对象
        5. 执行方法

代码实现

// Teacher类代码实现省略
public class ReflexDemo {
    public static void main(String[] args) throws Exception {
        //1.加载配置文件
        Properties properties = new Properties();
        properties.load(ReflexDemo.class.getClassLoader().getResourceAsStream("pro.properties"));
        //2.读取配置信息
        String className = properties.getProperty("ClassName");
        String methodName = properties.getProperty("MethodName");
        //3.使用反射技术来加载类文件进内存
        Class cls = Class.forName(className);
        Object o = cls.newInstance();
        //4.使用cls对象获取指定方法对象
        Method method = cls.getMethod(methodName);
        //5.执行方法
        method.invoke(o);
    }
}

三、注解

什么是注解?

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

  • 注解:说明程序的。给计算机看的
  • 注释:用文字描述程序的。给程序员看的

概念描述:

  • JDK1.5之后的新特性
  • 说明程序的:一种代码级别的说明
  • 使用注解:@注解名称

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理. 通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。

Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中.

Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据

作用分类

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

JDK中预定义的一些注解

@Override	//检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated //该注解标注的内容,表示已过时
@SuppressWarnings//抑制编译器警告
	//一般传递参数all  @SuppressWarnings("all")

自定义注解

格式

定义新的 Annotation 类型使用 @interface 关键字

public @interface 注解名称{
			//属性列表;
		}

Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明. 其方法名和返回值定义了该成员的名字和类型.

本质

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

public interface MyAnno extends java.lang.annotation.Annotation {}

属性

  1. 属性的返回值类型有下列取值
    • 基本数据类型
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  2. 定义了属性,在使用时需要给属性赋值
    • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

提取 Annotation 信息

JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。

当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取

程序可以调用 AnnotationElement 对象的如下方法来访问 Annotation 信息:

 

元注解

概念:用于描述注解的注解。即用于修饰其他 Annotation 定义。

  • @Target:描述注解能够作用的位置ElementType取值:
    TYPE:可以作用于类上
    METHOD:可以作用于方法上
    FIELD:可以作用于成员变量上
  • @Retention:描述注解被保留的阶段@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
    • RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
    • RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
    • RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
  • @Documented:描述注解是否被抽取到api文档中
  • @Inherited:描述注解是否被子类继承

在程序使用(解析)注解

也就是获取注解中定义的属性值

  1. 获取注解定义的位置的对象 (ClassMethod,Field)
    1. 获取指定的注解
      • getAnnotation(Class)
        //其实就是在内存中生成了一个该注解接口的子类实现对象

           public class ProImpl implements Pro{
               public String className(){
                   return "cn.imyjs.AnnotationDemo.Worker";
               }
               public String methodName(){
                   return "working";
               }
           }
        
    2. 调用注解中的抽象方法获取配置的属性值

案例

简单的测试框架

自定义注解代码实现

@Target(ElementType.TYPE) //TYPE:可以作用于类上
@Retention(RetentionPolicy.RUNTIME) // 程序可以通过反射获取该注释
public @interface MyAnno {
    String ClassName();
    String MethodName();
}

使用注解

@MyAnno(ClassName = "cn.imyjs.AnnotationDemo.Worker", MethodName = "working")
public class AnnoDemo {
    public static void main(String[] args) throws Exception {
        Class cls = AnnoDemo.class;
        MyAnno anno= (MyAnno) cls.getAnnotation(MyAnno.class);
        String className=anno.ClassName();
        String methodName=anno.MethodName();
        System.out.println(className);
        Class demo1=Class.forName(className);
        Method show=demo1.getMethod(methodName);
        Object o=demo1.newInstance();
        show.invoke(o);
    }
}

微信关注

青年之学

阅读剩余
THE END