> 技术文档 > Java高级技术(单元测试、反射、注解、动态处理)学习笔记(上)_java.perform

Java高级技术(单元测试、反射、注解、动态处理)学习笔记(上)_java.perform


Java高级技术(单元测试反射、注解、动态处理)学习笔记

1.单元测试

1.1 单元测试快速入门

所谓单元测试,就是针对最小的功能单元,编写测试代码对其进行正确性测试。

什么是单元测试?

  • 单元测试,就是针对最小的功能单元,编写测试代码对其进行正确性测试。

具体步骤

  1. 将Junit框架的jar包导入到项目中(注意:IDEA集成了Junit框架,不需要我们自己手工导入了)
  2. 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
  3. 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试
  4. 开始测试:选中测试方法,右键选择“JUnit运行” ,如果测试通过则是绿色;如果测试失败,则是红色

1.2 单元测试断言

所谓断言:意思是程序员可以预测程序的运行结果,检查程序的运行结果是否与预期一致。

什么是单元测试断言?

  • 所谓断言:意思是程序员可以预测程序的运行结果,检查程序的运行结果是否与预期一致。在JUnit中,可以使用assert方法来编写断言。如果断言失败,测试用例就会失败。

示例代码:

public class StringUtilTest{ @Test public void testGetMaxIndex(){ int index1 = StringUtil.getMaxIndex(null); System.out.println(index1);  int index2 = StringUtil.getMaxIndex(\"admin\"); System.out.println(index2); //断言机制:预测index2的结果 Assert.assertEquals(\"方法内部有Bug\",4,index2); }}

1.3 Junit框架的常用注解

除了@Test注解,还有一些其他的注解,我们要知道其他注解标记的方法什么时候执行,以及其他注解在什么场景下可以使用。

@Test 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行
@Before 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次
@After 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeClass 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次
@AfterClass用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。

1.4 课后例题

例一:编写一个JUnit测试用例,测试一个计算矩形面积的方法
①编写一个JUnit测试类RectangleTest,使用JUnit的注解和断言测试Rectangle类的getArea方法。
②使用@Test注解标记测试方法。使用@BeforeClass注解修饰静态的初始化方法设置长方形的长和宽。
③使用assertEquals断言验证计算结果是否正确。
Rectangle 代码如下:

`public class Rectangle { private Integer width; private Integer height; public Rectangle(Integer width, Integer height) { this.width = width; this.height = height; } public Integer getArea() { return width * height; } }
public class RectangleTest { private static Rectangle rectangle; @BeforeClass public static void setUp() { rectangle = new Rectangle(5, 10); } @Test public void testGetArea() { Integer expected = 50; Integer actual = rectangle.getArea(); assertEquals(\"The area of the rectangle should be 50.0\", expected, actual); } }

例二:开发了一个购物车类ShoppingCart ,现在需要编写一个测试方法来检查添加商品到购物车的功能是否按预期工作。
①使用JUnit框架来编写测试方法,
②使用@BeforeEach注释来初始化购物车对象,
③使用@AfterEach注释来清除购物车对象。
④在测试方法中,使用assert语句来检查添加商品到购物车的数量和价格功能是否按预期工作。

ShoppingCart 代码如下:import java.util.ArrayList;import java.util.List;public class ShoppingCart { private List<Item> items; public ShoppingCart() { items = new ArrayList<Item>(); } public void addItem(Item item) { items.add(item); } public void removeItem(Item item) { items.remove(item); } public int getItemCount() { return items.size(); } public double getTotalPrice() { double totalPrice = 0; for (Item item : items) { totalPrice += item.getPrice(); } return totalPrice; }}Item类代码如下public class Item { private String name; private double price; public Item(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; }}
public class ShoppingCartTest { private ShoppingCart cart; @BeforeEach public void setUp() { cart = new ShoppingCart(); } @AfterEach public void tearDown() { cart = null; } @Test public void testAddItem() { Item item = new Item(\"Shoes\", 50); car反射是用来写框架用的t.addItem(item); assertEquals(1, cart.getItemCount()); assertEquals(50, cart.getTotalPrice()); }}

2.反射

反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)。

反射的作用:反射是用来写框架用的

获取类的信息的步骤:

  1. 反射第一步:加载类,获取类的字节码:Class对象
  2. 获取类的构造器:Constructor对象
  3. 获取类的成员变量:Field对象
  4. 获取类的成员方法:Method对象

2.1 获取类的字节码Class

获取Class对象的三种方式

  1. Class c1 = 类名.class调用Class提供方法:public static Class forName(String package);

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

  3. Class c3 = 对象.getClass();

2.2 获取类的构造器Constructor

Class提供了从类中获取构造器的方法。

方法 说明 Constructor[] getConstructors() 获取全部构造器(只能获取public修饰的) Constructor[] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到) Constructor getConstructor(Class… parameterTypes) 获取某个构造器(只能获取public修饰的) Constructor getDeclaredConstructor(Class… parameterTypes) 获取某个构造器(只要存在就能拿到)

获取类构造器的作用:依然是初始化对象返回

Constructor****提供的方法 说明 T newInstance(Object… initargs) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

2.3 反射获取成员变量&使用Field

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

方法 说明 public Field[] getFields() 获取类的全部成员变量(只能获取public修饰的) public Field[] getDeclaredFields() 获取类的全部成员变量(只要存在就能拿到) public Field getField(String name) 获取类的某个成员变量(只能获取public修饰的) public Field getDeclaredField(String name) 获取类的某个成员变量(只要存在就能拿到)

获取到成员变量的作用:依然是赋值、取值。

方法 说明 void set(Object obj, Object value): 赋值 Object get(Object obj) 取值 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

2.4 反射获取成员方法

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

方法 说明 Method[] getMethods() 获取类的全部成员方法(只能获取public修饰的) Method[] getDeclaredMethods() 获取类的全部成员方法(只要存在就能拿到) Method getMethod(String name, Class… parameterTypes) 获取类的某个成员方法(只能获取public修饰的) Method getDeclaredMethod(String name, Class… parameterTypes) 获取类的某个成员方法(只要存在就能拿到)

成员方法的作用:依然是执行

Method****提供的方法 说明 public Object invoke(Object obj, Object… args) 触发某个对象的该方法执行。 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

2.5 课后例题

例一:在开发过程中,我们会需要获取一个类中的成员属性,并且知道该属性的类型,请使用反射完成该功能:
①定义一个Person类,定义两个私有属性name,age
②在测试类中获取Person类中的成员属性 并将 成员属性和类型打印在控制台

public class Demo { public static void main(String[] args) throws Exception { // 获取类的实例对象 Class<?> clazz = Person.class; // 获取所有属性 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // 设置为可访问 System.out.println(field.getName() + \" \" + field.getType()); } }}public class Person { private String name; private String age;}

例二:动态调用的好处就是可以在类不可知的情况下完成编码,而且可以随意更改具体实现的类。请按照如下要求模拟动态调用
①定义一个Student类,类中属性name(String),age(Int),并生成setter和getter方法
②在测试类中定义一个学生对象,并对其进行赋值
③利用反射获取Student的getName方法得到stu的name属性,打印在控制台
④利用反射获取Student的setName方法修改stu的name属性,并将stu打印在控制台

public class Demo { public static void main(String[] args) throws Exception { Student stu = new Student(); stu.setAge(1); stu.setName(\"zhao\"); System.out.println(stu); // 1.无参 Method method = stu.getClass().getMethod(\"getName\"); String name = (String)method.invoke(stu); System.out.println(name); // 2.一参 Method method2 = stu.getClass().getMethod(\"setName\", Class.forName(\"java.lang.String\")); method2.invoke(stu, \"meng\"); System.out.println(stu); }}public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return \"Student{\" + \"name=\'\" + name + \'\\\'\' + \", 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; }}

例三:设计一个程序,完成以下功能
1.在项目下创建一个conf.properties文件,文件内容为:
className=com.boxuegu.Person name=ls age=18
2.创建一个Person类,有两个String类型的属性name,age,生成setter和getter方法,并重写toString方法
3.创建一个测试类,解析配置文件,利用反射技术创建className这一项指定的对象,并将配置文件中的内容赋值到该对象

public class AppTest { public static void main(String[] args) throws Exception { //1,将 conf.properties文件中的信息读取到 Properties 对象中 Properties properties = new Properties(); properties.load(new FileReader(\"conf.properties\")); //2,获取 配置文件中的 className 然后通过反射创建对象 String className = properties.getProperty(\"className\"); //2.1 通过全路径获取Class Class<?> aClass = Class.forName(className); //2.2,创建实例对象 Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); Object instance = declaredConstructor.newInstance(); /*// 不太优雅 Method setName = aClass.getDeclaredMethod(\"setName\", String.class); setName.invoke(instance,properties.getProperty(\"name\")); Method setAge = aClass.getDeclaredMethod(\"setAge\", String.class); setAge.invoke(instance,properties.getProperty(\"age\")); System.out.println(instance);*/ //3,获取所有的方法 Method[] methods = aClass.getDeclaredMethods(); for (Method method : methods) { //4,判断是否是set方法 if (method.getName().startsWith(\"set\")) { //5,通过方法名得到一个key,并从 Properties 拿到值 String key = method.getName().substring(3,4).toLowerCase() + method.getName().substring(4 ); //6,执行该set方法 String property = properties.getProperty(key); if (property!=null) {  method.invoke(instance,property); } } } //7,打印对象 System.out.println(instance); }}public class Person { private String name; private String age; private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return \"Person{\" + \"name=\'\" + name + \'\\\'\' + \", age=\'\" + age + \'\\\'\' + \", address=\'\" + address + \'\\\'\' + \'}\'; }}