JavaEE 初阶篇-深入了解 Junit 单元测试框架和 Java 中的反射机制(使用反射做一个简易版框架)

csdn推荐

文章目录

1.0 Junit 单元测试框架概述

可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了 Junit 框架,比如 IDEA)

优点:

1)可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立。

2)不需要程序员去分析测试结果,会自动生成测试报告出来。

具体步骤:

1)将 Junit 框架的 jar 包导入到项目中(注意:IDEA 集成了 Junit 框架,不需要我们手动导入)

2)为需要测试的业务类,定义对应的测试类,并为每个方法,编写对应的测试方法(测试方法必须:公共、无参、无返回值)

3)测试方法上必须声明 @Test 注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试。其实就是列出实际例子进行测试。

4)开始测试:选中测试方法,右键选择 “Junit 运行”,如果测试通过则是绿色;如果测试失败,则是红色。

1.1 使用Junit 框架进行测试业务代码

举个例子:

业务代码:

//这是项目的业务代码
public class ProjectBusiness {
    //项目业务一:获取字符串的长度
    public static void printLength(String str){
        System.out.println(str.length());
    }
    //项目业务二:获取字符串最大的索引下标
    public static int getMaxIndex(String str){
        if (str == null){
            return -1;
        }
        return str.length();
    }
}

测试业务代码:

import org.junit.Test;
//测试业务:对项目业务代码进行测试
public class TestBusiness {
    //针对每一个方法进行测试,定义测试方法必须是公开、无参、无返回值
    //对业务一代码进行测试
    //记得要加上注解
    @Test
    public void testPrintLength(){
        //然后调用业务一的代码,列举实际例子
        ProjectBusiness.printLength("你好世界!!!");
        ProjectBusiness.printLength("你好小板");
        ProjectBusiness.printLength(null);
    }
}

对于业务一代码来说很明显是缺少了一个 if 判断是否为 null ,假设我们没有发现,现在来看测试结果:

测试出来的结果也明确的表示是因为空指针导致的测试失败。

将代码改进加上 if 判断是否为 null 之后的测试运行结果:

这样就测试通过了,不过需要注意的是,当前测试通过是指:目前写的业务代码是没有异常报错,但是对于逻辑是否正确,当前的测试正确与代码业务逻辑是否跟我们所想的没有任何关联,再次注意:此时的测试的通过,是指写到实际例子中没有抛异常、没有报错仅此而已,万一程序员写的实际例子考虑不周全,会导致测试覆盖面不够全面。

断言机制:程序员可以通过预测业务方法的结果,从而实现测试出来的结果与我们一开始所认为的结果是否相同,从而来判断代码逻辑是否正确。

断言方法的参数:

调用静态的断言方法,第一个参数是如果预测的结果与测试出来的结果不一样时,提供的消息。第二个参数是自己预测的结果。第三个参数是测试出来的结果。

对业务二代码进行测试:

业务二代码:

//这是项目的业务代码
public class ProjectBusiness {
    //项目业务二:获取字符串最大的索引下标
    public static int getMaxIndex(String str){
        if (str == null){
            return 0;
        }
        return str.length();
    }
}

测试业务二代码:

import org.junit.Assert;
import org.junit.Test;
//测试业务:对项目业务代码进行测试
public class TestBusiness {
    //对业务二代码进行测试
    @Test
    public void testGetMaxIndex(){
        //接着调用业务二的方法
        int index1 = ProjectBusiness.getMaxIndex(null);
        //进行断言
        Assert.assertEquals("此处出现 BUG 啦!!!",0,index1);
        int index2 = ProjectBusiness.getMaxIndex("你好鸭小板");
        //进行断言
        Assert.assertEquals("此处出现 BUG 啦!!!",4,index2);
    }
}

测试结果:

这里出现了测试错误,我们预期 4 ,实际测试出来的结果是 5 。显然是我们的业务二代码逻辑与我们一开始所认为的结果不一样。

现在将业务二代码逻辑进行改进:

    //项目业务二:获取字符串最大的索引下标
    public static int getMaxIndex(String str){
        if (str == null){
            return 0;
        }
        return str.length()-1;
    }

再来进行测试。

测试结果:

1.2 Junit 单元测试框架的常用注解(Junit 4.xxx 版本)

