【Java】浅析Junit单元测试+反射+注解

 

▊ Junit单元测试

 
测试分为黑盒测试白盒测试

Junit单元测试属于白盒测试

测试一个类,就创建一个"与这个类所在包并列的test包,test包中创建Test类"
命名规范:
	包名:test
	类名:被测试类名+Test
	方法名:test+被测试方法名
推荐的测试写法:
	返回值void,参数为空,函数体内直接断言
结果判定:
	绿色成功,红色失败(因为使用了断言,因此一般不看运行结果)
	

--------------------------------------- Demo:测试Calculator的add()方法 -----------------------------------------
package test

public class CalculatorTest {

		@Tset
		public void testAdd() {
			Calculator c = new Calculator();
			int result = c.add(1, 2);
			Assert.assertEquals(result, 3);			// 第一个参数是expected期望值,第二参数是actual实际值
		}
}


--------------------------------------- 补充:@Before@After注解 -------------------------------------------

@Before
public void init(){
    System.out.println("我会在所有测试方法之前自动执行————因此常用来初始化,获取资源");
}

@Test
// 测试方法...

@After
public void close(){
    System.out.println("我会在所有测试方法之后自动执行————因此常用来释放资源(就算断言失败,我也会执行!)");
}

 

 

 
 

▊ 反射

 
先明确类的生命分为三个阶段(下图)。

反射就是将Class类对象阶段时的成员变量、构造方法、成员方法封装成对象来操作。

反射是框架设计的灵魂

在这里插入图片描述

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

Class loliClass = Class.forName("myPackage.Loli");			// 1.类未被加载。手动加载类并返回Class对象
Class loliClass = Loli.class;								// 2.类已加载,但没有实例对象。
Class loliClass = loli.getClass();							// 3.已经做出实例对象。

----------------------------------------------- 1.三种对象 --------------------------------------------------
// 它们是java.lang.reflect包中三个类,分别可以做出成员变量、构造方法、成员方法的对象
Field
Constructor
Method

------------------------------------------- 2.利用Class对象获取三种对象 -----------------------------------------

// 1.获取成员变量
Field name = loliClass.getField("name");				// 获取指定名称成员变量,且必须是public		
Field age  = loliClass.getDeclaredField("age");			// 获取指定名称的成员变量,不考虑修饰符

Field[] fields1 = loliClass.getFields();				// 获取所有public的成员变量
Field[] fields2 = loliClass.getDeclaredFields();		// 获取所有的成员变量,不考虑修饰符


// 2.获取构造方法
Constructor constructor1 = loliClass.getConstructor();	
Constructor constructor2 = loliClass.getConstructor(String.class, int.class);	// 可以看出,是根据参数选出构造方法的
Constructor constructor3 = loliClass.getDeclaredConstructor(String.class, int.class);

Constructor[] constructors1 = loliClass.getConstructors(); 
Constructor[] constructors2 = loliClass.getDeclaredConstructors();


// 3.获取成员方法
Method method1 = loliClass.getMethod("eat");
Method method2 = loliClass.getMethod("eat", String.class);			// 可以看出,是根据名称和参数选出来方法的
Method method3 = loliClass.getDeclaredMethod("eat");

Method[] methods1 = loliClass.getMethods();	
Method[] methods2 = loliClass.getDeclaredMethods();


----------------------------------------------- 3.使用三种对象 ---------------------------------------------

// Filed对象————成员变量嘛,当然是get和set了!
Loli loli = new Loli();						// set和get方法都需要传入一个实例对象(谁的变量?)
name.set(loli, "Alice");
age.get(loli);
name.setAccessible(true);					// 忽略访问权限修饰符的安全检查————让私有成员变量也可以被访问和修改!!也称为"暴力反射"


// Constructor对象————构造方法当然还是要构造对象! (Constructor对象,参数 要相对应) (另外注意返回值是Object类型)
Object loli = constructor.newInstance();						// 无参构造
Object loli = constructor.newInstance("chino", 10);				// 有参构造
Object loli = loliClass.newInstance();							// 无参构造的简化(不再需要获取Constructor对象,直接Class对象构造)
constructor.setAccessible(true);								// 暴力反射


// Method对象————当然是执行方法! 
Loli loli = new Loli();
method.invoke(loli, "fish");						// 需要传入实例变量(谁调用方法?),以及参数
method.setAccessible(true);							// 暴力反射

 

 

 
 

▊ 注解

 
作用

  1. 编译检查
  2. 传递信息
  3. 标记
  4. 生成文档
--------------------------  JDK中预定义注解(部分) -------------------------------

@Override	:检测被该注解标注的方法是否是继承自父类(接口)@Deprecated :该注解标注的内容,表示已过时(使用过时的内容会有一个删除线提示)
@SuppressWarnings("all") :压制所有警告


--------------------------  自定义注解 ----------------------------------------
元注解
public @interface 注解名称{
	属性列表;
}

--------------------------  注解的本质 ----------------------------------------

进行javac编译和javap反编译后,可以得到注解的本质————一个继承了Annotation接口的接口:
public interface 注解名称 extends java.lang.annotation.Annotation {}

 
属性列表是什么呢?

