16 Junit单元测试框架、反射、注解、动态代理(黑马Java视频笔记)

发布于:2025-03-30 ⋅ 阅读:(28) ⋅ 点赞:(0)

在这里插入图片描述

单元测试

单元测试

  • 针对最小的功能单元:方法,编写测试代码对其进行正确性测试

之前在main方法中测试代码,无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试,也无法得到测试的报告

Junit单元测试框架

1、认识
  • 可以用来对方法进行测试,是第三方公司开源出来的
  • 可以灵活编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的测试
  • 独立测试,一个测试不会影响另一个方法的测试
2、使用步骤

需求

  • 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试

1) 先定义一个工具类
/**
 * 字符串工具类
 */
public class StringUtil {
    public static void printNumber(String name){
        if(name == null){
            System.out.println("名字不能为空");
            return ;
        }
        System.out.println("名字长度是:"+name.length());
    }

    /**
     * 求字符串的最大索引
     */
    public static int getMaxIndex(String data){
        if(data == null || "".equals(data)){
            return -1;
        }
        return data.length() -1;
    }
}
2)编写测试类
⭐测试方法:
  • 必须是公开public,无参,无返回值
  • 测试方法必须加上@Test注解
// 测试类:junit单元测试框架,对业务类中的业务方法进行正确性测试
public class StringUtilTest {

    @Test
    public void testPrintNumber() {
        // 测试步骤:
        StringUtil.printNumber("张无忌");  // 预期结果:3
        // 测试用例
        StringUtil.printNumber("");
        StringUtil.printNumber(null);
    }

    @Test
    public void testGetMaxIndex() {
        // 测试步骤:
        int index = StringUtil.getMaxIndex("abcdefg");  // 预期结果:6
        // 测试用例
        int index2 = StringUtil.getMaxIndex("");
        int index3 = StringUtil.getMaxIndex(null);

        System.out.println(index);
        System.out.println(index2);
        System.out.println(index3);

        // 断言测试:断言结果是否与预期结果一致
        Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",6, index);
        Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",-1, index2);
        Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",-1, index3);

    }
}
⭐断言测试:

断言结果是否与预期结果一致,有时候没有报错,但是可能不是预期的结果 → 黄色警告


反射

反射

1. 认识反射、获取类

反射

  • 加载类,并允许以编程的方式解剖内中的成分(成员变量、方法、构造器等)
    • 访问有关已加载类的字段、方法和构造函数的信息,以及使用反射字段,方法和构造函数在封装和安全限制内对其底层对应项进行操作
1.1 获取Class对象

  • Student类
@Data
public class Student {
    private String name;
    private int age;
    private double score;
    private String sex;
}
  • 测试方法
public static void main(String[] args) throws Exception {
    // 目标:掌握反射第一步操作:获取Class对象(获取类本身)
    // 1、获取类本身:类.class
    Class cls = Student.class;
    System.out.println(cls);    // class com.study.demo2reflect.Student
    String className = cls.getSimpleName(); // 获取类简称
    System.out.println(className);  // Student
    String fullName = cls.getName();
    System.out.println(fullName);   // 获取类全名 com.study.demo2reflect.Student


    // 2、获取类本身:Class.forName("类全名")
    Class cls2 = Class.forName("com.study.demo2reflect.Student");
    System.out.println(cls2);

    // 3、获取类本身:对象.getClass()
    Student s = new Student();
    Class cls3 = s.getClass();
    System.out.println(cls3);

    System.out.println(cls == cls2);	// true
    System.out.println(cls == cls3);	// true
}

2. ⭐获取类中的成分、并对其进行操作

💡关系反转

🤔个人理解:之前是拿着类对象去访问该类的成员变量、构造器以及成员方法;反射是获取类中成分后,就是拿着成分去找对象操作自己可以操作的部分

2.1 获取构造器并操作

  • Dog类
public class Dog {
    private String name;
    private int age;

    private Dog() {
    }
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void eat(String food) {
        System.out.println(this.name + "在吃" + food);
    }

