JavaWeb - Junit白盒测试、反射、注解
1.Junit白盒测试
*步骤:
1.定义一个测试类(测试用例)
*建议:
*测试类名:被测试的类名+Test
*包名:xxx.xxx.xxx.test cn.itcast.test
2.定义测试方法:可以独立运行
*建议:
*方法名:test+测试的方法名 testAdd()
*返回值:void
*参数列表:建议空参
3.给方法加@Test
4.导入Junit依赖环境
判定结果:
*红色:失败
*绿色:成功
*一般我们会使用assert断言操作来处理结果
Assert.assertEquals(期望值,真实值)进行比对
public class CalculatorTest { /* 测试add方法 */ @Test public void testAdd(){ //System.out.println("我被执行了"); /* * 1.创建计算器对象 * 2.调用add方法 * */ Calculator c = new Calculator(); int result = c.add(1, 2); System.out.println(result); //无意义,如果是写成了减法依旧测试成功 //3.断言 结果为3 Assert.assertEquals(4,result); } }
资源申请与资源释放:(@Before 和 @After)
public class CalculatorTest { /* * 初始化方法 * 用于资源申请,所有测试方法在执行之前都会先执行该方法 * */ @Before public void init(){ System.out.println("init...已被执行"); } /* * 释放资源的方法 * 在所有测试方法执行完后都会自动执行该方法 * */ @After public void close(){ System.out.println("close"); } }
2.反射:框架设计的灵魂
*框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
*将类的各个组成部分封装成为其他对象,这就是反射机制
*好处:
1.可以在程序运行过程中,操作这些对象。
2.可以解耦,来提高程序的可扩展性
获取Class对象的方式:
1.Class.forName("全类名"): 将字节码文件加载进内存,返回Class对象
*多用于配置文件,将类名定义在配置文件中。读取文件加载类
2.类名.class:通过类名的属性class获取
*多用于参数的传递
3.对象.getClass():getClass()方法在Object类中定义着
*多用于对象的一个获取字节码的方式
public class DemoReflect { public static void main(String[] args) throws Exception { //第一种方式 Class.forName Class cls1 = Class.forName("basicpart.day01.FaceObj.Zi"); System.out.println(cls1); //第二种方式 类名.class Class cls2 = Zi.class; System.out.println(cls2); //第三种方式 对象.getClass Zi zi = new Zi(); Class cls3 = zi.getClass(); System.out.println(cls3); System.out.println(cls1==cls2); } }
结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过那一种方式获取的class文件只有一个
3.Class对象的功能
获取功能:
1.获取成员变量们
操作:Field成员变量
1.设置值 void set(Object obj,Object value)
2.获取值 get(Object obj)
*Field【】 getFields():获取所有public修饰的成员变量
*Field【】 getField(String name):获取指定名称的public修饰的成员变量
public class DemoReflect { public static void main(String[] args) throws Exception { //获取一个字节码对象 Class<Person> personClass = Person.class; //获取指定的成员变量对象 Field field = personClass.getField("name"); //创建一个对象 Person p = new Person(); //获取值 Object value = field.get(p); System.out.println(value); //设置值 field.set(p,"chris"); System.out.println(p); } }
*Field【】 getDeclaredFields():获取所有的成员变量,不考虑权限修饰符
*Field【】 getDeclaredField(String name)在访问之前要先忽略访问权限修饰符 暴力反射 setAccessible(true)
Field age = personClass.getDeclaredField("age"); //在访问之前需要先忽略权限访问修饰符 age.setAccessible(true);//暴力反射 Object value2 = age.get(p); System.out.println(value2);
2.获取构造方法们
*Constructor<?>【】 getConstructors()
*Constructor<T>【】 getConstructor(类<?>...parameterTypes)
*Constructor<T>【】 getDeclaredConstructor(类<?>...parameterTypes)
*Constructor<?>【】 getDeclaredConstructors()
*Constructor用于创造对象 newInstance()方法
如果使用空参数构造方法创建对象,操作可以简化:Class对象有个newInstance()方法
public class Demo2 { public static void main(String[] args) throws Exception { //构造器的使用 Class<Person> personClass = Person.class; Constructor<Person> constructor = personClass.getConstructor(String.class, int.class); //创建对象 Person chris = constructor.newInstance("chris", 21); System.out.println(chris); //空参创建 Person person = personClass.newInstance(); } }
3.获取成员方法们
*Method【】 getMethods()
*Method【】 getMethod(String name,类<?>...parameterTypes)
*Method【】 getDeclaredMethods()
*Method【】 getDeclaredMethod(String name,类<?>...parameterTypes)
*方法对象的功能:*执行方法 Object invoke(Object obj,Object...args)
*获取方法名 String getName();
public class Demo2 { public static void main(String[] args) throws Exception { Class<Person> personClass = Person.class; //获取方法对象 Method eat_method = personClass.getMethod("eat"); //执行无参方法 //先创建确定的对象 Person p = new Person(); eat_method.invoke(p); //执行有参的fangfa Method eat_method2 = personClass.getMethod("eat", String.class); eat_method2.invoke(p,"苹果"); } }
4.获取类名
*String getName()
案例:不能改变该类的任何代码,可以创建任意类的对象,执行任意方法
/* * 框架类 * */ public class DemoStruct { public static void main(String[] args) throws Exception { //可以创建任意类的对象,可以执行任意方法 /* 不能改变该类的任何代码,可以创建任意类的对象,执行任意方法 */ //1.加载配置文件 //1.1创建Properties对象 Properties pro = new Properties(); //1.2加载配置文件,转换为一个集合 //1.2.1获取class目录下的配置文件 ClassLoader classLoader = DemoStruct.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); } }
4.注解
/** * javadoc文档 * @author chris * @since jdk1.5 * @version 1.0 */ public class annotation { /** * 文档注释 * @param a 整数 * @param b 整数 * @return 两数的和 */ public int add(int a , int b){ return a + b ; } }
JDK内置注解
1.限定父类重写方法:@Override - 检测该注解标注的方法是否是继承自父类(接口)的
2.标示已过时:@Deprecated - 将该注解标注的内容,表示已过时
3.抑制编译器警告:@SuppressWarnings - 压制警告的 一般传递参数all
@SuppressWarnings("all") //压制了该类右侧的所有警告 public class annoDemo2 { @Override public String toString() { return super.toString(); } @Deprecated public void show1(){ //有缺陷 } public void show2(){ //替代show1方法 } }
自定义注解:
(1)*格式:
*元注解 - 用于描述注解的注解
(1)@Target:描述注解能够作用的位置
ElementType取值:1.TYPE 类上 2. METHOD 方法上 3.FIELD 成员变量上
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
(2)@Retention:描述注解被保留的阶段 (源码,class,runtime)
@Retention(RetentionPolicy.RUNTIME) 该注解会保留到class字节码文件中,并被JVM读取到
(3)@Documented:描述注解是否被抽取到api文档
@Documented
(4)@Inherited:描述注解是否被子类继承
*public @interface 注解名称{
属性列表;
}
(2)*本质:注解本质上就是一个接口,该接口默认继承Annotation接口
*public interface MyAnno extends java.lang.annotation.Annotation { }
(3)*属性:接口中的抽象方法
*要求:
1.属性的返回值类型
*基本数据类型
*字符串String
*枚举
*注解
*以上类型的数组
2.定义了属性,在使用时需要给属性赋值 @MyAnno(show = "cc") 也可在注解里 default 设置默认值
如果只有一个属性需要赋值,而且属性的名称是value,则value可以省略
数组赋值时,值使用{ }包裹。如果数组中只有一个值,则{ }可以省略
*在程序中使用(解析)注解:获取注解中定义的属性值
1.获取注解定义位置的对象,定义在类上就获取类,定义在方法上就获取方法 (Class,Method,Field)
2.获取指定的注解对象 getAnnotation(xxx.class)
其实就是在内存中生成了该注解接口的子类实现对象,方法中的返回值就是刚刚定义的值
3.调用注解中的抽象方法获取配置的属性值
/* * 框架类 * */ @ProAnno(ClassName = "JavaWeb.reflect.Person", MethodName = "eat") public class AnnoComple { public static void main(String[] args) throws Exception { //可以创建任意类的对象,可以执行任意方法 /* 不能改变该类的任何代码,可以创建任意类的对象,执行任意方法 */ //1.解析注解 //1.1获取注解定义的该类的字节码文件对象 本类 Class<AnnoComple> annoCompleClass = AnnoComple.class; //2.获取上边的注解对象 //其实就是在内存中生成了该注解接口的子类实现对象,方法中的返回值就是刚刚定义的值 ProAnno an = annoCompleClass.getAnnotation(ProAnno.class); //3.调用注解对象定义的抽象方法来获取返回值 String className = an.ClassName(); String methodName = an.MethodName(); System.out.println(className); System.out.println(methodName); //3.加载该类进内存 Class cls = Class.forName(className); //4.创建对象 Object obj = cls.newInstance(); //5.获取方法对象 Method method = cls.getMethod(methodName); //6.执行方法 method.invoke(obj); } }
测试框架案例:
check注解:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }
测试框架:
/* 简单的测试框架 当主方法执行后,会自动执行被检测的所有方法(加了check注解的方法),判断方法是否有异常,记录到文件中 */ 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(); int number = 0; //记录异常的次数 BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt")); for (Method method : methods) { //4.判断方法上是否有check注解 if (method.isAnnotationPresent(Check.class)) { //5.有的话就执行 try { method.invoke(c); } catch (Exception e) { //6.捕获异常 记录到文件中 number++; 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("本次测试一共出现了"+number+"次异常"); bw.flush(); bw.close(); } }
被测试的方法:
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 div(){ System.out.println("1 / 0 = " + (1 / 0)); } public void show(){ System.out.println("永无bug . . ."); } }