这里的"属性",其实是指接口中的抽象方法。因为这些抽象方法返回值,需要在调用注解时像属性那样被赋值

public @interface myAnno {
	public abstract String name();			// public abstract可省略;因为要"赋值",抽象方法名也取的像"属性"
    public abstract int age(); 
}

// 调用时:
@myAnno(name = "Loli", age = 12)


1. 属性(抽象方法)的返回值类型可以是:
					* 基本数据类型
					* String
					* 枚举
					* 注解 
					* 以上类型的数组

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

 
元注解是什么呢?用于描述注解的注解

* @Target:描述注解能够作用的位置
			* ElementType.TYPE	 :可以作用于类上
			* ElementType.METHOD :可以作用于方法上
			* ElementType.FIELD  :可以作用于成员变量上
* @Retention:描述注解被保留的阶段
			* RetentionPolicy.SOURSE  : 不会保留到class字节码文件中
 			* RetentionPolicy.CLASS	  : 会保留到class字节码文件中,但不能被JVM读取到
			* RetentionPolicy.RUNTIME : 会保留到class字节码文件中,能被JVM读取到
* @Documented:描述注解是否被抽取到API文档中
* @Inherited :描述注解是否被子类继承

 
 
 
解析注解——通俗的说,就是用注解来传递信息,来替代配置文件的作用

下面举一个例子,看看注解是如何传递信息的:

--------------------------------- myAnno.java-----------------------------------------
// 自定义注解

@Target(ElementType.TYPE)						// 作用目标是类
@Retention(RetentionPolicy.RUNTIME)				// 被保留到运行时
public @interface myAnno {
    String className();							// 抽象方法
    String methodName();						// 抽象方法
}

--------------------------------- ReflectTest.java-----------------------------------------
// 测试类(用自定义注解修饰)

@myAnno(className = "myPackage.Loli1", methodName = "eat")					// 对应抽象方法(所谓"属性"),给其赋值————这就是我们要传递的信息
public class ReflectTest {
    public static void main(String[] args) {

        Class<ReflectTest> reflectTestClass = ReflectTest.class;        // 获取该类的类对象
        myAnno anno = reflectTestClass.getAnnotation(myAnno.class);     // 获取类对象上的注解对象(注解对象是原注解接口的实现类的实例对象)
        String className = anno.className();                            // 调用注解对象的抽象方法,获取返回值
        System.out.println(className);									// 成功打印"myPackage.Loli1",信息被传达到了!!!

    }
}


() 理解getAnnotation(Class)方法,是理解"注解如何传递信息"的关键 :
该方法的作用是:返回了一个注解对象。
具体的说:根据给注解的赋值,覆盖抽象方法(return语句),从而出现了一个原注解接口的实现类,返回的对象正是该实现类的对象;
		该对象具有被重写了的、带有return语句的、return的正是"信息"的方法————通过这个对象,在测试类中就可以任意使用这些"信息"!!!
		
进一步理解:注解接口的作用,与函数式接口的作用如出一辙:
          接口的抽象方法都是等待着被传入的"信息"重写的,接口本身都是等待着被实现的。
          被实现的接口做出的对象,承载着信息。而接口,仅仅是个"过渡"的作用。

/**
 * anno对象的来源———接口的实现类,抽象方法都已经被"信息"重写:
 * 
 * public class myAnnoImpl implements myAnno {
 *     public String className() {
 *         return "Day04.Loli1";
 *     }
 *     public String methodName() {
 *         return "eat";
 *     }
 * }
 */

 
 

下面是一个注解+反射的应用(用注解来作为标记

--------------------------------- Check.java-----------------------------------------
// 自定义注解
@Target(ElementType.METHOD)					// 目标为方法
@Retention(RetentionPolicy.RUNTIME)			// 保留到运行时
public @interface Check {					// Check注解只作为"标记",并不需要"传递信息"
}

--------------------------------- Check.java-----------------------------------------
/**
 * 简单的测试框架
 * 当主方法执行后,会自动检测Calculator类中所有加了Check注解的方法。判断是否有异常,并记录到文件中
 * 前提:Calculator类被注解的方法都是无参方法(否则需要传参,就不能无脑invoke了)
 */
public class TestCheck {

    public static void main(String[] args) throws IOException {
        Calculator c = new Calculator();
        Class cls = c.getClass();							// 获取类对象						
        Method[] methods = cls.getDeclaredMethods();		// 获取所有方法

        int num = 0;
        BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\bug.txt"));

        for(Method method : methods){
            if(method.isAnnotationPresent(Check.class)){		// 筛选出所有被@Check注解所标记的方法
                try {
                    method.invoke(c);
                } catch (Exception e){
                    num++;
                    bw.write(method + "方法出异常了>_<!");
                    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.newLine();

        bw.flush();
        bw.close();
    }
}


>>> 或许你会想到"Junit单元检测",但这是两回事
>>> Junit单元检测一般需要用测试数据,并进行断言;这里的Check注解仅仅是尝试运行了下这些方法,做不到逻辑上的"测试"

 

 

 

 

 

 

 

 

 

 

 

 
End

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值