    public void run() {
        System.out.println(this.name + "在跑");
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 测试方法
// 2、获取类的构造器对象并对其进行操作
@Test
public void getConstructor() throws Exception {
    // 目标:获取类的构造器对象并对其进行操作
    // 1、反射第一步:获取Class对象
    Class cls = Dog.class;
    // 2、获取构造器对象
    Constructor[] cons = cls.getDeclaredConstructors(); //只要有就拿,不管权限
    for(Constructor con:cons){
        System.out.println(con.getName()+"("+con.getParameterTypes()+")");
    }
    // 3、获取单个构造器
    Constructor con = cls.getDeclaredConstructor(); // 获取无参构造器
    System.out.println(con.getName()+"("+con.getParameterCount()+")");


    Constructor con2 = cls.getConstructor(String.class,int.class);  // 获取有参构造器
    System.out.println(con2.getName()+"("+con2.getParameterCount()+")");
    System.out.println("====================================================");

    // 4、通过获取的构造器创建对象
    // 无参构造器是私有方法 → 暴力反射:可以绕过访问权限
    con.setAccessible(true);
    Dog d1 = (Dog) con.newInstance();
    System.out.println(d1.toString());

    Dog d2 = (Dog) con2.newInstance("旺财", 5);
    System.out.println(d2.toString());

}

2.2 获取类的成员变量并操作

// 3、获取类的成员变量对象并对其进行操作
@Test
public void getFieldInfo() throws Exception {
    // 1、获取Class对象
    Class cls = Dog.class;
    // 2、获取成员变量对象
    Field[] fields = cls.getDeclaredFields();   // 只要有就拿,不管权限
    for(Field field:fields){
        System.out.println(field.getName() + "(" + field.getType().getName() + ")");
    }
    System.out.println("========================================");
    // 3、获取单个成员变量对象
    Field field = cls.getDeclaredField("name");
    System.out.println(field.getName() + "(" + field.getType().getName() + ")");

    // 4、通过获取的成员变量对象操作成员变量
    Dog d = new Dog("旺财", 5);
    System.out.println(d);
    field.setAccessible(true);      // 暴力反射:可以绕过访问权限
    field.set(d, "大黄");
    System.out.println(d);
}

在这里插入图片描述

2.3 获取类的成员方法并操作

在这里插入图片描述

// 4、获取类的成员方法对象并对其进行操作
@Test
public void getMethodInfo() throws Exception {
    // 1、获取Class对象
    Class cls = Dog.class;
    // 2、获取成员方法对象
    Method[] methods = cls.getDeclaredMethods();   // 只要有就拿,不管权限
    for(Method method:methods){
        System.out.println(method.getName() + "(" + method.getParameterCount() + ")");
    }
    System.out.println("========================================");
    // 3、获取单个成员方法对象
    Method eat = cls.getDeclaredMethod("eat", String.class);
    System.out.println(eat.getName() + "(" + eat.getParameterCount() + ")");
    Method run = cls.getDeclaredMethod("run");
    System.out.println(run.getName() + "(" + run.getParameterCount() + ")");
    // 4、通过获取的成员方法对象操作成员方法
    Dog d = new Dog("旺财", 5);
    eat.invoke(d, "骨头");
    run.invoke(d);
}

在这里插入图片描述

3. 作用、应用场景

3.1 作用
  • 可以得到一个类的全部成分然后操作
  • 可以破坏封装性(私有的那些)(单例不单例,私有不私有)
  • 可以绕过泛型的约束
public static void main(String[] args) throws Exception {
        // 目标:反射的基本作用
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("java");
//        list.add(34);         // 报错,泛型类型,只能放指定的类型

        // 通过反射绕过泛型的约束
        Class cls = list.getClass();
        Method add = cls.getDeclaredMethod("add",Object.class);
        add.invoke(list,34);
        add.invoke(list,true);
        System.out.println(list);	// [hello, world, java, 34, true]
    }

在这里插入图片描述

  • 最重要:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能
3.2 反射做通用框架

在这里插入图片描述
在这里插入图片描述

1)通用方法:接收任意对象,并存储对象信息
public class SaveObjectFrameWork {
    // 保存任意对象的静态方法
    public static void saveObject(Object obj) throws Exception {
        // 使用打印流,包装可以追加字节输出流
        PrintStream ps = new PrintStream(new FileOutputStream("F:\\object.txt",true));

        // 1. 获取字节码文件对象
        Class cls = obj.getClass();
        String className = cls.getSimpleName();
        ps.println("==============="+className+"=========================");
        // 2. 获取全部成员变量
        Field[] fields = cls.getDeclaredFields();
        for(Field field:fields){
            // 获取成员变量的名字和值
            field.setAccessible(true);
            String fieldName = field.getName();
            Object fieldValue = field.get(obj) + ""; // 转换为字符串
            System.out.println(fieldName + "=" + fieldValue);
            ps.println(fieldName + "=" + fieldValue);
        }
        ps.close();
    }
}
2)定义学生类和老师类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name;
    private int age;
    private double score;
    private String sex;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private String name;
    private int age;
    private double salary;
}
3)定义测试类:创建对象,并调用存储对象的方法
public class ReflectDemo4 {
    public static void main(String[] args) {
        Student s1 = new Student("小明", 14, 90, "男");
        Teacher t1 = new Teacher("张老师",26, 5000);

        SaveObjectFrameWork sof = new SaveObjectFrameWork();
        try {
            sof.saveObject(s1);
            sof.saveObject(t1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述


注解

注解

1. 概述、自定义注解

1.1 注解
  • Java代码里的特殊标记,比如:@Override、@Test等

  • 让其他程序根据注解信息来决定怎么执行该程序

  • 可以用在类上、构造器上、方法上、成员变量上、参数上等

1.2 自定义注解
1)使用

在这里插入图片描述

  • 自定义注释
public @interface A {
    String value();
    // 特殊属性:默认在使用时,如果只有一个value属性,那么value属性可以省略不写
}

public @interface B {
    String value();
    // 特殊属性:默认在使用时,如果只有一个value属性,那么value属性可以省略不写
    String hobby() default "打游戏";
}

public @interface MyBook {
    String name();
    int age() default 18;
    String[] address();
}
  • 使用注释
@MyBook(name = "leo", age = 20, address = {"北京", "上海"})
//@A(value = "leo")
@A("leo") //  如果只有一个value属性,那么value属性可以省略不写
@B("lay")
public class AnnotationDemo1 {
    @MyBook(name = "lay", age = 28, address = {"北京", "上海"})
    public static void main(@A("why") String[] args) {
        // 目标:自定义注解
        @A("what")
        int a;
    }
}
2)原理

在这里插入图片描述

2. 元注解

指的是注解注解的注解:放在注解上的注解

@target:约束注解的范围

Retention:声明注解的保留周期,一般都是保留到编译运行时RUNTIME
在这里插入图片描述

@Retention(RetentionPolicy.RUNTIME)     // 注解的保留策略
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD})    // 限制注解的声明位置
public @interface StudentLimit {
    int minAge() default 18;
}

