单元测试
- 针对最小的功能单元(方法)进行正确性测试
- 编写正规的单元测试框架
- 传统的无法执行自动化测试,且无法得到测试报告
Junit单元测试框架
- Junit的作用:
具体步骤
- 测试类取名:原类名+Test(大驼峰)
- 测试方法取名:test+原函数名称(小驼峰)
- 测试方法:必须public,无参,无返回值
- 测试方法上面必须加上@Test方法,不加不会跑的
- 测试方法只能判断程序能否正常运行,并不能判断功能是否正确,需要加断言(Assert)
publicclassStringUtilTest{
@Test // 测试方法publicvoidtestPrintNumber(){// StringUtil.printNumber("admin");
StringUtil.printNumber(null);}}
公共的(Public):JUnit框架在运行测试时,需要能够访问到测试方法。如果测试方法不是公共的,JUnit框架可能无法访问到它,从而无法执行测试。
无参数的:JUnit框架在运行测试时,会自动调用测试方法,而不需要手动传入参数。如果测试方法需要参数,JUnit框架无法知道应该传入什么值,因此,测试方法不能有参数。
无返回值的(Void):JUnit框架通过断言(Assert)来检查测试是否通过,而不是通过返回值。因此,测试方法不需要返回值。
Assert断言(重要)
@Test // 测试方法publicvoidtestGetMaxIndex(){int index1 = StringUtil.getMaxIndex(null);
System.out.println(index1);int index2 = StringUtil.getMaxIndex("admin");
System.out.println(index2);// 断言机制:程序员可以通过预测业务方法的结果。
Assert.assertEquals("方法内部有bug!",4, index2);}
- Assert.assertEquals(提示信息,希望的输出,实际输出)
自动化测试
- 自动化测试:IDEA中直接右键run就行(run整个类就是run所有有Test注解的方法)。
- 右键项目 run all_tests,测试所有
其他常见注解
publicclassStringUtilTest{
@Before //每次测试类的每个方法之前都执行一次publicvoidtest1(){
System.out.println("---> test1 Before 执行了---------");}
@BeforeClass //整个类只执行一次publicstaticvoidtest11(){
System.out.println("---> test11 BeforeClass 执行了---------");}
@After
publicvoidtest2(){
System.out.println("---> test2 After 执行了---------");}
@AfterClass
publicstaticvoidtest22(){
System.out.println("---> test22 AfterClass 执行了---------");}
@Test // 测试方法publicvoidtestPrintNumber(){// StringUtil.printNumber("admin");
StringUtil.printNumber(null);}}
- 在JUnit 5之后改名了:
反射
- 作用:加载类,并编程解析类中的各种成分(比如成员变量、方法、构造器等)
- 关键:如何获取类的各种信息
加载类
- 三种方法:
(方法三必须先new一个对象才能得到对应的类的class对象)
publicstaticvoidmain(String[] args) throws Exception {
Class c1 = Student.class;
System.out.println(c1.getName());// 全类名
System.out.println(c1.getSimpleName());// 简名:Student
Class c2 = Class.forName("com.itheima.d2_reflect.Student");
System.out.println(c1 == c2);
Student s =newStudent();
Class c3 = s.getClass();
System.out.println(c3 == c2);}
(forName的参数应该是类的全类名:包名.类名)
获取类的构造器
- 不加Declare只能拿public的,加了就都能拿
- 拿单个构造器应该表明参数类型(String.class, int.class)(不然不知道是纳哪个构造器)
- 拿单个的时候会报异常,可以throws抛出
publicvoidtestGetConstructors(){// 1、反射第一步:必须先得到这个类的Class对象
Class c = Cat.class;// 2、获取类的全部构造器// Constructor[] constructors = c.getConstructors();
Constructor[] constructors = c.getDeclaredConstructors();// 3、遍历数组中的每个构造器对象for(Constructor constructor : constructors){
System.out.println(constructor.getName()+"--->"+ constructor.getParameterCount());}}
Constructor constructor1 = c.getDeclaredConstructor();//无参构造器// 3、获取有参数构造器
Constructor constructor2 = c.getDeclaredConstructor(String.class,int.class);
获取构造器的作用:初始化对象返回(相当于new)
newInstance 初始化对象
Cat cat2 =(Cat) constructor2.newInstance("叮当猫",3);
找到对应的构造器,相当于new一个对象(实际开发还是用new),这里注意需要强转(Cat)
setAccessible 修改权限(暴力反射)
// 3、获取有参数构造器
Constructor constructor2 =
c.getDeclaredConstructor(String.class,int.class);
System.out.println(constructor2.getName()+"--->"+ constructor2.getParameterCount());
constructor2.setAccessible(true);// 禁止检查访问权限
Cat cat2 =(Cat) constructor2.newInstance("叮当猫",3);
System.out.println(cat2);
将原本private的构造器硬在外部禁止检查访问权限,无论如何都会new出一个对象。
获取类的成员变量
- 反射第一步:得到class对象
- 其他跟构造器的差不多,包括declare,setAccessible等
- getName拿到名字,getType拿到数据类型
publicvoidtestGetFields() throws Exception {// 1、反射第一步:必须是先得到类的Class对象
Class c = Cat.class;// 2、获取类的全部成员变量。
Field[] fields = c.getDeclaredFields();// 3、遍历这个成员变量数组for(Field field : fields){
System.out.println(field.getName()+"---> "+ field.getType());}// 4、定位某个成员变量
Field fName = c.getDeclaredField("name");
System.out.println(fName.getName()+"--->"+ fName.getType());
Field fAge = c.getDeclaredField("age");
System.out.println(fAge.getName()+"--->"+ fAge.getType());}
获取成员变量的作用:赋值(set)和取值(get)
// 赋值
Cat cat =newCat();
fName.setAccessible(true);// 禁止访问控制权限
fName.set(cat,"卡菲猫");
System.out.println(cat);// 取值
String name =(String) fName.get(cat);
System.out.println(name);
- 必须有对象才能有对象的成员变量
- set(变量名,值)
- get(对象名),需要强转。
setAccessible 修改权限(暴力反射)
fName.setAccessible(true);// 禁止访问控制权限
获取类的成员方法
- getName拿方法名,getParameterCount拿参数个数,getReturnType拿返回类型
- getDeclaredMethod要写参数类型,不然不知道找的是哪个同名函数
publicvoidtestGetMethods() throws Exception {// 1、反射第一步:先得到Class对象。
Class c = Cat.class;// 2、获取类的全部成员方法。
Method[] methods = c.getDeclaredMethods();// 3、遍历这个数组中的每个方法对象for(Method method : methods){
System.out.println(method.getName()+"--->"+ method.getParameterCount()+"---->"+ method.getReturnType());}// 4、获取某个方法对象
Method run = c.getDeclaredMethod("run", String.class);// 拿run方法,无参数的
获取类方法的作用:执行invoke
Cat cat =newCat();
run.setAccessible(true);// 禁止检查访问权限
Object rs = run.invoke(cat);// 调用无参数的run方法,用cat对象触发调用的。//这里无返回值则返回null
System.out.println(rs);
eat.setAccessible(true);// 禁止检查访问权限
String rs2 =(String) eat.invoke(cat,"鱼儿");
System.out.println(rs2);
反射的应用场景
示例
框架:
publicclassObjectFrame{// 目标:保存任意对象的字段和其数据到文件中去publicstaticvoidsaveObject(Object obj) throws Exception {
PrintStream ps =newPrintStream(newFileOutputStream("junit-reflect-annotation-proxy-app\\src\\data.txt",true));// obj是任意对象,到底有多少个字段要保存。
Class c = obj.getClass();
String cName = c.getSimpleName();
ps.println("---------------"+ cName +"------------------------");// 2、从这个类中提取它的全部成员变量
Field[] fields = c.getDeclaredFields();// 3、遍历每个成员变量。for(Field field : fields){// 4、拿到成员变量的名字
String name = field.getName();// 5、拿到这个成员变量在对象中的数据。
field.setAccessible(true);// 禁止检查访问控制
String value = field.get(obj)+"";
ps.println(name +"="+ value);}
ps.close();}}
测试:
publicclassTest5Frame{
@Test
publicvoidsave() throws Exception {
Student s1 =newStudent("黑马吴彦祖",45,'男',185.3,"蓝球,冰球,阅读");
Teacher t1 =newTeacher("播妞",999.9);// 需求:把任意对象的字段名和其对应的值等信息,保存到文件中去。
ObjectFrame.saveObject(s1);
ObjectFrame.saveObject(t1);}}
注解(Annotation)
- java中的特殊标记
- 作用:让其他程序根据注解信息决定怎么执行该程序。
- 可以用在类上、构造器上、方法上、成员变量上、参数上等。
自定义注解
/**
* 自定义注解
*/public @interface MyTest1 {
String aaa();
boolean bbb()defaulttrue;
String[]ccc();}
如何使用?
@MyTest1(aaa="牛魔王", ccc={"HTML","Java"})publicclassAnnotationTest1{
@MyTest1(aaa="铁扇公主", bbb=false, ccc={"Python","前端","Java"})publicvoidtest1(){}publicstaticvoidmain(String[] args){}}
(有参数的一定要填,有默认参数的不必须要填)
特殊属性名:value
- 如果注解中只有一个value属性(或多余的数值为默认值default),使用@注解时可以不写value名称
public @interface MyTest2 {
String value();// 特殊属性intage()default23;}
@MyTest2("孙悟空")publicclassAnnotationTest1{
@MyTest1(aaa="铁扇公主", bbb=false, ccc={"Python","前端","Java"})publicvoidtest1(){}publicstaticvoidmain(String[] args){}}
注解的原理:本质是一个接口!!!
- 把注解编译成class再反
- 本质是一个继承Annotation接口的接口!!!
- 使用注解时创建实现类对象,把参数传进去
元注解
定义:修饰注解的注解
常见:@Target和Retention
@Target
- TYPE:比如Field(变量),TYPE(类)等,多个用{,}隔开
@Target({ElementType.TYPE, ElementType.METHOD})// 当前被修饰的注解只能用在类上,方法上。public @interface MyTest3 {}
@MyTest3
publicclassAnnotationTest2{// @MyTest3 这里会报错private String name;
@MyTest3
publicvoidtest(){}}
Retention
@Retention(RetentionPolicy.RUNTIME)// 控制下面的注解一直保留到运行时public @interface MyTest3 {}
- RUNTIME:一直保留到运行阶段(常用)
注解的解析
- 定义:判断类上/方法上等上是否存在注解,并把注解里的内容解析出来
- 方法
解析案例
MyTest4:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)public @interface MyTest4 {
String value();doubleaa()default100;
String[]bbb();}
Demo:
@MyTest4(value ="蜘蛛精", aaa=99.5, bbb ={"至尊宝","黑马"})
@MyTest3
publicclassDemo{
@MyTest4(value ="孙悟空", aaa=199.9, bbb ={"紫霞","牛夫人"})publicvoidtest1(){}}
AnnotationTest3:
publicclassAnnotationTest3{
@Test
publicvoidparseClass(){// 1、先得到Class对象
Class c = Demo.class;// 2、解析类上的注解// 判断类上是否包含了某个注解if(c.isAnnotationPresent(MyTest4.class)){//这里是.class而不是直接输出类
MyTest4 myTest4 =(MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));}}
@Test
publicvoidparseMethod() throws Exception {// 1、先得到Class对象
Class c = Demo.class;
Method m = c.getDeclaredMethod("test1");// 2、解析方法上的注解// 判断方法对象上是否包含了某个注解if(m.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 =(MyTest4) m.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));}}}
应用场景
- 与反射配合做框架
示例
定义MyTest注解:
@Target(ElementType.METHOD)// 注解只能注解方法。
@Retention(RetentionPolicy.RUNTIME)// 让当前注解可以一直存活着。public @interface MyTest {}
定义若干个方法:
publicclassAnnotationTest4{// @MyTest 不会被执行publicvoidtest1(){
System.out.println("===test1====");}
@MyTest
publicvoidtest2(){
System.out.println("===test2====");}
@MyTest
publicvoidtest3(){
System.out.println("===test3====");}
@MyTest
publicvoidtest4(){
System.out.println("===test4====");}publicstaticvoidmain(String[] args) throws Exception {.......}}}
模拟Junit程序:通过反射配合注解解析
publicstaticvoidmain(String[] args) throws Exception {
AnnotationTest4 a =newAnnotationTest4();// 启动程序!// 1、得到Class对象
Class c = AnnotationTest4.class;// 2、提取这个类中的全部成员方法
Method[] methods = c.getDeclaredMethods();// 3、遍历这个数组中的每个方法,看方法上是否存在@MyTest注解,存在// 触发该方法执行。for(Method method : methods){if(method.isAnnotationPresent(MyTest.class)){// 说明当前方法上是存在@MyTest,触发当前方法执行。
method.invoke(a);}}}
调用结果:
- 1、得到Class对象
- 2、提取这个类中的全部成员方法
- 3、遍历这个数组中的每个方法,看方法上是否存在@MyTest注解,执行(必须创建对象,重新new一个)
动态代理
- 意义:对象身上事儿太多,需要通过代理转移部分职责;
- 含义:对象有什么方法想被代理,代理也得有,但不真正做,而是一些其他操作+调用对象的。
- 怎么做:对象声明接口,代理设置实现类。
- 接口:
public interface Star {
String sing(String name);voiddance();}
- 用明星类实现该接口:
publicclassBigStar implements Star{private String name;publicBigStar(String name){this.name = name;}public String sing(String name){
System.out.println(this.name +"正在唱:"+ name);return"谢谢!谢谢!";}publicvoiddance(){
System.out.println(this.name +"正在优美的跳舞~~");}}
- 让代理类实现该接口ProxyUtil(工具类,静态方法)
importjava.lang.reflect.Proxy;publicclassProxyUtil{publicstatic Star createProxy(BigStar bigStar){.......return starProxy;}}
newProxyInstance:
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是有哪些方法(接收的接口数组)
参数3:用来指定生成的代理对象要干什么事情(new一个接口的匿名内部类对象)
Star starProxy =(Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{Star.class},newInvocationHandler(){
@Override // 回调方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 代理对象要做的事情,会在这里写代码if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万");}elseif(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");}return method.invoke(bigStar, args);}});
回调方法invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 代理对象要做的事情,会在这里写代码if(method.getName().equals("sing")){//当前正在代理sing方法
System.out.println("准备话筒,收钱20万");}elseif(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");}return method.invoke(bigStar, args);//返回方法的返回值}
- proxy:代理对象
- method:方法;
- args:方法的参数
如何调用?test:
publicclassTest{publicstaticvoidmain(String[] args){
BigStar s =newBigStar("杨超越");
Star starProxy = ProxyUtil.createProxy(s);
String rs = starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();}}
(可通过单步调试追踪调用链条)
应用场景:AOP
动态代理设计模式主要用于以下几种场景:
- 接口适配:当一个类已经实现了某个接口,但是需要对该接口的某些方法进行修改时,可以使用动态代理。动态代理可以在不修改原有代码的情况下,对接口的方法进行增强。
- AOP(面向切面编程):动态代理是实现AOP的一种方式。在AOP中,通常会在业务方法执行前后插入一些公共的代码,如日志、事务管理等。动态代理可以在运行时动态地为目标对象生成一个代理对象,然后通过代理对象调用目标方法,从而实现在目标方法执行前后插入公共代码。
- 远程调用:在远程方法调用(RMI)中,客户端实际上是调用的是本地的一个代理对象,这个代理对象负责与远程服务器通信。这个代理对象就是通过动态代理生成的。
- 权限控制:动态代理可以用于实现权限控制。例如,当调用某个方法时,可以通过动态代理检查用户是否有权限执行该方法。
- 性能监控:动态代理可以用于监控方法的执行时间,从而实现性能监控。例如,可以在方法执行前后获取系统时间,然后计算出方法的执行时间。
- 单元测试:在单元测试中,经常需要模拟一些对象。这些模拟对象可以通过动态代理生成。例如,可以生成一个代理对象,这个代理对象的所有方法都返回默认值。 什么是AOP?
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,其目标是提高模块化的能力,特别是对于横切关注点(cross-cutting concerns)的处理。横切关注点是那些分散在多个模块中,但不能很好地通过传统的面向对象编程(OOP)模块化的问题,例如日志记录、事务管理、安全性等。
在AOP中,一个切面(Aspect)代表一个横切关注点,它可以包含一些通用的代码。这些代码可以被定义为通知(Advice),然后通过切入点(Pointcut)插入到目标对象的方法中。通知定义了何时(例如,方法调用前、后或异常抛出时)以及如何(执行何种代码)应用切面。
例如,你可能有一个用于记录日志的切面,它的通知在每个方法调用前后记录日志,切入点定义了这个切面应用于哪些方法。
AOP可以帮助我们将这些横切关注点从业务逻辑中分离出来,使得业务逻辑更加清晰,同时也更易于维护和重用。
版权归原作者 QHG7C0 所有, 如有侵权,请联系我们删除。