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);//true
System.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 {
//加法
@Check
public void add(){
String str = null;
str.toString();
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("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 小结
- 大多数时候,我们会使用注解,而不是自定义注解
- 注解是给编译器和解析程序用的
- 注解不是程序的一部分