说白了,JUnit就是避免启动完整项目,只调用项目里的部分方法,然后程序员根据调用方法后的结果来判断这个方法写的有没有问题。
JUnit是Java语言中最流行的单元测试框架之一。JUnit提供了一种简单的方法来编写、运行和组织测试代码,以验证代码的正确性和功能。
这里IDEA以2022.3的版本为例,不同的版本推荐引入的JUnit版本也可能有差异。
假设我们在进行一个写作业的模拟过程,老师布置了三项作业,分别是英语,数学,生物作业。
这里我们创建一个HomeWork类来封装这些作业的属性。
package budiu;
/*Author:l
Explain:
Version:1.0*/
import static java.lang.Thread.sleep;
public class Homework {
//假设有三项作业,分别是英语,数学,生物
private String englishHomework="英语阅读";
private String mathHomework="数学微积分";
private String biologyHomework="生物标本制作";
//对应方法
//完成英语作业
public void doEnglishHomeWork() throws InterruptedException {
System.out.println("正在完成"+englishHomework);
//具体业务逻辑
sleep(3000);
System.out.println("已完成");
}
//完成数学作业
public void doMathHomeWork() throws InterruptedException {
System.out.println("正在完成"+mathHomework);
//具体业务逻辑
sleep(3000);
System.out.println("已完成");
}
//完成生物作业
public void doBiologyHomeWork() throws InterruptedException {
System.out.println("正在完成"+biologyHomework);
//具体业务逻辑
sleep(3000);
System.out.println("已完成");
}
}
项目运行时,领导提出个规定,Main方法里必须以先完成英语作业,再完成数学作业,再完成生物作业。
package budiu;
public class Main {
public static void main(String[] args) throws InterruptedException {
Homework homework=new Homework();
//先完成英语作业
homework.doEnglishHomeWork();
//再完成数学作业
homework.doMathHomeWork();
//再完成生物作业
homework.doBiologyHomeWork();
System.out.println("所有作业已经完成");
}
}
但现在完成英语作业和数学作业的业务逻辑已经完全正确了,但是完成生物作业的具体业务逻辑是否正确还有待验证,那么该如何验证呢?
按照平时的习惯,我们会直接运行Main方法,通过运行时的异常来看完成生物作业的方法是否正确。(前面领导已经规定过Main方法里不能把生物作业放在前面写)
但是我们很容易发现,这运行整个项目会非常缓慢,因为我在完成英语作业,完成数学作业,完成生物作业的方法里都加了休眠时间。
我们必须等待6秒的时间才能到完成生物作业方法的执行。实际生活中的完整项目启动的等待时间甚至会比这个更长
sleep(3000);
这时候,JUnit就可以派上用场了。
我们先创建一个Test的软件包,实话说这并不是必要条件,但这是良好习惯。
(这里的Test的包和我写代码的budiu包是同级的)
在test包里添加自己想要测试的类:
测试类的名字一般是正式代码中要测试的类的类名后面加上Test。
这里我们要测试的类是Homework
类,也就是说测试名类可以是HomeworkTest
。
在测试类里添加自己想要测试的方法:
测试方法的名字一般是正式代码中要测试方法名的前面加上test,注意方法命名要遵守小驼峰规范。
这里要添加的测试方法是doBiologyHomework()
,也就是说测试方法可以是testDoBiologyHomework()
。
测试方法通常有几个规则:
无返回值;
无参数列表;
原理:测试方法是独立运行的,而返回值和参数列表一般是与其他方法有联系的,即使你的正式代码中有返回值和参数列表,你依旧可以把它们去掉。
这里的命名并没有绝对标准,有自己的编程习惯就好。
package test;
/*Author:l
Explain:作业测试类
Version:1.0*/
import budiu.Homework;
public class HomeworkTest {
//方法签名里的异常只是避免sleep()方法的异常而已,无需在意
public void testDoBiologyHomework() throws InterruptedException {
Homework homework=new Homework();
homework.doBiologyHomeWork();
}
}
单凭这些代码是无法进行单元测试(即独立运行)的,因为到目前我们还未见到过Junit的身影,将JUnit引入后即可使用,但该如何引用呢?
之前我都是自己在网上找JUnit的包下载,然后手动导入,不过现在IDEA已经很智能了,你只需要写一个注解在你要测试的方法上面,然后alter+enter快捷键即可快速导入。
5.8版本没用过,不过4已经够用了,所以这里我导入的是JUnit4。
java开发所需要导入的几乎所有包,都可以在Maven中找到,当弹出上面的窗口是,直接点确定即可。
之后你会发现外部库里已经出现了JUnit相关的包了。
然后你就会发现,测试方法已经有可以单独运行的图标了。
点击运行,会出现一个与控制台类似的界面,我们在测试时,并不是要看里面的结果,而是看测试的颜色,绿色即表示通过。里面的结果是不是正确的,达到了预期效果没有,这个JUnit管不着。
现在我们尝试在测试方法里弄点异常,比如加个算术异常:int i=1/0;
public void doBiologyHomeWork() throws InterruptedException {
int i=1/0;
System.out.println("正在完成"+biologyHomework);
sleep(3000);
System.out.println("已完成");
}
但你说有没有那种测试后还能看是否达到预期效果,还真有,它叫断言。
JUnit 断言用于验证代码是否按照预期执行。
我们在Homework类里面再添加一个方法:
//罢工的方法
public String strike(){
String str="老子不想做作业了";
System.out.println(str);
return str;
}
将它也按之前的步骤添加到测试类中,
@Test
public void testStrike(){
Homework homework=new Homework();
//预期值str1
String str1="对不起,作业放家里了";
//预期值str2
String str2="老子不想做作业了";
String str3=homework.strike();
//Assert.assertArrayEquals(str1.toCharArray(), str3.toCharArray());
Assert.assertArrayEquals(str2.toCharArray(), str3.toCharArray());
}
注释掉str1时的运行结果,测试通过。
取消注释后:它甚至能告诉你从哪开始就不符合预期结果了。
断言方法有很多,但不难,想用的时候去查查API就行,只需要记住有一个Assert类,它里面有断言方法。
在JUnit里除了@Test以外,还有两个注解要了解一下,一个是Before,一个是After(还有其他的,但用的不多)。
@Before
:用于在每个测试方法执行前执行一次,常用于初始化测试所需的对象或数据。
@After
:用于在每个测试方法执行后执行一次,常用于清理测试过程中产生的临时数据或对象。
假如说Homework类的各个作业并没有初始值,同时我们添加get和set方法。
package budiu;
/*Author:l
Explain:
Version:1.0*/
import static java.lang.Thread.sleep;
public class Homework {
//假设有三项作业,分别是英语,数学,生物
private String englishHomework;
private String mathHomework;
private String biologyHomework;
//对应方法
//完成英语作业
public void doEnglishHomeWork() throws InterruptedException {
System.out.println("正在完成"+englishHomework);
sleep(3000);
System.out.println("已完成");
}
//完成数学作业
public void doMathHomeWork() throws InterruptedException {
System.out.println("正在完成"+mathHomework);
sleep(3000);
System.out.println("已完成");
}
//完成生物作业
public void doBiologyHomeWork() throws InterruptedException {
System.out.println("正在完成"+biologyHomework);
sleep(3000);
System.out.println("已完成");
}
//罢工的方法
public String strike(){
String str="老子不想做作业了";
System.out.println(str);
return str;
}
public String getEnglishHomework() {
return englishHomework;
}
public void setEnglishHomework(String englishHomework) {
this.englishHomework = englishHomework;
}
public String getMathHomework() {
return mathHomework;
}
public void setMathHomework(String mathHomework) {
this.mathHomework = mathHomework;
}
public String getBiologyHomework() {
return biologyHomework;
}
public void setBiologyHomework(String biologyHomework) {
this.biologyHomework = biologyHomework;
}
}
这样作业的初始值都会是空值,在测试时运行就会成这样:
这时候,我们就可以用@Before注解来进行一些初始化,当然你在@Test里进行初始化也没错,但规范性会稍差一点。
加入初始化方法:
(还要将Homework类设为测试类的字段,保证初始化后,访问的是同一个对象。)
public class HomeworkTest {
private Homework homework;
@Before
public void init(){
homework=new Homework();
homework.setBiologyHomework("制作生态缸");
}
//方法签名里的异常只是避免sleep()方法的异常而已,无需在意
@Test
public void testDoBiologyHomework() throws InterruptedException {
homework.doBiologyHomeWork();
}
}