Java之Junit+反射补充+注解
一、Junit单元测试
1.1 测试方式
测试方式:
黑盒:黑盒测试,不知道具体实现逻辑,给输入值,单纯测试代码实现的功能
白盒:白盒测试,知道逻辑,根据逻辑测试,看能否实现功能或者是否有错误。
1.2 Junit使用:白盒测试
-
步骤:
-
定义一个测试类(测试用例)
建议:
-
**测试类名:xxxTest **
-
包名:xxx.xxx.xxx.test
-
-
定义测试方法:可以独立运行
建议:
-
方法名:test测试的方法名 testxxx( )
-
其返回值:void
-
参数列表:空参
-
-
给方法加 @test
-
加org.Junit.Test包
-
-
判断结果:
- 红色:失败
- 绿色:成功
- 一般来说我们会使用断言操作来处理结果,而不是输出结果
- 断言:Assert.assertEquals(期望的结果,运算的结果)
-
另外两个测试用注解:
- @Before:修饰的方法会在测试方法前被自动执行。(常用于变量初始化)
- @After:修饰的方法会在测试方法执行之后自动被执行(常用于关闭资源)
例子:
//Junit单元测试
import org.junit.Before;
import org.junit.Test;
import java.util.Scanner;
public class Junitdemo01 {
Scanner scanner =new Scanner(System.in);
/*
*测试test1方法
*/
@Test
@Check
public void test01(){
System.out.println("test01已被执行!");
}
@Before
public void chushihua(){
int x = 20;
}
public void close(){
scanner.close();
}
}
二、反射
反射的概念:什么是反射?
反射是将一个类的各个部分(构造器,成员变量,成员方法)进行封装为其他对象。
- 好处:可以在程序运行过操中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
具体的代码可以看java基础之反射
java代码在计算机中的三个阶段:
对应的,有三种获取Class的方法
- 源代码阶段,通过Class.forName(“类名”);///多用于配置文件,读取文件,加载类
- 类对象阶段,类名.class ///多用于参数的传递
- 运行时阶段,对象.getClass() //多用于对象的获取自己码方式
同一个类,通过三种方式获取得到的是同一个Class对象。
通过Class对象可以获得这个类的三个部分:
-
成员变量(Field)
-
构造方法(Constructor)
-
成员方法(Method)
可以选择获取返回是一个集或单个(须给定参数来确定是哪一个)。
三、注解
1.注解概念
注解:用于给计算机说明程序的。
注释:用于 给程序员说明程序的。
注解的定义:注解(Annotation),也叫元数据,是一种代码级别的说明,它在jdk1.5以后版本引入一个特性,与类,接口,枚举是同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用于对这些元素进行说明,注释。
作用分类:
- 编写文档:通过代码里标记的元数据生成文档 [生成文档doc文档]
- 代码分析:通过代码里标识的元数据对代码进行分析 [使用反射]
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查 [@Override]
常见注解:
- @Override:检查被注解标注的方法是否继承自父类或接口的。
- @Depercated:改注解标注的内容表示已过时。
- @SuppressWarnings:压制警告(需要传参:一般传‘all’,即@SuppressWarnings(“all”) )
2.自定义注解:
自定义注解格式:
元注解
public+@interface 注解名{ }
本质:注解本质是接口,该接口默认继承
- public interface 注解名 extends java.lang.annotation.Annotation{}
属性:接口中的抽象方法
属性的返回值类型:
- 基础数据类型
- String
- 枚举
- 注解
- 以及以上类型对应的数组类型。
枚举类型的定义:
public enum week {
星期一, 星期二,星期三,星期四,星期五,星期六,星期天
}
**元注解:**元注解是用以描述注解的注解
- @Target:描述注解能够作用的位置
- ElementType取值:(可同时取)
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- ElementType取值:(可同时取)
- @Retention:描述注解被保留的阶段
- RetentionPolicy取值:
- SOURCE :源码阶段:表示被描述的注解,不会被Class字节码文件保存
- CLASS:类加载器阶段:表示被描述的注解,会被保留到Class字节码文件中
- RUNTIME:运行时阶段:表示被描述的注解,会被保留到Class字节码文件中,并被JVM读取到
- RetentionPolicy取值:
- @Documented:描述注解是否被抽取到API文档中
- @Inherited:描述注解是否被子类继承
import java.lang.annotation.*;
//**元注解:**元注解是用以描述注解的注解
//
//* @Target:描述注解能够作用的位置
//* @Retention:描述注解被保留的阶段
//* @Documented:描述注解是否被抽取到API文档中
//* @Inherited:描述注解是否被子类继承
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
//被描述方法能作用的位置
@Retention(RetentionPolicy.RUNTIME) //被保留的阶段
@Deprecated //被保存到api文档中
@Inherited //可以自动被子类继承
public @interface MyAnno {
/* int show1(); //使用时是 @Myanno(show1=10),这样的表达方式来使用。
int value(); //这个value很特殊,若一个注解类里只要它要赋值的时候就不需要用键值对的方式表示,直接括号给值就ok了
String show2() default "lys";//默认赋值,有默认赋值可以不进行赋值。
Override anno2();
enum show3{};
String[] show4(); //枚举和数组在赋值时需要用大括号{},若只有一个,则可以用小括号()*/
}
简单来说注解作用:更多是作为一个”标签“来使用。
- 标记某个东西(类,方法,变量),被解析程序使用 //比如以下两个例子
- 编译器 //比如被javadoc利用
例子1:
使用自定义注解标记方法,检查方法是否异常。(简单方法测试框架)
//注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
//Check注解的解析程序
public class CheckTest {
public static void main(String[] args) throws IOException {
//先获取check属性值
Junitdemo01 j1 = new Junitdemo01();
//对应的字节码文件
Class<? extends Junitdemo01> c1 = j1.getClass();
//通过字节码文件获取其方法集
Method[] methods = c1.getMethods();
int count=0;
BufferedWriter bf = new BufferedWriter(new FileWriter("bug.txt"));
//方法集解集
for (Method m:methods){
//获取被check注解标记的方法
if (m.isAnnotationPresent(Check.class)){//isAnnotationPresent是用于判断是否被注解标记,参数是注解.class
try {
//调用方法
m.invoke(j1);
//捕抓方法异常
} catch (Exception e) {
count++;
bf.write(m.getName()+"方法发送异常");
bf.newLine();
bf.flush();
bf.write("异常名称:"+e.getCause().getClass().getSimpleName());
bf.newLine();
bf.flush();
bf.write("异常原因:"+e.getCause().getMessage());
bf.newLine();
bf.flush();
bf.write("-------------------------");
bf.newLine();
}
}
}
bf.write("共有"+count+"个方法发送了异常");
bf.close();
if (count!=0){
System.out.println("请前往bug.txt文件查看发生的异常");
}
}
}
只要运行checktest程序即可检查被chcek注解标记的程序是否异常,并把异常记录到bug.txt文件中。
例子2:
反射配合注解完成能运行任意方法的程序。(简单方法运行框架)
//注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CNameAndMName {
String className();
//其实就等同于
/* pubilc String className( String className){
return ClassName
}*/
String methodName();
}
//测试类
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//通过给注解的字段赋值,从而获取注解对象进而获取字段的信息
//就不用通过IO流获取Class名和方法名了
@CNameAndMName(className = "com.lys.heima.junitdemo.Junitdemo01",methodName = "test01")
public class ALLCanRun {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//获取我现在这个文件的字节码文件
Class<ALLCanRun> c1 = ALLCanRun.class;
//把这个注解对象拿过来创建
CNameAndMName anno = c1.getAnnotation(CNameAndMName.class);
//拿这个注解的的两个字段的内容
String className = anno.className();
String methodName = anno.methodName();
//通过字段获取class文件:
Class<?> appclass = Class.forName(className);
//创建进程对象
Object appobj = appclass.newInstance();
//指定给定方法
Method appclassMethod = appclass.getMethod(methodName);
// Method appclassMethod = appclass.getDeclaredMethod(methodName);//把私有方法也拿了+上取消访问检查,私有方法也能被运行了。
// appclassMethod.setAccessible(true);//取消访问检查
//运行方法
appclassMethod.invoke(appobj);
}
}
= appclass.newInstance();
//指定给定方法
Method appclassMethod = appclass.getMethod(methodName);
// Method appclassMethod = appclass.getDeclaredMethod(methodName);//把私有方法也拿了+上取消访问检查,私有方法也能被运行了。
// appclassMethod.setAccessible(true);//取消访问检查
//运行方法
appclassMethod.invoke(appobj);
}
}