Java SE 学习笔记(十七)—— 单元测试、反射
目录
- 1 单元测试
- 1.1 单元测试概述
- 1.2 单元测试快速入门
- 1.3 JUnit 常用注解
- 2 反射
- 2.1 反射概述
- 2.2 获取类对象
- 2.3 获取构造器对象
- 2.4 获取成员变量对象
- 2.5 获取常用方法对象
- 2.6 反射的作用
- 2.6.1 绕过编译阶段为集合添加数据
- 2.6.2 通用框架的底层原理
1 单元测试
1.1 单元测试概述
开发好的系统中存在很多方法,如何对这些方法进行测试?
以前我们都是将代码全部写完再进行测试。其实这样并不是很好。在以后工作的时候,都是写完一部分代码,就测试一部分。这样,代码中的问题可以得到及时修复。也避免了由于代码过多,从而无法准确定位到错误的代码。
单元测试就是针对最小的功能单元编写测试代码, Java 程序最小的功能单元是 方法,因此,单元测试就是针对 Java 方法的测试,进而检查方法的正确性。
JUnit
是使用 Java 语言实现的单元测试框架,它是开源的, Java 开发者都应当学习并使用 JUnit
编写单元测试。此外,几乎所有的 IDE 工具都集成了 JUnit
,这样我们就可以直接在 IDE 中编写并运行 JUnit
测试。
JUnit
优点:
- 可以灵活的选择执行哪些测试方法,也可以一键执行全部测试方法
- 可以生成全部方法的测试报告,如果测试良好则是绿色;如果测试失败,则变成红色
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试
1.2 单元测试快速入门
需求:使用单元测试进行业务方法预期结果、正确性测试的快速入门
分析:
- 将
JUnit
的 jar 包导入到项目中- IDEA 通常整合好了
JUnit
框架,一般不需要导入。 - 如果 IDEA 没有整合好,需要自己手工导入如下 2 个
JUnit
的 jar 包到模块
- IDEA 通常整合好了
- 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
- 在测试方法上使用
@Test
注解:标注该方法是一个测试方法 - 在测试方法中完成被测试方法的预期正确性测试。
- 选中测试方法,选择
JUnit 运行
,如果测试良好则是绿色;如果测试失败,则是红色
示例代码
要测试的方法
public class UserService {
public String login(String name,String passwd){
if ("admin".equals(name) && "123456".equals(passwd)){
return "登陆成功";
}else{
return "用户名或密码错误!";
}
}
public void selectNames(){
// System.out.println(10/0);
System.out.println("查询全部用户成功!");
}
}
测试方法
import org.junit.Assert;
import org.junit.Test;
public class TestUserService {
// 测试方法(公开的无参数无返回值的非静态方法)
@Test
public void testLogin(){
UserService userService = new UserService();
String rs = userService.login("admin", "123456");
// 进行预期结果的正确性测试
Assert.assertEquals("您的登录业务出现问题","登陆成功",rs);
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
// 要测试的方法没有返回值,不用断言
userService.selectNames();
}
}
一个业务要对应一个测试方法
1.3 JUnit 常用注解
Junit 4.xxxx 版本
Junit 5.xxxx 版本
2 反射
2.1 反射概述
反射是指对于任何一个 Class 类,在 " 运行的时候 " 都可以直接得到这个类全部成分。
- 在运行时 , 可以直接得到这个类的构造器对象:
Constructor
- 在运行时 , 可以直接得到这个类的成员变量对象:
Field
- 在运行时 , 可以直接得到这个类的成员方法对象:
Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为 Java 语言的反射机制。
反射的第一步都是先得到编译后的 Class 类对象,然后就可以得到 Class 的全部成分。
2.2 获取类对象
获取 class 对象的有以下三种方式
示例代码
Student
类
package com.huwei;
public class Student {
private String name;
private int age;
public Student() {
System.out.println("无参构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参构造器执行!");
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 通过Class类中的静态方法forName("全限名")来获取
Class c = Class.forName("com.huwei.Student");
System.out.println(c); // class com.huwei.Student
// 2. 通过class属性来获取
Class c1 = Student.class;
System.out.println(c1); // class com.huwei.Student
// 3. 利用对象的getClass方法来获取
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2); // class com.huwei.Student
}
}
注意:
- 第一种方式
forName(String className)
中的className
为全限名(包名+类名)
已经获取类对象了,接下来就可以获取以下对象
2.3 获取构造器对象
Class类中用于获取构造器的方法
示例代码
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
// 获取类对象
Class c = Student.class;
// System.out.println(c); // class com.huwei.Student
// 提取类中全部的构造器(只能是公开的构造器)
Constructor[] cons1 = c.getConstructors();
// 遍历构造器
for (Constructor con : cons1) {
System.out.println(con.getName() + "=====>" + con.getParameterCount());
}
System.out.println("-------------------------------------");
// 提取类中全部的构造器,包括私有
Constructor[] cons2 = c.getDeclaredConstructors();
// 遍历构造器
for (Constructor con : cons2) {
System.out.println(con.getName() + "=====>" + con.getParameterCount());
}
System.out.println("-------------------------------------");
// 获取单个构造器(无参构造器),只能是公开的(如果无参构造器私有会报错)
Constructor con1 = c.getConstructor();
System.out.println(con1.getName() + "=====>" + con1.getParameterCount());
System.out.println("-------------------------------------");
// 获取单个构造器(无参构造器),包括私有
Constructor con2 = c.getDeclaredConstructor();
System.out.println(con2.getName() + "=====>" + con2.getParameterCount());
System.out.println("-------------------------------------");
// 获取单个构造器(有参构造器),只能是公开的(如果有参构造器私有会报错)
Constructor con3 = c.getConstructor(String.class, int.class);
System.out.println(con3.getName() + "=====>" + con3.getParameterCount());
System.out.println("-------------------------------------");
// 获取单个构造器(有参构造器),包括私有
Constructor con4 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(con4.getName() + "=====>" + con4.getParameterCount());
}
}
获取构造器的作用依然是初始化一个对象返回
Constructor类中用于创建对象的方法
import java.lang.reflect.Constructor;
public class Test2 {
public static void main(String[] args) throws Exception{
// 获取类对象
Class c = Student.class;
// 获取单个构造器(无参构造器),包括私有
Constructor con1 = c.getDeclaredConstructor();
System.out.println(con1.getName() + "=====>" + con1.getParameterCount());
// 如果遇到了私有构造器,可以暴力反射
con1.setAccessible(true); // 权限被打开,仅当前这次
Student s1 = (Student) con1.newInstance();
System.out.println(s1);
System.out.println("--------------------------------");
// 获取单个构造器(有参构造器),包括私有
Constructor con2 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(con2.getName() + "=====>" + con2.getParameterCount());
Student s2 = (Student) con2.newInstance("孙悟空",50000);
System.out.println(s2);
}
}
如果遇到非 public 的构造器,需要打开权限(暴力反射),然后再创建对象,说明反射可以破坏封装性,私有的也可以执行了
2.4 获取成员变量对象
Class类中用于获取成员变量的方法
示例代码
import java.lang.reflect.Field;
public class Test3 {
public static void main(String[] args) throws NoSuchFieldException {
// 获取类对象
Class c = Student.class;
// 获取全部成员变量,包括私有
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+"=====>"+field.getType());
}
System.out.println("----------------------");
// 获取某一个成员变量,包括私有
// Field field = c.getDeclaredField("name");
Field field = c.getDeclaredField("age");
System.out.println(field.getName()+"=====>"+field.getType());
}
}
获取成员变量的作用依然是在某个对象中取值、赋值
Filed 类中用于取值、赋值的方法
示例代码
import java.lang.reflect.Field;
public class Test4 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 获取类对象
Class c = Student.class;
// 获取某一个成员变量,包括私有
Field name = c.getDeclaredField("name");
name.setAccessible(true); // 暴力打开权限
// 赋值
Student s = new Student();
name.set(s,"小明");
System.out.println(s);
// 取值
String name1 = (String) name.get(s);
System.out.println(name1);
}
}
2.5 获取常用方法对象
Class类中用于获取成员方法的方法
获取成员方法的作用依然是在某个对象中执行此方法
Method类中用于触发执行的方法
示例代码
定义Dog类
public class Dog {
private String name;
public Dog() {
}
public Dog(String name) {
this.name = name;
}
public void run(){
System.out.println("狗在跑");
}
public String eat(String name){
System.out.println(name+"在吃");
return "吃的很开心!";
}
private void eat(){
System.out.println("吃啥");
}
}
测试类
import java.lang.reflect.Method;
public class Test5 {
public static void main(String[] args) throws Exception {
// 获取类对象
Class c = Dog.class;
// 提取全部方法,包括私有
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "=====>" + method.getReturnType() + "====>" + method.getParameterCount());
}
// 提取单个方法,包括私有
Method eat1 = c.getDeclaredMethod("eat"); // 无参的eat方法
Method eat2 = c.getDeclaredMethod("eat", String.class); // 有参的eat方法
eat1.setAccessible(true); // 暴力反射
// 触发方法的执行
Dog d = new Dog();
Object rs1 = eat1.invoke(d); // 方法如果是没有返回值的,则返回的是null
System.out.println(rs1);
Object rs2 = eat2.invoke(d, "小黑");
System.out.println(rs2);
}
}
2.6 反射的作用
2.6.1 绕过编译阶段为集合添加数据
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成 Class 文件进入运行阶段的时候,其真实类型都是 ArrayList 了,泛型相当于被擦除了。
反射是作用在运行时的技术,此时已经不存在泛型了。
示例代码
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
// 编译成 Class 文件进入运行阶段的时候,泛型会自动擦除。===> ArrayList.class
System.out.println(list1.getClass()); // class java.util.ArrayList
System.out.println(list2.getClass()); // class java.util.ArrayList
System.out.println(list1.getClass() == list2.getClass()); // true
System.out.println("=======================================");
ArrayList<Integer> list3 = new ArrayList<>();
list3.add(11);
list3.add(22);
// list3.add("哈哈哈"); // 报错
Class c = list3.getClass(); // ArrayList.class ===> public boolean add(E e)
// // 定位c中的add方法
Method add = c.getDeclaredMethod("add", Object.class);
boolean rs = (boolean)add.invoke(list3, "嘿嘿嘿");
System.out.println(rs); // true
System.out.println(list3); // [11, 22, 嘿嘿嘿]
// 还有一种方法可以突破泛型的限制
ArrayList list4 = list3;
list4.add("呜呼啦胡");
list4.add(false);
System.out.println(list3); // [11, 22, 嘿嘿嘿, 呜呼啦胡, false]
}
}
2.6.2 通用框架的底层原理
反射可以做通用框架
需求:给你任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文件中去。
分析
- 定义一个方法,可以接收任意类的对象。
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
- 这个对象可能是任意的,那么怎么样才可以知道这个对象的全部成员变量名称呢?
- 使用反射获取对象的 Class 类对象,然后获取全部成员变量信息。
- 遍历成员变量信息,然后提取本成员变量在对象中的具体值
- 存入成员变量名称和值到文件中去即可
示例代码
存在Teacher
、Student
类
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class MybatisUtil {
/**
保存任意类型的对象
* @param obj
*/
public static void save(Object obj){
try (
PrintStream ps = new PrintStream(new FileOutputStream("reflect/src/data.txt", true));
){
// 1、提取这个对象的全部成员变量:只有反射可以解决
Class c = obj.getClass(); // c.getSimpleName()获取当前类名 c.getName获取全限名:包名+类名
ps.println("================" + c.getSimpleName() + "================");
// 2、提取它的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3、获取成员变量的信息
for (Field field : fields) {
String name = field.getName();
// 提取本成员变量在obj对象中的值(取值)
field.setAccessible(true);
String value = field.get(obj) + "";
ps.println(name + "=" + value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
目标:提供一个通用框架,支持保存所有对象的具体信息。
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Student s = new Student();
s.setName("猪八戒");
s.setClassName("西天跑路1班");
s.setAge(1000);
s.setHobby("吃,睡");
s.setSex('男');
MybatisUtil.save(s);
Teacher t = new Teacher();
t.setName("波仔");
t.setSex('男');
t.setSalary(6000);
MybatisUtil.save(t);
}
}
结果文件