Java Testing
本文带你入门junit和mockito。
junit
环境和前置知识
这里我用的是IDEA 、jdk11
需要以下基础:
- java (类、静态函数)
- maven (知道怎么用pom.xml添加依赖)
- IDEA(基础操作)
新建一个项目
导入maven依赖
打开pom.xml 添加junit的几行依赖内容。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
代码结构
现在的idea都会自动生成标准的项目结构,main文件夹里的java文件夹放程序文件,test内的java文件夹则明显就是存放我们测试代码的地方。
通常,单元测试是在单独的源文件夹中创建的,以使测试代码与真实代码分开。Maven 和 Gradle 构建工具的标准约定是:
- src/main/java - 用于 Java 类
- src/test/java - 用于测试类
编写一个待测试的demo
我们写个简单的计算器,模拟实际开发中的一些常见的开发问题,并进行测试。
我们编写了如下代码:
/**
* @author gongfpp https://github.com/gongfpp
* @date 2022/8/26 9:44
*/
public class Calculator {
private int value;
public static int add(int a,int b){
return a+b;
}
public static int sub(int a,int b){
return a+b;
}
public static void main(String[] args) {
System.out.println("yes");
}
}
在这里,我开发代码时复制黏贴不小心把减法(sub)的代码ctrl +v 粘了个加法的进去(假装),但是由于只是一个符号的区别,我没有看出来(假装我是瞎子,但实际开发中代码复杂的时候确实非常难看出来,有时候浪费一天结果发现是很低级的错误,有经历过的都懂)
,接下来我们进行测试
编写测试代码
我们很容易想到在main函数里调用编写的代码进行测试,但程序并非一成不变的,如果每次加新功能测试的时候,main函数内每次都注释掉原有的代码而添加测试代码,测完了再删测试代码并还原注释,一是非常麻烦,二是非常臃肿,三是不规范。于是我们需要一些测试框架来辅助我们进行单元测试,单元测试指的是能对能测试的最小单元(unit)进行测试,一般是一个方法。
我们在test/java/内新建一个CalculatorTest类
Import我们需要的test注解,并写一个没有任何执行内容的方法,在它上面添加@Test注解。
/**
* @author gongfpp https://github.com/gongfpp
* @date 2022/8/26 9:55
*/
import org.junit.jupiter.api.Test;
public class CalculatorTest {
@Test
public void test(){
}
}
这时这个方法可以被作为独立单元来进行测试,左边能看到绿色的运行。这样我们免去了每次都运行main方法的繁杂,而且每个@Test方法都能独立运行。
运行结果如下:
这里由于什么都没写,便只会有测试通过的结果。
太棒了,不写测试代码就不会通不过测试!(别)
为了更多的测试功能,我们import一些静态函数。
import static org.junit.jupiter.api.Assertions.*;
import static 的意思是导入后面的包内的类的静态函数,这样我们使用的时候就不需要类名。
不然我们调用的时候需要Assertions.assertEquals(); 这样比较长,也没必要,测试类里一般只有测试方法,没有重名的风险。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void add5_5_10(){
assertEquals(10,Calculator.add(5,5));
}
@Test
public void sub10_5_5(){
assertEquals(5,Calculator.sub(10,5));
}
}
函数名建议使用一眼能看出来的含义,这里我们测试两个用例(case),assertEquals()
的参数分别为期望值和实际值,当两个相同则测试通过,不同则失败。
我们点击类旁的run则可以运行所有带@Test的测试用例。运行结果如下。
明确指出期望值和实际值,这样我们就能去修bug了。
同理,常用的测试函数如下 ,可以在注释里看函数作用:
/**
* @author gongfpp https://github.com/gongfpp
* @date 2022/8/26 9:55
*/
import org.junit.jupiter.api.*;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
public class CalculatorTest {
@Test
public void add5_5_10(){
assertEquals(10,Calculator.add(5,5));
}
@Test
public void sub10_5_5(){
assertEquals(5,Calculator.sub(10,5));
}
@Test
public void testTrueWithTrue(){
//某个结果必须为true,否则测试失败 这里成功
assertTrue(true);
}
@Test
@DisplayName("我的名字会代替函数名出现在测试运行窗口,你跑我一下试试")
public void tetsTrueWithFalse(){
//某个结果必须为true,否则测试失败 这里失败
assertTrue(false);
}
@Test
public void testThrow(){
//运行不抛出异常,这里一般用lambda函数,在lambda内写逻辑代码,lambda固定格式 ()->{} ,这里只需要知道代码写在大括号里就行了。
assertDoesNotThrow(()->{
throw new Exception("AAA");
});
}
@Test
public void testTimeout1Sec(){
// 限制时间未完成执行则测试不通过,第一个函数为Duration.ofxxx ,可选秒、分钟、小时等,
assertTimeout(Duration.ofSeconds(1),()->{
//线程休眠4秒,这里时间限制是1秒,所以肯定运行超时,测试不通过
Thread.sleep(4000);
});
}
@BeforeEach
public void beforeEach(){
// BeforeEach一般是每个方法前的初始化,比如Calculator calculator = new Calculator() 这样的代码,
// 每次都要写一遍很麻烦,所以有了这个,可以理解为这里的代码会被复制黏贴到每个test函数的开头部分,不能是static
System.out.println("每个运行方法前我出来一次 我不能是静态函数");
}
@BeforeAll
public static void beforeAll(){
// beforeAll一般是所以方法执行前的一次操作,比如LogSave("开始测试calculor类"),这样的代码,一批测试只运行一次,
// 必须是static,可以理解为先执行这里的代码,再去分别运行其他的@Test代码
System.out.println("执行所有测试前我来一次 我必须是静态函数");
}
//可以代替@Test 并且跑3次,参数为运行次数
@RepeatedTest(3)
public void other(){
// 当参数条件为true才往下运行,否则忽略该测试
// 比如先跑一个条件方法,当满足才往下继续
assumeTrue(false);
}
}