▊ 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); // 暴力反射
▊ 注解
作用
- 编译检查
- 传递信息
- 标记
- 生成文档
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