3. 注解的解析

判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来

3.1 如何解析注解
  • 要解析谁的注解,就应该先拿到谁
    • Class、Method、Field、Constructor、都实现了AnnotatedElement接口,他们都拥有解析注解的能力


1)注解MyTest4
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
    String value();
    double aaa() default 100;
    String[] bbb();
}
2)Demo类
@MyTest4(value = "aaa", aaa = 100, bbb = {"aaa","bbb"})
public class Demo {
    @MyTest4(value = "bbb",bbb={"aaa","bbb"})
    public void test1(){

    }

}
3)定义AnnotationTest3测试类
  • 解析类的注释
public static void main(String[] args) throws Exception {
    // 目标:解析注释
    parseClass();
    parseMethod();
}

public static void parseClass() {
    // 1. 获取类对象
    Class cls = Demo.class;
    // 2. 获取注解对象:先判断是否使用了该注解,再获取注解对象
    if (cls.isAnnotationPresent(MyTest4.class)) {
        // 3. 获取注解对象
        MyTest4 myTest4 = (MyTest4) cls.getAnnotation(MyTest4.class);

        // 4. 获取注解的属性值
        String value = myTest4.value();
        double aaa = myTest4.aaa();
        String[] bbb = myTest4.bbb();

        // 5. 打印
        System.out.println(value);      // aaa
        System.out.println(aaa);    // 100.0
        System.out.println(Arrays.toString(bbb));   // [aaa, bbb]
    }
}
  • 解析成员方法的注释
public static void parseMethod() throws Exception {
    // 1. 获取类对象
    Class cls = Demo.class;
    // 2、获取方法对象
    Method method = cls.getDeclaredMethod("test1");
    // 3、判断方法是否使用了指定的注解
    if(method.isAnnotationPresent(MyTest4.class)){
        // 4、获取注解对象
        MyTest4 myTest4 = method.getAnnotation(MyTest4.class);

        // 5、获取注解的属性值
        String value = myTest4.value();
        double aaa = myTest4.aaa();
        String[] bbb = myTest4.bbb();

        // 6、打印
        System.out.println(value);  // bbb
        System.out.println(aaa);    // 100.0
        System.out.println(Arrays.toString(bbb));   // [aaa, bbb]
    }
}

4. 作用、应用场景

4.1 作用
  • 对程序做特殊标记,以方便别人针对性的处理
4.2 应用场景

需求

  • 定义若干个方法,只要加了MyTest注解,就会触发该方法执行

分析

① 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在

② 定义若干个方法,部分方法加上@MyTest

public class AnnotationDemo4 {

    public static void main(String[] args) throws Exception{
        AnnotationDemo4 ad = new AnnotationDemo4();
        // 1.获取类对象
        Class cls = ad.getClass();
        // 2.获取存在的方法
        Method[] methods = cls.getDeclaredMethods();
        // 3.遍历方法,判断方法上是否使用了指定的注解
        for(Method method: methods){
            // 判断方法上是否使用了指定的注解
            if(method.isAnnotationPresent(MyTest4.class)){
                // 4.如果有该注解就执行方法
                method.invoke(ad);
            }
        }
    }

