JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
注意:Junit 测试也是程序员测试,即所谓的白盒测试,它需要程序员知道被测试的代码如何完成功能,以及完成什么样的功能.
测试分类:
我们知道 Junit 是一个单元测试框架,那么使用 Junit 能让我们快速的完成单元测试。
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
一. 首先编写一个需要测试的类:
package Test02;
public class Calculator {
/**
* 求和
* @param a
* @param b
* @return
*/
public int add(int a,int b){
return a + b;
}
/**
* 求差
* @param a
* @param b
* @return
*/
public int sub(int a,int b){
return a - b;
}
}
二. 编写测试类:
package Test02;
public class CalculatorTest {
public static void main(String[] args){
addTest();
subTest();
}
public static void addTest(){
Calculator c = new Calculator();
int resultAdd = c.add(1,2);
if(resultAdd == 3){
System.out.println("add方法测试通过!");
}
}
public static void subTest(){
Calculator c = new Calculator();
int resultSub = c.sub(1,2);
if(resultSub == -1){
System.out.println("sub方法测试通过!");
}
}
}
运行结果如下:
那么我们可以看到,不用 Junit 只能写在 main()方法中,通过运行结果来判断测试结果是否正确。这里需要测试的只有两个方法,如果有很多方法,那么测试代码就会变得很混乱。
package Test02;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest02 {
@Test
public void addTest(){
Calculator c = new Calculator();
int resultAdd = c.add(1,2);
Assert.assertEquals(3,resultAdd);
}
@Test
public void subTest(){
Calculator c = new Calculator();
int resultSub = c.sub(1,2);
Assert.assertEquals(-1,resultSub);
}
}
其中我调用了一个Assert.assertEquals(expected,actual)方法(断言操作)。
该方法就是判断expected与actual相同,如果相同即测试通过,反之则不通过.
运行结果如下:
1.@Test: 测试方法
2.@Ignore: 被忽略的测试方法:加上之后,暂时不运行此段代码
3.@Before: 每一个测试方法之前运行
4.@After: 每一个测试方法之后运行
5.@BeforeClass: 方法必须必须要是静态方法(static 声明),所有测试开始之前运行,注意区分before,是所有测试方法
6.@AfterClass: 方法必须要是静态方法(static 声明),所有测试结束之后运行,注意区分 @After
注意:编写测试类的原则:
①测试方法上必须使用@Test进行修饰
②测试方法必须使用public void 进行修饰,不能带任何的参数
③新建一个源代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开
④测试类所在的包名应该和被测试类所在的包名保持一致
⑤测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖
⑥测试类使用Test作为类名的后缀(不是必须)
⑦测试方法使用test作为方法名的前缀(不是必须)
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。
Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
类名.class:通过类名的属性class获取
多用于参数的传递
对象.getClass():getClass()方法在Object类中定义着。
多用于对象的获取字节码的方式
注意:
package Test02;
import Reflect.Person;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class cls = Class.forName("Reflect.Person");
System.out.println(cls);
Class cls2 = Person.class;
System.out.println(cls2);
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3);
System.out.println(cls == cls2);
System.out.println(cls == cls3);
}
}
运行结果如下:
由此得出结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
1. 设置值
void set(Object obj, Object value)
2. 获取值
get(Object obj)
3. 忽略访问权限修饰符的安全检查
setAccessible(true):暴力反射
package Test02;
//先写一个Person类
public class Person {
public String a;
private String b;
}
package Test02;
import java.lang.reflect.Field;
public class ReflectDemo01 {
public static void main(String[] args) throws Exception{
//创建Person的Class对象
Class personClass = Person.class;
//Field[] getFields()获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("--------------------------");
//Field getField(String name)获取指定public修饰的成员变量
Field a = personClass.getField("a");
//获取成员变量a 的值
Person p = new Person();
Object s = a.get(p);
System.out.println(s);
a.set(p,"私忆一秒钟");
System.out.println(p.a);
System.out.println("==========================");
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//Field getDeclaredField(String name)
Field b = personClass.getDeclaredField("b");
//忽略访问权限修饰符的安全检查
b.setAccessible(true);//暴力反射
Object value2 = b.get(p);
System.out.println(value2);
}
}
创建对象:
T newInstance(Object... initargs)
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
package Test02;
//先写一个Person类
public class Person {
public String a;
private String b;
public void eat(){
System.out.println("eat...");
}
public void sleep(){
System.out.println("sleep...");
}
public Person(){
}
public Person(String a,String b){
this.a = a;
this.b = b;
}
@Override
public String toString() {
return "Person{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
'}';
}
}
package Test02;
import java.lang.reflect.Constructor;
public class ReflectDemo02 {
public static void main(String[] args) throws Exception{
//获取Person的Class类对象
Class personClass = Person.class;
//获取指定构造方法
Constructor constructor = personClass.getConstructor(String.class,String.class);
System.out.println(constructor);
//创建对象
Object person = constructor.newInstance("私忆","一秒钟");
System.out.println(person);
Constructor constructor1 = personClass.getConstructor();
System.out.println(constructor1);
//创建对象
Object person1 = constructor1.newInstance();
System.out.println(person1);
Object o = personClass.newInstance();
System.out.println(o);
}
}
* 执行方法:
Object invoke(Object obj, Object... args)
* 获取方法名称:
String getName():获取方法名
package Test02;
public class Person {
public void eat(){
System.out.println("eat...");
}
public void eat(String food){
System.out.println("eat..." + food);
}
public void sleep(){
System.out.println("sleep...");
}
@Override
public String toString() {
return "Person{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
'}';
}
}
package Test02;
import java.lang.reflect.Method;
public class ReflectDemo03 {
public static void main(String[] args) throws Exception{
//0.获取Person的Class对象
Class personClass = Person.class;
//获取指定名称的方法
Method eat_method = personClass.getMethod("eat");
Person p = new Person();
//执行方法
eat_method.invoke(p);
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(p,"饭");
System.out.println("-----------------");
//获取所有public修饰的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
String name = method.getName();
System.out.println(name);
//method.setAccessible(true);
}
//获取类名
String className = personClass.getName();
System.out.println(className);//cn.itcast.domain.Person
}
}
运行结果如下:
注意:获取的不仅是我们写的eat()和sleep()方法,还有来自超类Object的方法。
需求:
写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
演示:
package Test02;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
*/
public class ReflectTest02 {
public static void main(String[] args) throws Exception{
// 加载配置文件
// 创建Properties对象
Properties pro = new Properties();
// 获取配置文件,并通过类加载器转换为一个集合
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
// 获取类名和方法名
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
// 创建所需类的对象
Class cls = Class.forName(className);
Object obj = cls.newInstance();
// 获取所需的方法
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
java.lang.annotation.Annotation接口中有这么一句话,用来描述『注解』。
The common interface extended by all annotation types
所有的注解类型都继承自这个普通的接口(Annotation)
这句话有点抽象,但却说出了注解的本质。我们看一个 JDK 内置注解的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是注解 @Override 的定义,其实它本质上就是:
public interface Override extends Annotation{
}
没错,注解的本质就是一个继承了 Annotation 接口的接口。有关这一点,你可以去javap命令反编译任意一个注解类,你会得到结果的。
一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。
而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。
典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。
这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而你自定义的注解,编译器是不知道你这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
格式:
元注解
public @interface 注解名称{
属性列表;
}
属性:
接口中的抽象方法
要求:
这次我们通过注解来写上一个案例,不能改变该类的任何代码的前提下,可以通过改变注解帮我们创建任意类的对象,并且执行其中任意方法。
package Test02;
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 Pro {
String className();
String methodName();
}
package Test02;
import java.lang.reflect.Method;
@Pro(className = "Test02.Person",methodName = "eat")
public class ReflecTest03 {
public static void main(String[] args) throws Exception{
// 1. 解析注解
// 获取该类的字节码文件对象
Class<ReflecTest03> reflecTest03Class = ReflecTest03.class;
// 获取上面的注解对象
Pro an = reflecTest03Class.getAnnotation(Pro.class);
// 调用注解对象中的抽象方法,获取返回值
String className = an.className();
String methodName = an.methodName();
// 创建所需类的对象,实现所需方法
Class cls = Class.forName(className);
Object obj = cls.newInstance();
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
运行结果如下:
相信朋友们对于获取注解对象后是怎么获取抽象方法中的返回值很好奇。
其实 getAnnotation(Class)是在内存中生成了一个该注解接口的子类实现对象
即:
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
要求:通过注解测试给定类中指定的方法,并统计测试不通过的方法放入bug3.txt文件中。
package Test02.Test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
package Test02.Test;
public class Calculator {
//加法
@Check
public void add(){
String str = null;
str.toString();
System.out.println("1 + 0 =" + (1 + 0));
}
//减法
@Check
public void sub(){
System.out.println("1 - 0 =" + (1 - 0));
}
//乘法
@Check
public void mul(){
System.out.println("1 * 0 =" + (1 * 0));
}
//除法
@Check
public void div(){
System.out.println("1 / 0 =" + (1 / 0));
}
public void show(){
System.out.println("永无bug...");
}
}
package Test02.Test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
public class TestCheck {
public static void main(String[] args) throws IOException {
// 创建指定类的对象
Calculator c = new Calculator();
// 获取字节码对象
Class cls = c.getClass();
// 获取所有的方法
Method[] methods = cls.getMethods();
// 定义异常次数,初始化为0,创建字符高效输出流对象
int number = 0;
BufferedWriter bw = new BufferedWriter(new FileWriter("bug3.txt"));
// 遍历所有方法
for (Method method : methods) {
// 判断方法是否Pro注解被标记
if(method.isAnnotationPresent(Check.class)){
//通过try-catch调用方法并获取异常
try {
method.invoke(c);
}catch (Exception e){
number ++;
// 将异常写入bug3.txt文件中
bw.write("异常的方法名称为: " + method.getName());
bw.newLine();
bw.write("异常的名称: " + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因: " + e.getCause().getMessage());
bw.newLine();
bw.write("------------------------------------------");
bw.newLine();
}
}
}
bw.write("本次测试总共 " + number + " 次异常");
bw.flush();
bw.close();
}
}