一篇文章学会Junit单元测试&反射&注解!
一、Junit单元测试
简介:JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
什么是单元测试?
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 程序单元是应用的最小可测试部件。简单来说,就是测试数据的稳定性是否达到程序的预期。
单元测试的重要性(好处)
- 可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。
- 启动后,自动化测试,并判断执行结果, 不需要人为的干预。
- 只需要查看最后结果,就知道整个项目的方法接口是否通畅。
- 每个单元测试用例相对独立,由Junit 启动,自动调用。不需要添加额外的调用语句。
- 添加,删除,屏蔽测试方法,不影响其他的测试方法。 开源框架都对JUnit 有相应的支持。
黑盒测试与白盒测试
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:需要写代码的。关注程序具体的执行流程。(Junit单元测试属于白盒测试)
Junit使用
- 定义一个测试类(测试用例)
- 定义测试方法:可以独立运行
- 给方法加@Test注解
- 导入junit依赖环境
单元测试的编码规范
类名: 定义测试类,类名是由
被测试类名Test
构成。包名: 定义的测试类需要放在
xxx.xxx.xxx.test
包中。方法名: 测试方法的方法名有两种定义方式
test测试方法
和测试方法
。返回值: 因为我们的方法只是在类中测试,可以独立运行,所以不需要处理任何返回值,所以这里使用
void
。参数列表: 因为我们的方法是用来测试的,至于参数列表的传入是没有必要的。我们在测试的时候自行传入需要的参数测试即可。所以在此参数列表为
空
。@Test注解: 测试是需要运行来完成的。如果我们只有一个main方法,显然在结构上还是需要我们去注释掉测试过的。解决此问题这里我们需要在测试方法上方加
@Test
注解来完成测试,只要是加该注解的方法,可以单独运行此方法来完成测试。@Test注解jar包Junit4、5: @Test注解是需要我们导入jar包才能使用的。jar包有两个分别是:junit-4.13-rc-2和hamcrest-core-1.3。
断言assert
断言可以简单理解为判断。Junit所有的断言都包含在 Assert 类中。
一般我们会使用断言操作来处理结果:Assert.assertEquals(期望的结果,运算的结果);
断言方法 | 描述 |
---|---|
assertNull(java.lang.Object object) | 检查对象是否为空 |
assertNotNull(java.lang.Object object) | 检查对象是否不为空 |
assertEquals(long expected, long actual) | 检查long类型的值是否相等 |
assertEquals(double expected, double actual, double delta) | 检查指定精度的double值是否相等 |
assertFalse(boolean condition) | 检查条件是否为假 |
assertTrue(boolean condition) | 检查条件是否为真 |
assertSame(java.lang.Object expected, java.lang.Object actual) | 检查两个对象引用是否引用同一对象(即对象是否相等) |
assertNotSame(java.lang.Object unexpected, java.lang.Object actual) | 检查两个对象引用是否不引用统一对象(即对象不等) |
Assert语句的方法可以既可以断言数组,也可以断言普通数据类型。
使用断言的时候尽量不要去断言Double对象。对于双精度数,绝对有必要使用增量进行比较,以避免浮点舍入的问题。
Junit注解
`注解 | 含义 |
---|---|
@Test | 依附在 JUnit 的 public void 方法可以作为一个测试案例。 |
@Before | 修饰的方法会在测试方法之前被自动执行。 |
@After | 修饰的方法会在测试方法执行之后自动被执行。 |
@BeforeClass | 在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。 |
@AfterClass | 它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。 |
@Ignore | 这个注释是用来忽略有关不需要执行的测试的。 |
JUnit加注解执行过程
beforeClass()
: 方法首先执行,并且只执行一次。afterClass()
:方法最后执行,并且只执行一次。before()
:方法针对每一个测试用例执行,但是是在执行测试用例之前。after()
:方法针对每一个测试用例执行,但是是在执行测试用例之后- 在 before() 方法和 after() 方法之间,执行每一个测试用例。
JUnit时间测试和异常测试
时间测试
如果一个测试用例比起指定的毫秒数花费了更多的时间,那么 Junit 将自动将它标记为失败。
@Test(timeout=1000)
异常测试
Junit 用代码处理提供了一个追踪异常的选项。你可以测试代码是否它抛出了想要得到的异常。expected
参数和 @Test
注释一起使用。
@Test(expected = ArithmeticException.class)
代码实现
功能类
public class Count {
public int add(int a, int b){
return a+b;
}
public int sub(int a, int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public int div(int a, int b){
return a/b;
}
}
测试类
public class TestCount {
@Before
public void before(){
System.out.println("before....");
}
@After
public void after(){
System.out.println("after....");
}
@Test
public void testadd(){
Count con = new Count();
int result = con.add(6,12);
// System.out.println(result); 通常在测试环境下不需要打印结果
//一般我们会使用断言操作来处理结果:Assert.assertEquals(期望的结果,运算的结果);
Assert.assertEquals(18,result);
}
@Test(timeout = 1) //测试用例比起指定的毫秒数花费了更多的时间,那么 Junit 将自动将它标记为失败
public void testsub(){
Count con = new Count();
int result = con.sub(6,12);
// System.out.println(result); 通常在测试环境下不需要打印结果
//一般我们会使用断言操作来处理结果:Assert.assertEquals(期望的结果,运算的结果);
Assert.assertEquals(-6,result);
}
}
二、反射
反射的理解(框架设计的灵魂)
反射机制:将类的各个组成部分封装为其他对象。
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
- ......
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法
- ......
Class 类
在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass(){}
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
获取Class类对象
- 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class clazz = String.class;
- 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = people.getClass();
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz = Class.forName(“java.lang.String”);
将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类.
- 其他方式
ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”);
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
获取Class对象代码实现
/**
* 获取Class对象的几种方式
*/
public class GetClass {
public static void main(String[] args) throws ClassNotFoundException {
//1.若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class cls = People.class;
System.out.println(cls);
//2.已知某个类的实例,调用该实例的getClass()方法获取Class对象
People people =new People();
Class cls2 = people.getClass();
System.out.println(cls2);
//3.该类在类路径下,通过Class类的静态方法forName()获取
Class cls3 = Class.forName("Reflextion.People"); //全类名 抛出ClassNotFoundException
System.out.println(cls3);
//4.
ClassLoader cl = GetClass.class.getClassLoader();
Class cls4 = cl.loadClass("Reflextion.People");
System.out.println(cls4);
System.out.println(cls == cls2); //true
System.out.println(cls == cls3); //true
System.out.println(cls == cls4); //true
//同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
}
}
关于Class类的注意
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的完整结构
Class类的常用方法
- 获取成员变量
Field[] getFields() //获取所有public修饰的成员变量 Field getField(String name) //获取指定名称的 public修饰的成员变量 Field[] getDeclaredFields() //获取所有的成员变量,不考虑修饰符 Field getDeclaredField(String name) //获取指定名称的成员变量
Field类中:
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Object get(Object obj) //取得指定对象obj上此Field的属性内容 public void set(Object obj,Object value) //设置指定对象obj上此Field的属性内容 //注:在类中属性都设置为private的前提下,在使用set()和get()方法时,首先要使用Field类中的setAccessible(true)方法将需要操作的属性设置为可以被外部访问。 public void setAccessible(true) //访问私有属性时,让这个属性可见。 public int getModifiers() //以整数形式返回此Field的修饰符 public Class<?> getType() //得到Field的属性类型 public String getName() //返回Field的名称。
- 获取构造方法
Constructor<?>[] getConstructors() //返回此 Class 对象所表示的类的所有public构造方法。 Constructor<T> getConstructor(类<?>... parameterTypes) Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) Constructor<?>[] getDeclaredConstructors() //返回此 Class 对象表示的类声明的所有构造方法。
- 获取成员方法
Method[] getMethods() //返回此Class对象所表示的类或接口的public的方法 Method getMethod(String name, 类<?>... parameterTypes) Method[] getDeclaredMethods() //返回此Class对象所表示的类或接口的全部方法 Method getDeclaredMethod(String name, 类<?>... parameterTypes)
Method类中(通过反射,调用类中的方法,通过Method类完成。):
/* 步骤: 1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。 2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。 */ Object invoke(Object obj, Object …args) /* 说明: 1.Object 对应原方法的返回值,若原方法无返回值,此时返回null 2.若原方法若为静态方法,此时形参Object obj可为null 3.若原方法形参列表为空,则Object[] args为null 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方 法,将可访问private的方法。 */ public Class<?> getReturnType()//取得全部的返回值 public Class<?>[] getParameterTypes()//取得全部的参数 public int getModifiers()//取得修饰符 public Class<?>[] getExceptionTypes()//取得异常信息
- 创建类的对象调用Class对象的newInstance()方法
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
- 其他方法
方法名 | 说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Package getPackage() | 获取类所在的包 |
Type getGenericSuperclass() | 获取父类泛型类型 |
常用方法代码实现
//Person类代码实现省略
/**
* Class 对象功能(方法)测试
*/
public class ClassDemo {
/**
* 1. 在运行时获取运行时类的实例
*/
@Test
public void test1() throws Exception{
//获取Class对象
Class cls = Class.forName("Reflextion.People");
//使用Class对象newInstance()实例化
People people = (People) cls.newInstance();
people.setName("小白");
people.setAge(18);
System.out.println(people.toString());
}
/**
* 2. 利用类加载器加载属性文件
* 在项目src目录下创建文件pro.properties
*/
@Test
public void test2() throws IOException {
//提前在src目录下新建pro.properties文件
Properties pro = new Properties();
//加载配置文件进内存
pro.load(this.getClass().getClassLoader().getResourceAsStream("pro.properties"));
//读取配置信息
String className = pro.getProperty("ClassName");
String methodName = pro.getProperty("MethodName");
System.out.println(className + methodName);
}
/**
* 3.在运行时获取并操作运行时类对象的属性
*/
@Test
public void test3() throws Exception {
//获取Class对象
Class cls = People.class;
//实例化People对象
People o = (People) cls.newInstance();
//获取属性Field[]
Field[] declaredFields = cls.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
System.out.println(declaredFields[i]);
}
//获取指定属性Field
Field age = cls.getField("age");
age.set(o,10);
//获取属性值
Object o1 = age.get(o);
System.out.println(o1);
System.out.println("-----------------------------");
Field name = cls.getDeclaredField("name");
name.setAccessible(true);//忽略访问权限
name.set(o, "小白");
Object o2 = name.get(o);
System.out.println(o2);
}
/**
* 4.在运行时获取并调用运行时类对象的方法
*/
@Test
public void test4() throws Exception {
//1.获取Class对象
Class cls = People.class;
//2.使用Class对象实例化
People o = (People) cls.newInstance();
//3.获取指定Method
Method eat = cls.getMethod("eat");
//4.调用方法
eat.invoke(o);
//获取指定私有方法
//Method sleep = cls.getMethod("sleep");error:getMethod()方法不能获取私有方法
Method sleep = cls.getDeclaredMethod("sleep");
//调用方法
//sleep.invoke(o); //报错!!java.lang.NoSuchMethodException
sleep.setAccessible(true); //暴力反射
sleep.invoke(o);
}
}
反射案例
案例:
- 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
- 实现:
- 配置文件
- 反射
- 步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
代码实现
// Teacher类代码实现省略
public class ReflexDemo {
public static void main(String[] args) throws Exception {
//1.加载配置文件
Properties properties = new Properties();
properties.load(ReflexDemo.class.getClassLoader().getResourceAsStream("pro.properties"));
//2.读取配置信息
String className = properties.getProperty("ClassName");
String methodName = properties.getProperty("MethodName");
//3.使用反射技术来加载类文件进内存
Class cls = Class.forName(className);
Object o = cls.newInstance();
//4.使用cls对象获取指定方法对象
Method method = cls.getMethod(methodName);
//5.执行方法
method.invoke(o);
}
}
三、注解
什么是注解?
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
- 注解:说明程序的。给计算机看的
- 注释:用文字描述程序的。给程序员看的
概念描述:
- JDK1.5之后的新特性
- 说明程序的:一种代码级别的说明
- 使用注解:@注解名称
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理. 通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中.
Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据
作用分类
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
JDK中预定义的一些注解
@Override //检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated //该注解标注的内容,表示已过时
@SuppressWarnings//抑制编译器警告
//一般传递参数all @SuppressWarnings("all")
自定义注解
格式
定义新的 Annotation 类型使用 @interface 关键字
public @interface 注解名称{
//属性列表;
}
Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明. 其方法名和返回值定义了该成员的名字和类型.
本质
注解本质上就是一个接口,该接口默认继承Annotation接口。
public interface MyAnno extends java.lang.annotation.Annotation {}
属性
- 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
提取 Annotation 信息
JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。
当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取
程序可以调用 AnnotationElement 对象的如下方法来访问 Annotation 信息:
元注解
概念:用于描述注解的注解。即用于修饰其他 Annotation 定义。
- @Target:描述注解能够作用的位置ElementType取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
- @Retention:描述注解被保留的阶段@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
- RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
- RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
- RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
- @Documented:描述注解是否被抽取到api文档中
- @Inherited:描述注解是否被子类继承
在程序使用(解析)注解
也就是获取注解中定义的属性值
- 获取注解定义的位置的对象 (ClassMethod,Field)
- 获取指定的注解
- getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类实现对象public class ProImpl implements Pro{ public String className(){ return "cn.imyjs.AnnotationDemo.Worker"; } public String methodName(){ return "working"; } }
- getAnnotation(Class)
- 调用注解中的抽象方法获取配置的属性值
- 获取指定的注解
案例
简单的测试框架
自定义注解代码实现
@Target(ElementType.TYPE) //TYPE:可以作用于类上
@Retention(RetentionPolicy.RUNTIME) // 程序可以通过反射获取该注释
public @interface MyAnno {
String ClassName();
String MethodName();
}
使用注解
@MyAnno(ClassName = "cn.imyjs.AnnotationDemo.Worker", MethodName = "working")
public class AnnoDemo {
public static void main(String[] args) throws Exception {
Class cls = AnnoDemo.class;
MyAnno anno= (MyAnno) cls.getAnnotation(MyAnno.class);
String className=anno.ClassName();
String methodName=anno.MethodName();
System.out.println(className);
Class demo1=Class.forName(className);
Method show=demo1.getMethod(methodName);
Object o=demo1.newInstance();
show.invoke(o);
}
}
微信关注
青年之学