目录
1. Junit单元测试
1.1 测试的概述
测试是指检验所写程序是否存在错误的一个方法,根据测试方式的不同可以分为黑盒测试与白盒测试,那么什么是黑盒测试和白盒测试呢?
- 黑盒测试:不需要写任何代码,输入数据到一个程序,观察输出结果是否有错误,不关注程序的执行步骤、过程等(相当于你把数据扔进一个黑盒子,不知道他弄了什么,你就等一下拿出来看看对不对)
- 白盒测试:需要写测试代码,利用代码对程序进行测试,可给出具体的测试结果,关注程序的具体执行流程(相当于程序是透明的,你可以看见、操作,因此称为白盒)
1.2 Junit的使用
Junit是属于白盒测试,其使用步骤其实非常简单,首先我们来定义一个想要被测试的类,这里定义一个简单的计算类:
然后再定义一个测试类,主要利用该类进行测试,例如我想要测试刚才所定义的计算类是否可以正确计算,那么我们可以定义两个测试方法调用他看一看输出结果:
你会发现我没有定义main
方法,所以这两个测试方法是无法执行的,你在疑惑我是不是搞错了?不是的,我还没说完!Junit单元测试的使用,需要用到一个Test
注解,注解是什么?别急,后面的章节会介绍,在这里,你只需要在想要测试的方法前加上一个@Test
,例如这样子:
咦,怎么会是红色?因为其实Junit严格说并不是我们JDK中已有的东西,我们需要导入Junit依赖环境,怎么做呢?点击一下写好的Test
,旁边就会出现一个红色小灯泡,点它!一般情况有两个版本可以进行选择,这里我选择Junit4来讲解(Junit5与Junit4在关键词上有点区别,想了解具体差异的同学可以出门左转百度)
选择好版本后在弹出框直接选择OK即可
等待片刻后就已经导包完成了,这时候你可以看见左侧处在每个方法的前都有运行符号
点击他就可以看见运行结果了,绿色勾则表示测试通过:
问题少年的问题又来了,这样测试打印输出似乎没什么意义啊,打印什么都还需要我来看对不对,能不能我直接看图标就知道呢?可以的,这也是Junit的正确用法,这时候我们来学习一个断言语句(也在Junit包中):
Assert.assertEquals(期望的结果,运行的结果);
这里就不过多介绍断言是什么了,用法很简单明了,我们来修改一下代码:
运行整个测试类看看效果:
感受还不够明显?我们给计算类人为搞搞错误:
再次运行测试类代码:
这时候就明显了吧,用Assert断言可以判断你的运行结果与我的期望结果是否一致,并且出现错误时可以在控制台告诉你期望值与运行结果的不同,也可点击某个方法单独查看该测试的结果
1.3 @Before与@After
@Test
是Junit中一个最简单的注解,这里还有两个注解是比较常用的:
@Before
:该注解所修饰的方法会在每个测试方法运行前自动执行,一般用于资源申请@After
:该注解所修饰的方法会在每个测试方法运行后自动执行,一般用于资源释放
什么意思呢?下面我们来直观感受一下,为方便演示这里改改代码:
运行整个测试类可以发现,在每个测试方法的前后都会运行了init
和close
方法:
要是我想在整个测试类运行的首尾就只执行一次怎么办呢?来,又给你两个注解介绍:@BeforeClass
:该注解所修饰的方法会在整个测试类运行前自动执行,注意修饰的方法需要是static
@AfterClass
:该注解所修饰的方法会在整个测试类运行后自动执行,注意修饰的方法需要是static
来看看演示代码:
为了不要太多错误信息我把计算类都改成正确的了,下面来看看效果:
注意:本章所介绍的注解都是Junit4的,Junit5的会略有不同
2. 注解与内置注解
2.1 注解的概述
回想一下我们至今为止一共接触了多少种注解呢?本文的第一章一共介绍了5种,还有一种在我们学习方法的覆盖重写时有接触到的,还记得是什么吗?对,就是@Override
,该注解的作用是检查被注解的方法是否正确进行覆盖重写
在我们还不了解注解是什么的情况下,我们大概已经接触了六种注解,那么注解到底是什么东西,它又有什么用呢?下面就来康康吧!
- 注解:也叫元数据,一种代码级别的说明,它是JDK 1.5及以后的版本加入的一个特性,与类、接口、枚举是在同一个层次,它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明
注意注解和注释是不一样的,注解是用于说明程序,是给计算机看的;而注释是自己用文字描述程序,是给程序员看的,千万不能混淆
注解的作用主要有三类:
- 编写文档:通过代码里标识的注解生成文档【生成doc文档】
- 代码分析:通过代码里标识的注解对代码进行分析【
@Test
、使用反射等】 - 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【
@Override
】
编译检查与代码分析我们已经使用过了,另外代码分析关于反射部分将随后介绍,此处主要介绍编写文档的功能,文字都是抽象的,我们看个例子,假如我们写了个程序:
我想生成一个类似API文档的文件让别人可以查阅这个类的相关方法,我们就可以通过注解来完成这个操作:
/**
与 */
部分就是注解内容,在IDEA中输入 /**
回车即可快捷生成,在类前的注释即介绍该类的情况,在方法前的注释即介绍该方法的情况,另外各关键词的含义如下:
@author
作者名@version
版本号@since
指明需要最早使用的jdk版本@param
参数名 说明@return
返回值情况
写好注释内容后,我们在桌面随便新建个文件夹,把该 .java文件复制进去(在IDEA项目栏复制即可到文件夹粘贴),使用Notepad++打开该文件然后把该行删除
观察下方显示该文档的编码方式,若是UTF-8则改为ANSI(即GBK)
保存后打开cmd窗口,更改为当前路径,输入javadoc 文件名
,回车
随后查看该文件夹的index.html
文件,是不是很熟悉的画面?
同样可以查看方法说明,所显示的说明即是注解的内容
这就是注解的编写文档功能,接下来介绍一下JDK中的内置注解
2.2 内置注解
这里主要介绍JDK三个常用的注解:
@Override
:检测该注解所标注的方法是否正确覆盖重写@Deprecated
:把该注解标注的方法标志为已过时@SuppressWarmings
:压制警告,需要传递参数,一般为all
(@SuppressWarmings(“all”))
Override
就不用介绍了,大家都很熟悉,下面来介绍一下Deprecated
的用法含义,首先我们来新建一个类
show2
方法为show1
方法的新版本,但是show1
版本不能删除,不然使用旧版本的用户会无法使用,那么我需要提醒用过该方法已过时,怎么办呢?这时候可以在方法前加上注解
怎么可以体现出来呢?我们再新建一个方法调用一下show1
和show2
方法:
可见show1
方法有删除线,即表示已过时,但能不能使用呢?还是可以使用也不会报错的
另外一个@SuppressWarmings
压制警告是什么意思呢?先再来看一眼我们刚刚的程序,有一处警告提醒:
我不想他显示这个警告怎么办呢?这时候可以把它压制掉,如果写在类前则压制整个类所有的警告,若只想压制某个方法的警告则可写在某个方法前,这里写在类前,可见右侧已经没有任何警告提醒了:
3. 自定义注解
除了JDK内置的注解以外,我们也可以自定义一些注解进行使用
3.1 自定义注解的格式与本质
首先我们来了解一下自定义注解的格式,自定义注解的格式其实非常简单,下面给出参考格式:
元注解
public @interface 注解名称{
属性列表;
}
在IDEA新建文件的时候也可以选择新建注解类型
自定义的属性与元注解随后介绍,这里我们先来研究一下,注解的本质是什么东西呢,了解一个东西最好的方法就是看看别人是怎么写这个东西的,我们写一个@Override
,Ctrl点击看看源码
发现这个注解的定义的属性列表没有任何东西,所以怎么办呢?我们可以进行反编译看看,首先找个地方新建个记事本,敲上如下代码,这是一个最简单的注解定义:
打开我们熟悉的cmd窗口,javac
编译一下,然后,javap
进行反编译:
可以看出,反编译的结果是一个接口的定义,那么代表什么呢?代表注解的本质就是一个接口,一个默认继承java.lang.annotation.Annotation
的接口,所以这时候你应该知道前面所写基本格式中的属性列表是什么了吧?其实就是一堆抽象方法
3.2 自定义注解的属性定义
了解清楚注解的本质后,我们来了解一下它的属性定义,其实就是我们刚刚提到的抽象方法,不过属性的定义返回值类型有一定的限制,只能是以下的类型:
- 基本数据类型(八种)
- String
- 枚举
- 注解
- 以上类型的数组
下面演示一下每个属性的定义(DemoEnum
为枚举,DemoTest
为注解):
这些全部都是合格的属性定义,不相信的同学可以尝试使用void
、Character
等定义会报错
定义好这些属性变量后,我们来使用一下它,既然定义了属性,那么我们在使用的时候就需要对属性进行赋值(定义的属性全部都需要赋值),下面为方便演示,只保留几个属性:
当我们在类中加入注解时候,IDEA会提示需要赋值的注解属性:
其赋值的方式是属性名称 = 值,属性之间用逗号进行分隔
如果我觉得每次赋值都很麻烦,我想要只赋值一个,其他采取默认值怎么办呢?这时候可以在自定义注解处使用default
关键字进行初始化值的设置:
那么在使用该注解时候就只需要对一个赋值,有默认值的可以不赋值
特别地,如果我属性的名字是value
,则赋值时候可以省略该名字,直接定义值,例如注解属性改为:
显然只有value
需要进行赋值,则使用时候可以简化成直接定义值
另外,要注意到数组赋值需要用大括号{ }包裹的,但若数组中只赋值一个时候,可以省略大括号{ }
或者在使用时赋值也一样:
3.3 自定义注解的元注解
关于自定义注解的最后一个问题来了,基本格式中的元注解是什么意思呢?我们再来看看@Override
的源码
你会发现在该注解前还有@Target
和@Retention
注解,这种对注解进行描述的注解就称为元注解,有点绕,好好理解这句话,认真琢磨两遍你会发现其实很好理解
下面来介绍四个常用的元注解:
@Target
:描述该注解能够作用的位置
该元注解的属性为ElementType
,其取值为:
TYPE:可以作用在类上
METHOD:可以作用在方法上
FIELD:可以作用在成员变量上@Retention
:描述该注解被保留的阶段
该元注解的属性为RetentionPolicy
,其取值为:
SOURCE:源代码阶段
CLASS:类对象阶段
RUNTIME:运行时阶段@Documented
:描述该注解是否允许被抽取到API文档中@Inherited
:描述该注解是否允许被子类继承
这四种元注解其实都是很好理解的,下面我们来一一演示
@Target
把Demo1注解设置为可用在类上:
当用在方法或变量时就会报出错误:
其它两种类型的取值同理,不再过多介绍
@Retention
一般情况自定义注解时使用该元注解都是设置为RUNTIME阶段,表示在运行时这个注解还是可以被JVM所读取的
例如@Override
这种在编译期起作用的,他的保留阶段就只是SOURCE阶段
@Documented
标注这个元注解后,代表该注解可以被抽取到API文档中,什么意思呢?我们先来看一下没有加该元注解的注解:
以及使用注解的类:
接下来我们按照前文所介绍的方法来抽取一次API文档,注意要把注解的java文件与类的java文件放在同一个文件夹:
javadoc
一下然后打开index.html,
发现这里没有任何Demo2注解的内容:
接下来我们往注解中加入@Documented
元注解:
重复一遍刚才的步骤,这时候API文档中就已经成功把该Demo2注解抽取出来了:
@Inherited
继承关系相信大家都很好理解,因为这里不方便做相关演示就先不介绍了
最后要注意在自定义注解时,一般都需要用@Target
和@Retention
这两个元注解
4. 注解的应用
注解所有主要内容都已经介绍完了,下面来做两个实际应用来加深对注解的理解
4.1 注解与反射
(还没学习反射的同学,传送门>>>https://blog.csdn.net/weixin_45453739/article/details/107024788)
有学习我的反射文章的同学应该还记得,我们在实现框架时用到了一个Properties
集合来储存配置信息,通过加载该配置信息来实现功能,其实,注解也同样可以实现这样的功能,下面我们就来尝试一下利用注解实现一个简单框架,该框架可以使用任意类的任意方法
首先再回忆一下我们在反射中所写的框架
所定义的两个类:
public class Person {
public String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println("eat something");
}
private void sleep(int time){
System.out.println("sleep " + time + " hours");
}
}
public class Student {
public void study(){
System.out.println("studying...");
}
}
用注解怎么来实现这个框架呢?还记得注解有属性列表并且在使用时候需要赋值吗?下面我们先来写一个注解,其属性列表就是你框架需要使用(配置)的内容,显然我需要知道全类名与方法名
Person类与Student类直接Copy过来,不用修改,接下来我们可以开始写框架了,还记得注解怎么赋值吧,我们需要调用Person类的eat方法:
那,属性列表赋值了,我怎么可以把它拿出来使用呢?其使用过程也很简单,首先创建一个本类(框架)的Class对象:
然后调用该对象的方法获取本类的注解
然后再获取该注解的属性列表的值:
咦,为什么可以这样用呢?怎么像是调用方法的格式?没错,其实它本质也就是调用方法,还记得在本文3.1时说到注解的本质就是一个接口吗?那么getAnnotation
方法其实就是在内存中生成了一个该注解接口的实现类对象:
public class pro implements Pro {
public String className( ) {
return "practice.java03.coding.day04Mix.Person";//赋值的内容
}
public String methodName( ) {
return "eat";//赋值的内容
}
}
了解完原理后,这个框架也没有难点了,后续部分与反射的其实相同,下面给出完善后的框架:
看看运行的结果:
我们再换一个类和方法
再看看运行的结果:
显然使用注解的方法是可以替代反射中所介绍的配置文件,并且起到同样的效果
4.2 简单测试框架
在本文第一章介绍了Junit单元测试,是利用@Test
注解来进行的,那么我可不可以自定义一些注解,写一个测试框架,使得运行框架就能告诉我哪里出现了异常与异常原因呢?然后生成异常日志文件
首先写一个测试注解:
再写一个被测试的计算类:
显然1/0是非法的,一眼就能看出来,但是我们是演示嘛!继续假装没看见,再来写一个框架:
这里直接给出例子,具体的思想大家可以琢磨琢磨,下面看看执行结果:
可见除法没有被顺利执行,这时候我们看看所生成的bug.txt
文件:
我们在add方法处人为创造一下异常:
再看看运行结果
咦,怎么只有一个结果输出了?我们看看异常日志:
两个错误,还差一个啊?别急,你再看看改过后的乘法处,我偷偷把@Check
注解删除了,那么框架在进行测试时就不会对该方法进行测试了
虽说是测试框架,但应该有同学发现了这个测试框架我并没有利用反射技术来写,所以严格意义上也不算是框架,但是可以按照该思路进行改写成为一个可以利用任意注解做标记测试任意类,有兴趣的同学可以自己研究把这个框架给写出来
最后,本文内容可能读一遍下来比较难理解,但其实内容不难,跟着做一遍大概就可以掌握了