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

news/2024/5/19 4:41:10 标签: java, junit, 反射, 注解

 

▊ Junit单元测试

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

Junit单元测试属于白盒测试

java">测试一个类,就创建一个"与这个类所在包并列的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类对象阶段时的成员变量、构造方法、成员方法封装成对象来操作。

反射框架设计的灵魂

在这里插入图片描述

java">---------------------------------------------- 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. 生成文档
java">--------------------------  JDK中预定义注解(部分) -------------------------------

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


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

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

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

 
属性列表是什么呢?

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

java">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. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

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

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

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

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

java">--------------------------------- 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";
 *     }
 * }
 */

 
 

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

java">--------------------------------- 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


http://www.niftyadmin.cn/n/1330483.html

相关文章

【JDBC】JDBC核心基础

▊ JDBC初识 Java DataBase Connectivity&#xff0c;Java数据库连接&#xff0c; 即用Java语言操作数据库 JDBC的本质&#xff1a;其实是Sun公司定义的一套操作所有关系型数据库的规则&#xff0c;即接口。各个数据库厂商(Oracle&#xff0c;MySql)去实现这套接口&#xff0…

hiho #1014 : Trie树 (字典树模版题)

1014 : Trie树 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友&#xff0c;出生在信息化社会的他们对编程产生了莫大的兴趣&#xff0c;他们约定好互相帮助&#xff0c;在编程的学习道路上一同前进。 这一天&#xff0c;他们遇到了一本词…

【JDBC】连接池 + Spring JDBC

○●○ 数据库连接池 本质&#xff1a; 存放数据库连接的容器(集合)。 原理&#xff1a; 容器中会申请一些连接对象。当用户来访问数据库时&#xff0c;从容器中获取连接对象&#xff1b;访问结束&#xff0c;归还连接对象给容器。 优点 不言而喻&#xff1a; 降低资源消耗&…

实现用户密码登录(MySQL数据库+自行封装的JDBC工具类)

★ JDBC的核心就是&#xff1a; 注册驱动 → 获取连接 → 获取sql执行对象 → 执行sql → 释放资源 -------------------------------------- jdbc.properties 配置文件 ------------------------------------------------driverClassNamecom.mysql.cj.jdbc.Driver urlj…

hiho#1107 : Shortest Proper Prefix (统计非指定前缀在字符串集中出现次数)

1107 : Shortest Proper Prefix 时间限制:10000ms 单点时限:1000ms 内存限制:512MB 描述 Query auto-completion(QAC) is widely used in many search applications. The basic idea is that when you type some string s in the search box several high-frequency que…

【图解算法】染上龙血的勇者——彻底理清【递归】【记忆化搜索】【动态规划】的关系

很久以前&#xff0c;有一个王国。 突然有一天&#xff0c;国王的女儿失踪了。 一名勇者去寻找她。他年轻、强大、无畏&#xff0c;只是有着致命的软肋。 他提剑找寻到了森林深处的山顶&#xff0c;山洞里是公主和一头恶龙。 为了打败恶龙&#xff0c;勇者饮下了龙血。…

【LeetCode】Sama的个人记录_27

【Q95】(md) 不同的二叉搜索树Ⅱ 给定一个整数 n&#xff0c;生成所有由 1 … n 为节点所组成的 二叉搜索树 。 示例&#xff1a; 输入&#xff1a;3 输出&#xff1a; [ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3] ] >>> 这道题…

区间异或最大

首先我们看这样一个问题&#xff1a;给定一个包含N个整数的集合S{A1, A2, A3, … AN}。然后有M个询问&#xff0c;每次询问给定一个整数X&#xff0c;让你找一个Ai使得Ai xor X的值最大。 这道题也是可以用Trie解决的。首先我们知道一个整数可以用二进制表示成一个01串。比如3…