首页 > 基础资料 博客日记
JavaEE 初阶篇-深入了解 Junit 单元测试框架和 Java 中的反射机制(使用反射做一个简易版框架)
2024-05-11 06:00:04基础资料围观280次
🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1.2 Junit 单元测试框架的常用注解(Junit 4.xxx 版本)
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); } }
测试结果:
生成的文件:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: