1 Junit
黑盒测试:不需要写代码,给输入值,看程序输出是否符合期望
白盒测试:需要写代码,关注程序具体的执行流程
Junit —> 白盒测试
步骤
- 定义一个测试类(测试用例)
【命名:类名+Test】【包名:xxx.xx.xx.test】 - 定义测试方法,可以独立运行
【命名:test+测试的方法】【返回值:void】【参数列表:空参】 - 给方法加注解@Test
- 导入Junit的依赖环境
判断结果
5. 断言:Assert.assertEquals(expected,result);
6. 红失败,绿成功
@Before注解,所有测试方法前会先执行,初始化申请资源方法可以用
@After注解,所有测试方法执行后执行,释放资源方法可以用
2 反射
反射:框架设计的灵魂,将类的各个组成成分
框架:半成品软件,可以在框架的基础上进行软件的开发,简化编码
Java代码在计算机中经历的三个阶段
Java文件 —> Javac编译 —>class文件【Source源代码阶段】 —> 类加载器Class.Loader —> Class类对象 【Class类对象阶段】—> 创建对象 —> Xxxx对象 【Runtime运行时阶段】
反射的好处:
- 在程序运行过程中,操作这些对象
- 可以解耦,提高程序的可扩展性
获取字节码Class对象的三种方式
- Class.forName(“全类名”):将字节码文件加载进内存,返回class对象
- 类型.class:通过类名的属性class获取
- 对象.getClass() :getClass()方法在Object类中定义着
public static void main(String[] args) throws ClassNotFoundException {//1. Class.forName("全类名"):将字节码文件加载进内存,返回class对象Class cls = Class.forName("reflect.Person");System.out.println(cls);//2. 类型.class:通过类名的属性class获取Class cls2 = Person.class;System.out.println(cls2);//3. 对象.getClass() :getClass()方法在Object类中定义着Class cls3 = new Person().getClass();System.out.println(cls3);System.out.println(cls == cls2);//trueSystem.out.println(cls == cls3);//true}
同一个字节码文件**.class文件在一次程序运行中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
方法1多用于配置文件,将类名定义在配置文件中,读取文件,加载类
方法2多用于参数的传递
方法3多用于对象的获取字节码的方式
class对象的功能
- 获取成员变量们
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {Class cls = Person.class;Field[] fields = cls.getFields();//获取所有public修饰的成员变量System.out.println(Arrays.toString(fields));Field school = cls.getField("school");//获取指定的public修饰的成员变量System.out.println(school);Person p = new Person();//获取成员变量school的值Object o = school.get(p);System.out.println(o);//null//设置成员变量school的值school.set(p,"张三小学");System.out.println(p);//Person{name='null', age=0, school='张三小学'}Field[] declaredFields = cls.getDeclaredFields();//获取所有的成员变量for(Field field: declaredFields){System.out.println(field);}//获取私有成员变量的值Field name = cls.getDeclaredField("name");//获取指定的成员变量//访问私有成员前 要忽略访问权限修饰符的安全检查name.setAccessible(true);//暴力反射Object o1 = name.get(p);System.out.println(o1);//null}
- 获取构造方法们
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {Class cls = Person.class;//全参构造Constructor constructor = cls.getConstructor(String.class, int.class);System.out.println(constructor);//创建对象Object obj = constructor.newInstance("张三", 19);System.out.println(obj);//空参构造Constructor constructor2 = cls.getConstructor();System.out.println(constructor2);Object obj2 = constructor2.newInstance();System.out.println(obj2);//空参构造一般用这个 简化版Object obj3 = cls.newInstance();System.out.println(obj3);}
- 获取成员方方法们
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {Class cls = Person.class;//eat方法为空参Method eat = cls.getMethod("eat");Person p = new Person();//执行方法eat.invoke(p);//eat方法中有一个String类型的参数Method eat2 = cls.getMethod("eat", String.class);eat2.invoke(p," food");//获取所有public修饰的方法Method[] methods = cls.getMethods();for(Method m : methods){System.out.println(m.getName());//除了类自己的方法,Object的方法也存在其中}}
- 获取类名
获取的是全类名
public static void main(String[] args) {Class cls = Person.class;String name = cls.getName();System.out.println(name);//reflect.Person}
案例:框架类
在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法
实现:1. 配置文件 2.反射
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
创建配置文件 xxx.properties
修改配置文件就可以选择使用的类和方法
className=reflect.Student//全类名
methodName=learn
public class ReflectTest {//在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//1.加载配置文件//1.1创建Properties对象Properties pro = new Properties();//1.2加载配置文件,转换为一个集合//1.2.1获取class目录下的配置文件ClassLoader classLoader = ReflectTest.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("pro.properties");pro.load(is);//2.获取配置文件中定义的数据String className = pro.getProperty("className");String methodName = pro.getProperty("methodName");//3.加载该类进内存Class cls = Class.forName(className);//4.创建对象Object obj = cls.newInstance();//5.获取方法对象Method method = cls.getMethod(methodName);//6.执行方法method.invoke(obj);}
}
运行执行的是配置文件里对应的类和类的方法
3 注解
定义:Annotation,也叫元数据,一种代码级别的说明,JDK1.5后的新特性,与类、接口、枚举在同一层子,可以声明在包、类、字段、方法、局部变量和方法参数等前面,用来对这些元素进行说明,注释。
概念:JDK1.5之后的新特性,用来说明程序
作用分类:
- 编译检查:让编译器实现基本的编译检查 【@Override @FunctionalInterface】
- 代码分析:通过代码标识的注解对代码进行分析【使用反射】
- 编写文档:通过代码表示的注解生产文档【javadoc文档】
JDK的内置注解
- @Override:检测被该注解标注的方法是否是继承自父类/父接口的
- @Deprecated:将该注解标注的内容,表示已过时
- @SuppressWarnings :压制警告 @SuppressWarnings(“all”)写在类上方
自定义注解
- 格式:
元注解
public @interface 注解名称{}
注解的本质:是一个接口 默认继承自Annotation接口
public interface javadoc.MyAnno extends java.lang.annotation.Annotation {}
注解的属性:接口中的抽象方法
要求:
- 属性的返回值类型有【基本数据类型 String 枚举 注解 以上类型的数组】,不可以为自定义类和void。
- 定义了属性,在使用时需要给属性赋值。如果在定义时,分号前写上default “默认值”,可以不赋值
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义。
元注解:用来描述注解的注解
- @Target:描述注解能够作用的位置
ElementType的取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FILED:可以作用于成员变量上
@Target(value = ElementType.TYPE)
- @Retention:描述注解被保留的阶段
RetentionPolicy的取值:
RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
CLASS:
SOURCE:
@Retention(RetentionPolicy.RUNTIME)
- @Documented:描述注解是否被抽取到api文档中
- @Inherited:描述注解是否被子类继承
解析注解
- 获取注解定义的位置对象(Class, Method, Field)
- 获取指定的注解:getAnnotation(Class)
Pro annotation = reflectTestClass.getAnnotation(Pro.class); - 调用注解中的抽象方法来获取配置的属性值
定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {String className();String methodName();
}
自定义类
public class Demo01 {public void show(){System.out.println("demo1...show");}
}
定义框架类
@Pro(className = "reflect.Demo01",methodName = "show")
public class ReflectTest {//在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//1.解析注解//1.1 获取该类的字节码文件对象Class<ReflectTest> reflectTestClass = ReflectTest.class;//2.获取注解对象,在内存中生产了一个该注解接口的子类实现对象/*public class ProImpl implements Pro{public String className(){return "reflect.Demo01";}public String methodName(){return "show";}}*/Pro annotation = reflectTestClass.getAnnotation(Pro.class);//3.调用注解对象中定义的抽象方法获取返回值String className = annotation.className();String methodName = annotation.methodName();System.out.println(className);System.out.println(methodName);//4.加载类进内存Class cls = Class.forName(className);//5.创建对象Object obj = cls.newInstance();//6.获取方法对象Method method = cls.getMethod(methodName);method.invoke(obj);}
}
注解案例:简单的测试框架
定义Check注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
定义类
package demo;public class Calculator {//加法@Checkpublic void add(){String str = null;str.toString();System.out.println("1 + 0 =" + (1 + 0));}//减法@Checkpublic void sub(){System.out.println("1 - 0 =" + (1 - 0));}//乘法@Checkpublic void mul(){System.out.println("1 * 0 =" + (1 * 0));}//除法@Checkpublic void div(){System.out.println("1 / 0 =" + (1 / 0));}public void show(){System.out.println("no bug...");}
}
测试类-解析程序
public class TestCheck {public static void main(String[] args) throws IOException {//1.创建计算器对象Calculator c = new Calculator();//2.获取字节码文件对象Class cls = c.getClass();//3.获取所有方法Method[] methods = cls.getMethods();BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));//4.判断方法上是否有check注解int num = 0;for(Method method: methods){if(method.isAnnotationPresent(Check.class)){//5.有就执行方法try{method.invoke(c);//6.捕获异常}catch (Exception e){num++;bw.write(method.getName()+"方法出异常了");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.flush();bw.close();}
}
bug.txt文件
add方法出异常了
异常的名称:NullPointerException
异常的原因:null
-----------------------------
div方法出异常了
异常的名称:ArithmeticException
异常的原因:/ by zero
-----------------------------
本次测试一共出现了2次异常
4 小结
- 大多数时候,我们会使用注解,而不是自定义注解
- 注解是给编译器和解析程序用的
- 注解不是程序的一部分