文章目录
一、Junit单元测试
1.1 测试分类
1.1.1 黑盒测试
不需要写代码,给输入值,看程序是否能够输出期望的值。
1.1.2 白盒测试
需要写代码,关注程序具体的执行流程。
1.2 Junit的使用
1.2.1 步骤
- 定义一个测试类(测试用例)。
建议:
测试类名:测试类名TestCalculatorTest
包名:xxx.xxx.xx.testcn.itcast.test
- 定义测试方法:可以独立运行
建议:
方法名:test测试的方法名testAdd()
返回值:void
参数列表:建议空参 - 给方法加
@Test
- 导入Junit依赖环境
1.2.2 判定结果
- 绿色:成功
- 红色:失败
- 一般会使用断言操作
Assert.assertEquals(long expected, long result)
1.2.3 补充
@Before
修饰的方法会在测试方法之前自动被执行。@after
修饰的方法会在测试方法之后自动被执行。
二、反射(框架设计的灵魂)
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
反射:将类的各个组成部分封装成其他对象,这就是反射机制。
好处:
- 在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
2.1 获取Class对象的方式
Class.forName("全类名");
将字节码文件加载进内存,返回Class
对象。——多用于配置文件,将类名定义在配置文件中。读取文件,加载类。类名.class
:通过类名的属性class获取。——多用于参数的传递对象.getClass()
:getClass()
在Object
类中。——多用于对象的获取字节码的方式
注意:同一个字节码文件(*.class
)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class
对象都是同一个。
2.2 Class对象功能
2.2.1 获取功能
- 获取成员变量们
Field[] getField(String)
:获取所有public
修饰的成员变量Field getField(String name)
:获取指定名称的public
修饰的对象Field[] getDeclaredFields()
:获取所有的成员变量,不考虑修饰符Field getDeclaredField(String name)
- 获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructors(类<?>... parameterTypes)
Constructor<T> getDeclaredConstructors(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
- 获取成员方法们
Method[] getMethods()
Method getMethod(String name, 类<?>...parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>...parameterTypes)
- 获取类名
String getName()
2.2.2 具体操作
-
Field
:成员变量- 获取值
get(Object obj);//a.set(p,"张三");//将p对象的a值设定为“张三”
- 设置值
void set(Object obj, Object value);//a.set(p,"张三");//将p对象的a值设定为“张三”
- 忽略访问权限修饰符的安全检查
setAccessible(true);//暴力反射
-
Constructor
:构造方法- 创建对象
T newInstance(Object... initargs)
如果使用空参数构造方法创建对象,操作可以简化:
Class
对象的newInstance
方法 -
Method
:方法对象- 执行方法
Object invoke(Object obj, Object... args)
- 获取方法的名称
String getName()
-
代码示例
package day01_StrengthBase;
import java.lang.reflect.Method;
public class ReflectDemo3 {
/*
3. 获取成员方法们
`Method[] getMethods()`
`Method getMethod(String name, 类<?>...parameterTypes)`
`Method[] getDeclaredMethods()`
`Method getDeclaredMethod(String name, 类<?>...parameterTypes)`
*/
public static void main(String[] args) throws Exception {
//0. 获取Person的Class对象
Class personClass = Person.class;
//获取指定名称的方法
Method eat = personClass.getMethod("eat");
Person p = new Person();
//执行方法
eat.invoke(p);//要执行eat方法,将含这方法的对象传入,才能invoke
//获取带参数、指定名称的方法
Method eat1 = personClass.getMethod("eat", String.class);//方法名,参数类型
eat1.invoke(p,"饭");//执行方法,传入含这方法的对象,以及要传入的参数
System.out.println("--------------");
//获取所有public方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);//不仅仅只有Person自己的方法,还有超类Object的方法
String name = method.getName();
System.out.println(name);
}
//获取类名
String className = personClass.getName();
System.out.println(className);//day01_StrengthBase.Person
}
}
2.2.3 案例
需求:写一个”框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
- 实现:
- 配置文件
- 反射
- 步骤
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
2.2.3.1 代码
-
反射
public class ReflectTest { public static void main(String[] args) throws Exception { //可以创建任意类的对象,可以执行任意方法 /* 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法 */ /*Person p = new Person(); p.eat();*/ //1. 加载配置文件 //1.1 创建Properties对象 Properties pro = new Properties(); //1.2 加载配置文件,转换为一个集合 //1.2.1 获取class目录下的配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties"); pro.load(resourceAsStream); //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); } }
-
配置文件(文件名:pro.properties)
className=day01_StrengthBase.Student
methodName=sleep
- Person类
package day01_StrengthBase;
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("eat...");
}
public void eat(String food){
System.out.println("eat" + food);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
}
- Student类
public class Student {
public void sleep(){
System.out.println("sleep...");
}
}
三、注解
3.1 概念
- 注解:说明程序的。给计算机看的。
- 注释:用文字描述程序的。给程序员看的。
- 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
- 概念描述:
- JDK1.5之后的特性
- 说明程序的
- 使用注解:@注解名称
3.2 作用分类
- 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
- 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
3.3 JDK预定义的注解
@Override
:检测被该注解标注的方法是否是继承自父类(接口)的@Deprecated
:该注解标注的内容,标识已过时。@SuppressWarnings
:压制警告。(一般传递“all”
参数,压制全部警告)
3.4 自定义格式
-
格式:
- 元注解
public @interface MyAnno
-
本质:注解本质就是一个接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation{}
-
属性:接口中可以定义的成员方法,接口中的抽象方法。
-
要求
- 属性的返回值类型
- 基本数据类型
- 字符串
String
- 注解
- 以上类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用
default
关键字给属性默认初始化 值,则使用注解时,可以不进行属性的赋值。 - 如果只有一个属性需要赋值并且属性的名称是
value
,则value
可以省略,直接定义值即可。 - 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。
- 如果定义属性时,使用
注解类
public @interface MyAnno { int age(); //int value();//1 String name() default "张三"; }
被注解类
@MyAnno(age = 12) //张三为默认值可以不传参 //@MyAnno(12)//1 public class Worker { }
- 属性的返回值类型
-
3.5 元注解:用于描述注解的注解
-
@Target
:描述注解能够作用的位置ElementType
取值:TYPE
:可以作用域类上Method
:可以作用在方法上Field
:可以作用在变量上
-
@Retention
:描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
-
@Documented
:描述注解是否被抽取到api文档中 -
@Inherited
:描述注解是否被子类继承 -
在程序中使用(解析)注解:获取注解中定义的属性值
-
获取注解定义的位置对象(Class, Method, Field)
-
获取指定的注解
getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类对象 public class Proiml implements Pro{ public String className(){ return "day01_StrengthBase.Demo01"; } public String methodName(){ return "show"; } }
-
调用注解中的抽象方法获取配置的属性值
-
3.6 小结
- 以后大多数的时候,我们会使用注解,而不是自定义注解
- 注解给谁用
- 编译器
- 解析程序
- 注解不是程序的一部分,可以理解为注解就是一个标签。
3.7 案例——方法测试
要测试的方法
package day01_StrengthBase.Prac;
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..");
}
}
注解
package day01_StrengthBase.Prac;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
测试代码
package day01_StrengthBase.Prac;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 简单的测试框架
* 当主方法执行后,会自动执行被检测的所有方法(加了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();
}
}