1)@Test:测试类中的方法必须用它修饰才能成为测试方法,才能启动执行。

2)@Before:用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。

3)@After:用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。

4)@BeforeClass:用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。

5)@AfterClass:用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。

在测试方法执行前执行的方法,常用于:初始化资源。

在测试方法执行后执行的方法。常用于:释放资源。

代码演示:

import org.junit.*;
//测试业务:对项目业务代码进行测试
public class TestBusiness {
    //在执行全部测试方法之前,先执行以下代码
    @BeforeClass
    public static void bfc(){
        System.out.println("在执行bfc方法,在全部测试方法之前执行且只执行一次");
    }
    //执行每个测试代码之前都会先执行以下的方法
    @Before
    public void bf(){
        System.out.println("在执行bf方法啦!!!");
    }
    //执行每个测试代码之后都会执行以下的方法
    @After
    public void at(){
        System.out.println("在执行at方法啦!!!");
    }
    
    //在执行全部测试方法之后,再执行以下代码
    @AfterClass
    public static void atc(){
        System.out.println("在执行atc方法,在全部测试方法之后执行且只执行一次");
    }
    //针对每一个方法进行测试,定义测试方法必须是公开、无参、无返回值
    //对业务一代码进行测试
    //记得要加上注解
    @Test
    public void testPrintLength(){
        //然后调用业务一的代码,列举实际例子
        ProjectBusiness.printLength("你好世界!!!");
        ProjectBusiness.printLength("你好小板");
        ProjectBusiness.printLength(null);
        System.out.println("业务代码一执行完毕!!!");
    }
    //对业务二代码进行测试
    @Test
    public void testGetMaxIndex(){
        //接着调用业务二的方法
        int index1 = ProjectBusiness.getMaxIndex(null);
        //进行断言
        Assert.assertEquals("此处出现 BUG 啦!!!",0,index1);
        int index2 = ProjectBusiness.getMaxIndex("你好鸭小板");
        //进行断言
        Assert.assertEquals("此处出现 BUG 啦!!!",4,index2);
        System.out.println("业务代码二执行完毕!!!");
    }
    
}

全部测试方法的结果:

2.0 反射概述

Java 反射是指在运行时动态地获取类的信息、调用类的方法、操作类的属性等能力。Java 反射机制提供了一种在运行时检查类、接口、字段和方法的能力,而不需要在编译时就知道这些信息。

使用 Java 反射,可以在运行时动态地加载类、创建对象、调用方法、访问属性等,这为编写灵活的、可扩展的程序提供了便利。反射机制允许程序在运行时获取类的信息,甚至可以动态地修改类的行为。

简单来说:反射是在类加载之后对类进行操作的一种机制,允许以编程的方式解剖类中的各种成分(构造器、成员变量、成员方法等)。

反射过程:

1)反射第一步:加载类、获取类的字节码:Class 对象。

2)获取类的构造器:Constructor 对象

3)获取类的成员变量:Field 对象

4)获取类的成员方法:Method 对象

2.1 获取 Class 对象的三种方式

第一种方式:

Class c1 = 类名.class

public class demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过类名来获取Class对象
        Class c1 = Cat.class;
        System.out.println(c1.getName());
    }
}

第二种方式:

调用 Class 提供的方法:public static Class forName(String package);

该参数名为:全类名

public class demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过全类名获取Class对象
        Class c2 = Class.forName("Reflection.Cat");
        System.out.println(c2.getName());
    }
}

第三种方式:

Object 提供的方法:public Class getClass();

public class demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过对象名来获取
        Cat cat = new Cat();
        Class c3 = cat.getClass();
        System.out.println(c3.getName());
    }
}

以上三种获取 Class 对象都是同一个对象,因为 Cat 类的字节码文件只有一份。可以通过代码进行比较以下。

public class demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过类名来获取Class对象
        Class c1 = Cat.class;
        System.out.println(c1.getName());
        //通过全类名获取Class对象
        Class c2 = Class.forName("Reflection.Cat");
        System.out.println(c2.getName());
        //通过对象名来获取
        Cat cat = new Cat();
        Class c3 = cat.getClass();
        System.out.println(c3.getName());
        System.out.println(c1 == c2);
        System.out.println(c3 == c2);
        System.out.println(c1 == c3);
    }
}