    @MyTest4(value = "aaa", aaa = 100, bbb = {"aaa","bbb"})
    public void test1(){
        System.out.println("test1方法执行啦~");
    }
    public void test2(){
        System.out.println("test2方法执行啦~");
    }
    public void test3(){
        System.out.println("test3方法执行啦~");
    }
    @MyTest4(value = "bbb",bbb={"aaa","bbb"})
    public void test4(){
        System.out.println("test4方法执行啦~");
    }
}

// test1方法执行啦~
// test4方法执行啦~
4.3 理解注解的属性

对该成分做特殊处理:例如多次执行某一方法
在这里插入图片描述


动态代理

动态代理

1. 什么是代理

在这里插入图片描述

1.1 如何为Java对象创建一个代理对象?

在这里插入图片描述

1) Star类和StarService接口

在这里插入图片描述

2)代理类

通过需代理任务的接口连接代理对象实际执行任务的对象

  • 创建代理对象方法的返回类型:StarService接口对象
/**
 * 代理工具类:专门负责创建代理对象并返回给别人使用
 */
public class ProxyUtil {
    // 创建一个明星对象的代理对象
    public static StarService createProxy(Star s) {
        /**
         * 参数一:用于执行用哪个类加载器去加载生成的代理类
         * 参数二:用于指定代理类需要实现的接口
         * 参数三:用于指定代理对象需要调用哪个类中的方法
         */
        StarService proxy = (StarService) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                s.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 用来声明代理对象要干的事
                        // 参数一:proxy接收到代理对象本身(暂时用处不大)
                        // 参数二:method接收到代理对象调用的方法
                        // 参数三:args接收到代理对象调用的方法的参数
                        System.out.println("代理对象开始工作了...");
                        String methodName = method.getName();
                        if("sing".equals(methodName)){
                            System.out.println("准备话筒,收费");
                        } else if ("dance".equals(methodName)) {
                            System.out.println("准备场地,收费");
                        }
                        // 找真正的明星对象来执行被代理的行为:method方法
                        Object result = method.invoke(s, args);

                        return result;
                    }
                }
        );
        return proxy;
    }
}

在这里插入图片描述

2. 解决实际问题、掌握使用代理的好处

在这里插入图片描述

1)用户类和用户管理类(接口-实现)

在这里插入图片描述

/**
 * 用户业务实现类
 */
public class UserServiceImpl implements UserService{

    @Override
    public void login(String loginName, String password) throws Exception {
//        long start = System.currentTimeMillis(); // 记录开始时间 1970年1月1日0时0分0秒0毫秒 走到此刻的总毫秒值

        if("admin".equals(loginName) && "123".equals(password)){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
        Thread.sleep(1000);

//        long end = System.currentTimeMillis();
//        System.out.println("login方法耗时:"+(end-start)/1000.0+"秒");
    }

    @Override
    public void delete(String id) throws Exception {
//        long start = System.currentTimeMillis();

        System.out.println("删除用户成功");
        Thread.sleep(1500);

//        long end = System.currentTimeMillis();
//        System.out.println("delete方法耗时:"+(end-start)/1000.0+"秒");

    }

    @Override
    public String[] selectUsers() throws Exception{
//        long start = System.currentTimeMillis();

        System.out.println("查询用户成功");
        String[] names = {"张三","李四","王五","赵六","孙七"};
        Thread.sleep(500);

//        long end = System.currentTimeMillis();
//        System.out.println("selectUsers方法耗时:"+(end-start)/1000.0+"秒");
        return names;
    }
}

🤔记录方法耗时情况的代码具有很大的重复性,且不太适合归类到用户管理类的方法中

2)使用代理类,记录方法耗时情况
public class ProxyUtil {
    public static UserService createProxy(UserService userService) {
        UserService proxy = (UserService) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理对象开始工作了...");
                        long start = System.currentTimeMillis();

                        // 真正调用业务对象被代理的方法
                        Object result = method.invoke(userService, args);

                        long end = System.currentTimeMillis();
                        System.out.println(method.getName() + "耗时:" + (end - start) / 1000.0 + "秒");
                        System.out.println("代理对象结束了工作了...");

                        return result;
                    }
                });

        return proxy;
    }
}
3)使用代理执行方法
public static void main(String[] args) throws Exception {
    // 1、创建用户业务对象
    UserService userService = new UserServiceImpl();
    // 创建代理对象
    userService = ProxyUtil.createProxy(userService);
    // UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

    // 2、调用用户业务的功能
    userService.login("admin", "123");

    userService.delete("1");

    String[] names = userService.selectUsers();
    System.out.println(Arrays.toString(names));
}

在这里插入图片描述

💡如果要让该代理可以接收任意对象,测试任意对象的方法运行耗时,只需将对象限定改为泛型即可
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到