06_单元测试与反射
单元测试
什么是单元测试
- 就是针对最小的功能单位(方法),编写测试代码对其进行正确性测试
- Junit 单元测试框架:可以用来对方法进行测试,它是第三方公司开源出来的(IDEA 以及集成了此框架)
注解 | 说明 |
---|---|
@Test | 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行 |
@Before | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次 |
@After | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次 |
@BeforeClass | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次 |
@AfterClass | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次 |
- 在测试方法执行前执行的方法,常用于:初始化资源
- 在测试方法执行完后再执行的方法,常用于:释放资源
// 被测试的方法
/**
* 字符串工具类
*/
public class StringUtil {
public static void printNumber(String name) {
if (name == null) {
System.out.println(0);
return;// 停掉方法
}
System.out.println("名字的长度是:" + name.length());
}
// 求字符串最大索引
public static int getMaxIndex(String data) {
if (data == null) {
return -1;
}
return data.length();
}
}
// 进行测试的类
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 测试类
* 测试方法必须做到:公共、无参、无返回值
* 测试方法上面必须声明 @Test 注解
*/
public class StringUtilTest {
@Before
public void test1() {
System.out.println("------test1执行了------");
}
@Test
public void testPrintNumber() {
StringUtil.printNumber(null);
}
@Test
public void testGetMaxIndex() {
int index1 = StringUtil.getMaxIndex(null);
System.out.println(index1);
int index2 = StringUtil.getMaxIndex("admin");
System.out.println(index2);
// 断言机制:程序员可以通过预测业务方法的结果
Assert.assertEquals("方法内部有bug!", 4, index2);
}
}
反射
认识反射
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器)
获取类
public class Test {
public static void main(String[] args) throws Exception {
// 获取Class对象的三种方式
// 方式一
Class c1 = Student.class;
// 获取类的名字
// 获取全类名 【全类名 = 包名.类的简名】
System.out.println(c1.getName()); // login.Student ()
// 获取类的简名
System.out.println(c1.getSimpleName()); // Student
// 方式二
Class c2 = Class.forName("login.Student");
System.out.println(c2 == c1); // true
// 方式三
Student s = new Student();
Class c3 = s.getClass();
System.out.println(c3 == c2); // true
}
}
class Student {
private String name;
private int age;
}
获取类的构造器
// Cat 类 (前置资源)
public class Cat {
private String name;
private int age;
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
// 第一部分展示
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 反射第一步:必须先得到这个类的 Class 对象
Class c = Cat.class;
// 2. 获取类的全部构造器
// Constructor[] constructors = c.getConstructors(); // getConstructors 功能当构造器为私有时,获取不到
Constructor[] constructors = c.getDeclaredConstructors(); // 不管是否私有,都能拿到全部的构造器
// 3. 遍历数组中的每个构造器对象
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "--->" +
constructor.getParameterCount());
}
/*
* 运行结果:
*
* login.Cat--->0
* login.Cat--->2
*
* */
}
}
// 第二部分展示
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 反射第一步:必须先得到这个类的 Class 对象
Class c = Cat.class;
// 2. 获取某个构造器:无参构造器
Constructor constructor = c.getConstructor();
System.out.println(constructor.getName() + "--->" +
constructor.getParameterCount()); // login.Cat--->0
// 3. 获取有参构造器
Constructor constructor1 = c.getConstructor(String.class, int.class);
System.out.println(constructor1.getName() + "--->" +
constructor1.getParameterCount()); // login.Cat--->2
}
}
获取类的构造器,有什么用吗?
获取类的构造器的作用,依然是初始化一个对象返回(这种初始化,可以实现暴力破解构造器中 private 的限制)
// Cat 类 (前置资源)
class Cat {
private String name;
private int age;
// 重写了 toString 方法!
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 反射第一步:必须先得到这个类的 Class 对象
Class c = Cat.class;
// 2. 获取某个构造器:无参构造器
Constructor constructor = c.getConstructor();
System.out.println(constructor.getName() + "--->" +
constructor.getParameterCount()); // login.Cat--->0
// ---访问无参构造器的成员变量
constructor.setAccessible(true); // 当输入了这一句代码,意味着
// 开启了"禁止检查访问权限"功能,即使构造器是私有的,也能实现访问成员变量,这个是暴力反射!
Cat cat = (Cat) constructor.newInstance();
System.out.println(cat); // Cat{name='null', age=0}
// 3. 获取有参构造器
Constructor constructor1 = c.getConstructor(String.class, int.class);
System.out.println(constructor1.getName() + "--->" +
constructor1.getParameterCount()); // login.Cat--->2
// ---访问有参构造器的成员变量
Cat cat1 = (Cat) constructor1.newInstance("叮当猫", 3);
System.out.println(cat1); // Cat{name='null', age=0}
}
}
获取类的成员变量
// Cat 类 (前置资源)
public class Cat {
public static final String COUNTRY = "中国";
public static int a;
private String name;
private int age;
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 反射第一步:必须是先得到类的 Class 对象
Class c = Cat.class;
// 2. 获取类的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3. 遍历这个成员变量数组
for (Field field : fields) {
System.out.println(field.getName() + "--->" + field.getType());
}
System.out.println("----------------------------");
// 4. 定位到某个成员变量
Field fName = c.getDeclaredField("name");
System.out.println(fName.getName() + "--->" + fName.getType());
Field fAge = c.getDeclaredField("age");
System.out.println(fAge.getName() + "--->" + fAge.getType());
// 运行结果:
/*
COUNTRY--->class java.lang.String
a--->int
name--->class java.lang.String
age--->int
----------------------------
name--->class java.lang.String
age--->int
*/
}
}
获取成员变量的作用:依然是赋值、取值
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
// 得到类的 Class 对象
Class c = Cat.class;
// 定位到某个成员变量
Field fName = c.getDeclaredField("name");
Field fAge = c.getDeclaredField("age");
// 进行赋值
Cat cat = new Cat();
fName.setAccessible(true); // 禁止访问控制权限
fName.set(cat, "咖啡猫");
System.out.println(cat); // Cat{name='咖啡猫', age=0}
// 进行取值
String name = (String) fName.get(cat);
System.out.println(name); // 咖啡猫
}
}
获取类的成员方法
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 得到类的 Class 对象
Class c = Cat.class;
// 2. 获取类的全部成员方法
Method[] methods = c.getDeclaredMethods();
// 3. 遍历数组中的每个方法对象
for (Method method : methods) {
System.out.println(method.getName() + "--->"
+ method.getParameterCount() + "--->"
+ method.getReturnType());
}
System.out.println("-----------------------");
// 4. 获取某个方法对象
Method run = c.getDeclaredMethod("run"); // 拿run方法,无参数的
System.out.println(run.getName() + "--->"
+ run.getParameterCount() + "--->"
+ run.getReturnType());
System.out.println("-----------------------");
Method eat = c.getDeclaredMethod("eat", String.class); // 拿eat方法,含参数的
System.out.println(eat.getName() + "--->"
+ eat.getParameterCount() + "--->"
+ eat.getReturnType());
// 5. 获取成员方法的作用:依然是执行
Cat cat = new Cat(); // 先创造一个成员出来,才能执行成员方法
run.setAccessible(true); // 禁止检查访问权限 (暴力解除了 private 的限制)
Object res1 = run.invoke(cat);
System.out.println(res1);
eat.setAccessible(true); // 禁止检查访问权限 (暴力解除了 private 的限制)
Object res2 = eat.invoke(cat, "鱼");
System.out.println(res2);
// 运行结果
/*
eat--->1--->class java.lang.String
getAge--->0--->int
setAge--->1--->void
getName--->0--->class java.lang.String
run--->0--->void
toString--->0--->class java.lang.String
setName--->1--->void
-----------------------
run--->0--->void
-----------------------
eat--->1--->class java.lang.String
猫跑得快
null
猫最爱吃:鱼
*/
}
}
应用场景
- 基本作用:可以得到一个类的全部成分然后操作
- 可以破坏封装性
- 最重要的用途是:适合做 Java 的框架,基本上,主流的框架都会基于反射设计出一些通用的功能
案例:要求对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去
// 运行程序部分
public class Test {
public static void main(String[] args) throws Exception {
Student s1 = new Student("Jack", 29, '男', 168, "唱歌");
Teacher t1 = new Teacher("Tony", 9000);
// 需求:要求对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去
String PATH = "E:/Desktop/temp.txt";
System.out.println(ObjectFrame.savaObject(s1, PATH));
System.out.println(ObjectFrame.savaObject(t1, PATH));
}
}
class Student {
private String name;
private int age;
private char sex;
private double height;
private String hobby;
public Student(String name, int age, char sex, double height, String hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
this.hobby = hobby;
}
}
class Teacher {
private String name;
private double salary;
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
}
// 框架部分
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class ObjectFrame {
// 目的:可以保存任何对象的字段和其数据到文件中去
public static boolean savaObject(Object obj, String path) throws Exception {
// 创建打印输出流,待会保存数据到文件要用
PrintStream ps = new PrintStream(new FileOutputStream(path, true)); // true 代表开启追加模式
// obj 是任意对象,不清楚到达有多少个字段要保存
Class c = obj.getClass(); // 通过对象拿到它的"类对象"
String cName = c.getSimpleName(); // 通过"类对象"获取类的简单名
ps.println("----------" + cName + "----------");
// 从这个类中提取它的全部成员变量
Field[] fields = c.getDeclaredFields();
// 遍历,得到并操作每个成员对象
for (Field field : fields) {
String name = field.getName(); // 拿到该成员变量的名字
field.setAccessible(true); // 禁止检查访问权限 (通过暴力手段解除了 private 的限制)
String value = field.get(obj) + ""; // 拿到对象在"该成员变量"中存的值
ps.println(name + "=" + value); // 将数据输出到文件中
}
ps.close();
return true;
}
}
// 路径"E:/Desktop/temp.txt"的文件内容
----------Student----------
name=Jack
age=29
sex=男
height=168.0
hobby=唱歌
----------Teacher----------
name=Tony
salary=9000.0