运行结果:

2.2 获取类的构造器并对其进行操作

获取构造器的方法:

1)Constructor[] getConstructors():获取全部构造器(只能获取 public 修饰的)

代码演示:

import java.lang.reflect.Constructor;
public class demo2 {
    public static void main(String[] args) {
        //第一步先获取Class对象
        Class c = Cat.class;
        //第二步利用Class对象提供的方法来获取构造器
        //获取全部用 Public 修饰的构造器
        Constructor[] constructors = c.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName() + " => 参数有 " + constructor.getParameterCount() + " 个");
        }
        
    }
}

运行结果:

2)Constructor[] getDeclaredConstructors():获取全部构造器(只要存在就能拿到)

代码演示:

import java.lang.reflect.Constructor;
public class demo3 {
    public static void main(String[] args) {
        //第一步先获取Class对象
        Class c = Cat.class;
        //第二步再来获取全部构造器(即使用private修饰的构造器也可以获取到)
        Constructor[] constructors = c.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName() + " =>参数有 " + constructor.getParameterCount() + " 个");
        }
    }
}

运行结果:

3)Constructor[] getConstructor():获取某个构造器(只能获取 public 修饰的)

代码演示:

import java.lang.reflect.Constructor;
public class demo4 {
    public static void main(String[] args) throws NoSuchMethodException {
        //第一步先获取Class对象
        Class c = Cat.class;
        //第二步再获取指定的构造器
        //先获取无参的构造器
        Constructor c1 = c.getConstructor();
        System.out.println(c1.getName() + " => 参数有 " + c1.getParameterCount() + " 个");
        //获取有参的构造器
        Constructor c2 = c.getConstructor(String.class,int.class);
        System.out.println(c2.getName() + " => 参数有 " + c2.getParameterCount() + " 个");
        
    }
}

运行结果:

4)Constructor[] getDeclaredConstructor():获取某个构造器(只要存在就能拿到)

代码演示:

import java.lang.reflect.Constructor;
public class demo5 {
    public static void main(String[] args) throws NoSuchMethodException {
        //第一步先获取Class对象
        Class c = Cat.class;
        //第二步再获取指定的构造器
        //先获取无参的构造器
        Constructor c1 = c.getDeclaredConstructor();
        System.out.println(c1.getName() + " => 参数有 " + c1.getParameterCount() + " 个");
        //获取有参的构造器
        Constructor c2 = c.getDeclaredConstructor(String.class,int.class);
        System.out.println(c2.getName() + " => 参数有 " + c2.getParameterCount() + " 个");
    }
}

运行结果:

获取类构造器的作用:

利用获取的构造器来创建对象。

Constructor 提供的方法:

1)T newInstance(Object):调用次构造器对象表示的构造器,并传入参数,完成对象的初始化并返回。

2)public void setAccessible(boolean flag):设置为 true ,表示禁止检查访问控制(暴力反射)。

代码演示:

Cat 类

public class Cat {
    String name;
    int age;
    public Cat(){
    }
    private 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;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

利用反射获取有参构造器,来创建 Cat 对象:

import java.lang.reflect.Constructor;
public class demo6 {
    public static void main(String[] args) throws Exception {
        //第一步先获取Class对象
        Class c = Cat.class;
        //第二步再获取构造器
        Constructor constructor = c.getDeclaredConstructor(String.class,int.class);
        //由于Cat类中的有参构造器是private修饰的,所以需要设置禁止检查
        constructor.setAccessible(true);
        //最后再来利用获取的构造器来创建Cat对象
        Cat cat = (Cat) constructor.newInstance("小板",2);
        //再来查看对象是否创建成功
        System.out.println(cat);
    }
}

运行结果:

2.3 获取类的成员变量

Class 提供从类中获取成员变量的方法:

1)public Field[] getFields():获取类的全部成员变量(只能获取 public 修饰的)

代码演示:

import java.lang.reflect.Field;
public class demo7 {
    public static void main(String[] args) {
        //先获取Class对象
        Class c = Cat.class;
        //再获取成员变量(只能获取全部的public修饰的成员变量)
        Field[] fields = c.getFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
    }
}

运行结果:

2)public Field[] getDeclaredFields():获取类的全部成员变量(只要存在就能拿到)

代码演示:

import java.lang.reflect.Field;
public class demo7 {
    public static void main(String[] args) {
        //先获取Class对象
        Class c = Cat.class;
        //再获取成员变量(只能获取全部的成员变量)
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
    }
}

运行结果:

3)public Field[] getField(String name):获取类的某个成员变量(只能获取 public 修饰的)

代码演示:

import java.lang.reflect.Field;
public class demo8 {
    public static void main(String[] args) throws NoSuchFieldException {
        Class c = Cat.class;
        //根据名字来获取变量对象
        Field fieldName = c.getField("name");
        System.out.println(fieldName.getName());
        Field fieldAge = c.getField("age");
        System.out.println(fieldAge.getName());
    }
}

运行结果:

4)public Field[] getDeclaredField(String name):获取类的某个成员变量(只要存在就能拿到)

代码演示:

import java.lang.reflect.Field;
public class demo8 {
    public static void main(String[] args) throws NoSuchFieldException {
        Class c = Cat.class;
        //根据名字来获取变量对象
        Field fieldName = c.getDeclaredField("name");
        System.out.println(fieldName.getName());
        Field fieldAge = c.getDeclaredField("age");
        System.out.println(fieldAge.getName());
    }
}

运行结果:

获取到成员变量的作用:

获取到的成员变量对象可以用来赋值、取值操作。

常用方法:

1)void set(Object obj,Object value):赋值,第一个参数为 Cat 实例对象,第二个参数为需要赋值为 value 。

2)Object get(Object obj):取值,参数为 Cat 实例对象。

3)public void setAccessible(boolean flag):设置为 true ,表示禁止检查访问控制(暴力反射)

代码演示:

import java.lang.reflect.Field;
public class demo9 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //先获取class对象
        Class c = Cat.class;
        //Cat 的实例对象
        Cat cat = new Cat();
        //再获取变量对象
        Field fieldName = c.getDeclaredField("name");
        Field fieldAge = c.getDeclaredField("age");
        //禁止检查访问控制
        fieldName.setAccessible(true);
        fieldAge.setAccessible(true);
        //进行赋值操作
        fieldName.set(cat,"小板");
        fieldAge.set(cat,2);
        //再进行取值操作
        String name = (String) fieldName.get(cat);
        int age = (int) fieldAge.get(cat);
        //查看是否赋值成功
        System.out.println(cat);
    }
}

运行结果:

2.4 获取类的成员方法

Class 提供了从类中获取成员方法的 API :

1)Method[] getMethods():获取类的全部成员方法(只能获取 public 修饰的方法)

代码演示:


import java.lang.reflect.Method;
import java.util.Arrays;
public class demo10 {
    public static void main(String[] args) {
        //先获取class对象
        Class c = Cat.class;
        //再获取全部public修饰的方法
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName() + " =>" + Arrays.toString(method.getParameterTypes()) +  " => " + method.getReturnType());
        }
    }
}

运行结果:

2)Method[] getDeclaredMethods():获取类的全部成员方法(只要存在就能拿到)

代码演示:

import java.lang.reflect.Method;
import java.util.Arrays;
public class demo10 {
    public static void main(String[] args) {
        //先获取class对象
        Class c = Cat.class;
        //再获取全部public修饰的方法
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName() + " =>" + Arrays.toString(method.getParameterTypes()) +  " => " + method.getReturnType());
        }
    }
}

运行结果:

3)Method getMethod(String name,参数类型):获取类的某个成员方法(只能获取 public 修饰的)

代码演示:

import java.lang.reflect.Method;
import java.util.Arrays;
public class demo11 {
    public static void main(String[] args) throws Exception {
        
        //先获取class对象
        Class c = Cat.class;
        //再获取指定方法
        Method setNameMethod = c.getMethod("setName", String.class);
        System.out.println(setNameMethod.getName() + " => " + Arrays.toString(setNameMethod.getParameterTypes()) + " => " + setNameMethod.getReturnType());
        
    }
}

运行结果:

4)Method getDeclaredMethod(String name,参数类型):获取类的某个成员方法(只要存在就能拿到)

代码演示:

import java.lang.reflect.Method;
import java.util.Arrays;
public class demo11 {
    public static void main(String[] args) throws Exception {
        //先获取class对象
        Class c = Cat.class;
        //再获取指定方法
        Method setNameMethod = c.getDeclaredMethod("setName", String.class);
        System.out.println(setNameMethod.getName() + " => " + Arrays.toString(setNameMethod.getParameterTypes()) + " => " + setNameMethod.getReturnType());
    }
}

运行结果:

成员方法的作用:

执行某个对象的方法。

Methos 提供的方法:

1)public Object invoke(Object obj,方法的参数):触发某个对象的该方法执行。

2)public void setAccessible(boolean flag):设置为 true ,表示禁止检查访问控制(暴力反射)

代码演示:

import java.lang.reflect.Method;
public class demo12 {
    public static void main(String[] args) throws Exception {
        //先获取class对象
        Class c = Cat.class;
        //Cat对象
        Cat cat = new Cat();
        //再来获取指定的方法来设置名字,年龄
        Method setNameMethod = c.getDeclaredMethod("setName", String.class);
        setNameMethod.setAccessible(true);
        setNameMethod.invoke(cat,"小板");
        Method setAgeMethod = c.getDeclaredMethod("setAge", int.class);
        setNameMethod.setAccessible(true);
        setAgeMethod.invoke(cat,2);
        //查看是否设置成功
        Method getNameMethod = c.getDeclaredMethod("getName");
        getNameMethod.setAccessible(true);
        String name = (String) getNameMethod.invoke(cat);
        System.out.println(name);
        Method getAgeMethod = c.getDeclaredMethod("getAge");
        getAgeMethod.setAccessible(true);
        int age = (int) getAgeMethod.invoke(cat);
        System.out.println(age);
        System.out.println(cat);
    }
}

运行结果:

3.0 使用反射做一个简易版的框架

需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。

实现步骤:

1)定义一个方法,可以接收任意对象。

2)每收到一个对象后,使用反射获取该对象的 class 对象,然后获取全部成员变量。

3)遍历成员遍历,然后提取成员变量在该对象中具体的值。

4)把成员变量名和值,写出到文件中即可。

代码如下:

学生类:

public class Student {
    public String name;
    public int age;
    public double height;
    public String gender;
    public String description;
    public Student(String name, int age, double height, String gender, String description) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.gender = gender;
        this.description = description;
    }
}

老师类:

public class Teacher {
    public String name;
    public int age;
    public double height;
    public String gender;
    public String description;
    public Teacher(String name, int age, double height, String gender, String description) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.gender = gender;
        this.description = description;
    }
}

核心业务代码:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class Project {
    public static void business(Object obj) throws IllegalAccessException, FileNotFoundException {
        //创建I/O对象,可追加
        PrintStream ps = new PrintStream(new FileOutputStream("D:\software\code\2023_java\2023_java_code\code_24_5_3\src\Reflection\Text.text",true));
        //先获取class对象
        Class c = obj.getClass();
        ps.println("==============" + c.getName() + "==============");
        //再获取全部成员变量
        Field[] fields = c.getDeclaredFields();
        //将其进行遍历
        for (Field field : fields) {
            String name = field.getName();
            String value = field.get(obj) + "";
            ps.println(name + " => " + value);
        }
        //记得关闭资源
        ps.close();
    }
}

对业务代码进行测试:

import org.junit.Test;
import java.io.FileNotFoundException;
public class TestProject {
    //对该项目业务进行测试:
    @Test
    public void testProject() throws FileNotFoundException, IllegalAccessException {
        Teacher teacher = new Teacher("小板",20,175.5,"男","热爱学习技术且分享技术");
        Teacher teacher1 = new Teacher("小狗",22,179.5,"男","热爱骨头且分享骨头");
        Teacher teacher2 = new Teacher("小扳手",22,174.5,"男","热爱奋斗");
        Student student = new Student("小童",21,170.0,"女","爱笑");
        Student student1 = new Student("小黑",20,178.0,"女","爱哭");
        Project.business(teacher);
        Project.business(teacher1);
        Project.business(teacher2);
        Project.business(student);
        Project.business(student1);
    }
}

测试结果:

生成的文件:

文章来源:https://blog.csdn.net/Tingfeng__/article/details/138410068



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容