一、Junit单元测试
1. 测试分类
- 黑河测试:不需要写代码,给定输入值,判断程序是否能够输出期望的值。
- 白盒测试:需要写代码,关注程序具体的执行流程。
2. 测试步骤(使用Junit进行白盒测试)
- 定义一个测试类(测试用例),建议测试类名为被测试的类名+Test,包名为xxx.xxx.xxx.test。
- 定义测试方法,可以独立运行,建议方法名为test+测试的方法名,返回值为void,参数列表为空参。
- 给方法加@Test注解
- 导入junit依赖
3. 判定结果
- 红色表示失败, 绿色表示成功,一般我们会使用断言操作来处理结果
4. 示例代码
/**
* 初始化方法,用于资源申请,所有测试方法在执行之前都会先执行该方法
*/
@Before
public void init(){
System.out.println("init...");
}
@Test
public void testAdd(){
//这里写测试代码
//...
// 断言操作,predict为预期的结果,result为真正的结果
Assert.assertEquals(predict,rersult);
}
/**
* 释放资源方法,所有测试方法在执行完毕之后都会执行该方法
*/
@After
public void close(){
System.out.println("close...");
}
二、反射:框架设计的灵魂
1. 反射的概念
- 将类的各个组成部分封装为其他对象,这就是反射机制。
- 框架指半成品软件,可以在框架的基础上进行软件开发,简化编码。
- Java代码在计算机中经历的三个阶段如下图,在Class类对象阶段,Java将成员变量、构造方法、成员方法等分别都封装成了对象数组。
2. 反射的好处
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
3. 获取Class对象的方式
- 在源代码阶段,只有一个字节码文件时,通过
Class.forName("全类名")
,将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中,读取文件,加载类。 - 在Class类对象阶段,字节码文件已经在内存中,通过
类名.class
,通过类名的属性class获取。多用于参数的传递。 - 在运行时阶段,通过
对象.getClass()
,getClass()方法在Object类中定义。多用于对象获取自己的字节码的方式。
示例代码
public static void main(String[] args) {
/**
* 三个对象是同一个对象,同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个。
*/
// 1. Class.forName("全类名")
Class cls1 = Class.forName("com.fan.start.Person");
// 2. 类名.class
Class cls2 = Person.class;
// 3. 对象.getClass()
Person p = new Person();
Class cls3 = p.getClass();
}
4. Class类的方法
Field getField(String name)
:获取指定名称的public修饰的成员变量Field[] getFields()
:获取所有public修饰的成员变量Field getDeclaredField(String name)
:获取指定名称的成员变量,不考虑修饰符Field[] getDeclaredFields()
:获取所有的成员变量,不考虑修饰符Constructor<T> getConstructor(Class<?>... parameterTypes)
:获取指定构造参数的public修饰的构造方法Constructor<?>[] getConstructors()
:获取空参的public修饰的构造方法Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
:获取指定构造参数的构造方法Constructor<?>[] getDeclaredConstructors()
:获取所有的构造方法Method getMethod(String name, Class<?>... parameterTypes)
:返回一个方法对象,它反映此表示的类或接口的指定公共成员方法类对象。Method[] getMethods()
:返回包含一个数组方法对象反射由此表示的类或接口的所有公共方法类对象,包括那些由类或接口和那些从超类和超接口继承的声明。Method getDeclaredMethod(String name, Class<?>... parameterTypes)
:返回一个方法对象,它反映此表示的类或接口的指定声明的方法类对象。Method[] getDeclaredMethods()
:返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。String getName()
:返回由类对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为 String 。
5. Field类的方法
- 设置值:
void set(Object obj,Object value)
- 获取值:
void get(Object obj)
- 忽略访问权限修饰符的安全检查:
setAccessible(true) // 暴力反射
6. Constructor类的方法
- 创建对象:
T newInstance(Object...initargs)
,如果使用空参构造方法创建对象,操作可以简化:Class对象的newInstance方法,即Class.newInstance()
。
7. Method类的方法
- 执行方法:
invoke(对象,参数)
8. 反射的案例
需求:写一个“框架”,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
可以通过配置文件和反射来实现,步骤如下:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
示例代码
- 配置文件:pro.properties
className=com.fan.study.bean.Person
methodName=eat
- 测试反射机制
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 1. 加载配置文件
// 1.1 创建properties对象
Properties properties = new Properties();
// 1.2 加载配置文件,转换为一个集合
ClassLoader classLoader = ReflectTest.class.getClassLoader();
// 获取class目录下的配置文件
InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
properties.load(resourceAsStream);
// 2. 获取配置文件定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3. 加载该类进内存
Class<?> aClass = Class.forName(className);
// 4. 创建对象
Object o = aClass.newInstance();
// 5. 获取方法对象
Method method = aClass.getMethod(methodName);
// 6. 执行方法
method.invoke(o);
}
}
三、注解
1. 注解的概念
- 注解:说明程序的,给计算机看的。从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
- 注释:用文本描述程序的,给程序员看的。
2. 注解的作用分类
- 编写文档:通过代码里标识的注解生成文档(生成文档doc文档)
- 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(例如:Override)
3. JDK中预定义的一些注解
- @Override:检测被该注解标注的方法是否是继承自父类(接口)的
- @Deprecated:该注解标注的内容,表示已过时
- @SuppressWarnings(“all”):压制警告
示例代码
public class Annotation1 {
@Override
public String toString() {
return super.toString();
}
@Deprecated
public void method1() {
// 已经过时,不建议使用
}
/**
* 压制警告
*/
@SuppressWarnings(value = "all")
public void method2() {
// 新的方法
}
public static void main(String[] args) {
Annotation1 annotation1 = new Annotation1();
annotation1.method1();
}
}
4. 自定义注解
- 格式:
- 元注解
- public @interface 注解名称{
属性列表;
}
- 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
- public interface MyAnno extends java.lang.annotation.Annotation{}
- 属性:接口中的抽象方法
- 要求:
- 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上数据类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性值需要赋值,并且属性的名称是value,则value可以省略。
- 数组赋值时,值使用{}包裹,如果数组中只有一个值,则{}可以省略。
- 元注解:用于描述注解的注解
- @Target:描述注解能够作用的位置
- ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- ElementType取值:
- @Retention:描述注解被保留的阶段
- @Retention(RetentionPolicy.RUNTIME)当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
- @Documented:描述注解是否被抽取到api文档中
- @Inherited:描述注解是否被子类继承
- @Target:描述注解能够作用的位置
示例代码
- 注解:MyAnnotation.java
public @interface MyAnnotation {
// 基本数据类型
int age();
// String
String name() default "张三";
// 枚举
Person per() default Person.p1;
// 注解
MyAnnotation2 myAnnotation() default @MyAnnotation2();
// 以上数据类型的数组
String[] str() default {"fan", "a"};
}
- 注解使用
@MyAnnotation(age = 18)
public class Worker {
}
5. 在程序使用(解析)注解
- 获取注解定义的位置的对象 (Class、Method,Field)
- 获取指定的注解:getAnnotation(Class)
- 调用注解中的抽象方法获取配置的属性值
示例代码
- 配置注解:Pro.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
- 解析注解
@Pro(className = "com.fan.study.bean.Person", methodName = "eat")
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 1.解析注解,获取该类的字节码文件对象
Pro annotation = ReflectTest.class.getAnnotation(Pro.class);
// 2. 获取注解
String className = annotation.className();
String methodName = annotation.methodName();
System.out.println(className);
System.out.println(methodName);
//3. 加载该类进内存
Class<?> aClass = Class.forName(className);
// 4. 创建对象
Object o = aClass.newInstance();
// 5. 获取方法对象
Method method = aClass.getMethod(methodName);
// 6. 执行方法
method.invoke(o);
}
}
6. 注解的案例
需求:写一个简单的测试框架,检查计算器中的方法是否存在异常。
- 计算器
public class Calculator {
@Check
public void add() {
System.out.println("1+0=" + (1 + 0));
}
@Check
public void sub() {
System.out.println("1-0=" + (1 - 0));
}
@Check
public void mul() {
System.out.println("1*0=" + (1 * 0));
}
@Check
public void div() {
System.out.println("1/0=" + (1 / 0));
}
public void show() {
System.out.println("永无bug...");
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
calculator.add();
}
}
- 检查注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
- 测试
public class TestCheck {
/**
* 简单的测试框架
* 当主方法执行后,会自动检查被加了Check注解的的所有方法,判断方法是否异常,记录到文件中
*/
public static void main(String[] args) throws IOException {
// 1. 创建计算器对象
Calculator calculator = new Calculator();
// 2. 获取字节码文件对象
Class<? extends Calculator> aClass = calculator.getClass();
// 3. 获取所有的方法
Method[] methods = aClass.getMethods();
// 记录出现异常的次数
int number = 0;
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.txt"));
for (Method method : methods) {
// 4. 判断方法上是否有Check注解
if (method.isAnnotationPresent(Check.class)) {
// 5. 有,执行
try {
method.invoke(calculator);
} catch (Exception e) {
// 6. 捕获异常
e.printStackTrace();
// 记录到文件中
number++;
bufferedWriter.write(method.getName() + "方法出异常了");
bufferedWriter.newLine();
bufferedWriter.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bufferedWriter.newLine();
bufferedWriter.write("异常的原因:" + e.getCause().getMessage());
bufferedWriter.newLine();
bufferedWriter.write("------------------------------");
bufferedWriter.newLine();
}
}
}
bufferedWriter.write("本次测试共出现" + number + "次异常");
bufferedWriter.flush();
bufferedWriter.close();
}
}
小结
- 以后大多数时候,我们会使用注解,而不是自定义注解。
- 注解给编译器和解析程序用。
- 注解不是程序的一部分,可以理解为注解就是一个标签。
参考:黑马程序员