JUnit单元测试框架由Erich Gamma和Kent Beck编写的一个回归测试框架(Regresion Testing Framework),主要用于Java语言程序的单元测试,目前使用的主流版本是JUnit以上版本。
安装JUnit5累(IDEA)
以一个简单的程序为例:
public class Main {
public static long fact(long n) {//实现阶乘
long r = 1;
for (long i = 1; i <= n; i++) {
r = r * i;
}
return r;
}
}
1.点击类名,并且点击Generate,再选择Test
2.可能会出现以下情况,点击Fix,IDEA帮助你下载好JUnit5(点击OK就行)
3.以后出现就是这样的 选择自己需要进行测试的方法打勾(可以将setup 和tearDown打上勾)
一个测试类中只能声明此注解一次,此注解对应的方法只能被执行一次
@BeforeClass 使用此注解的方法在测试类被调用之前执行
@AfterClass 使用此注解的方法在测试类被调用结束退出之前执行
一个类中有多少个@Test注解方法,以下对应注解方法就被调用多少次
@Before 在每个@Test调用之前执行
@After 在每个@Test调用之后执行(一般用于初始化或者清除)
@Test 使用此注解的方法为一个单元测试用例,一个测试类中可多次声明,每个注解为@Test只执行一次
@Ignore 暂不执行的测试用例,会被JUnit4忽略执行
4.会自动生成
内容如下:(testFact()里面的内容是自己写的测试程序)
然后我们可以直接点击testFact(),右键直接运行,看输出效果就行。
核心测试方法testFact()
加上了@Test
注解,这是JUnit要求的,它会把带有@Test
的方法识别为测试方法。在测试方法内部,我们用assertEquals(1, Factorial.fact(1))
表示,期望Factorial.fact(1)
返回1
。assertEquals(expected, actual)
是最常用的测试方法,它在Assertion
类中定义。Assertion
还定义了其他断言方法,例如:
-
assertTrue()
: 期待结果为true
-
assertFalse()
: 期待结果为false
-
assertNotNull()
: 期待结果为非null
-
assertArrayEquals()
: 期待结果为数组并与期望数组每个元素的值均相等
单元测试的好处
单元测试可以确保单个方法按照正确预期运行,如果修改了某个方法的代码,只需确保其对应的单元测试通过,即可认为改动正确。此外,测试代码本身就可以作为示例代码,用来演示如何调用该方法。
使用JUnit进行单元测试,我们可以使用断言(Assertion
)来测试期望结果,可以方便地组织和运行测试,并方便地查看测试结果。此外,JUnit既可以直接在IDE中运行,也可以方便地集成到Maven这些自动化工具中运行。
在编写单元测试的时候,我们要遵循一定的规范:
一是单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试;
二是每个单元测试应当互相独立,不依赖运行的顺序;
三是测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0
,null
,空字符串""
等情况。
下面解释一下@Before 在每个@Test调用之前执行 和 @After 在每个@Test调用之后执行的用途
JUnit提供了编写测试前准备、测试后清理的固定代码,称之为Fixture。(新建立一个.java)
public class Calculator {
private long n = 0;
public long add(long x) {
n = n + x;
return n;
}
public long sub(long x) {
n = n - x;
return n;
}
}
将Before和After前面的勾都选择上,然后写上自己的测试程序。
public class CalculatorTest {
Calculator calculator;
@BeforeEach
public void setUp() {
this.calculator = new Calculator();
}
@AfterEach
public void tearDown() {
this.calculator = null;
}
@Test
void testAdd() {
assertEquals(100, this.calculator.add(100));
assertEquals(150, this.calculator.add(50));
assertEquals(130, this.calculator.add(-20));
}
@Test
void testSub() {
assertEquals(-100, this.calculator.sub(100));
assertEquals(-150, this.calculator.sub(50));
assertEquals(-130, this.calculator.sub(-20));
}
}
有两个标记为@BeforeEach
和@AfterEach
的方法,它们会在运行每个@Test
方法前后自动运行,通过@BeforeEach
来初始化,通过@AfterEach
来清理资源,称之为Fixture。
上面的测试代码在JUnit中运行顺序如下:
for (Method testMethod : findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest(); // 创建Test实例
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}
//可见,@BeforeEach和@AfterEach会“环绕”在每个@Test方法前后。
JUnit还提供了@BeforeAll
和@AfterAll
,它们在运行所有@Test前后运行,顺序如下:(一般用于初始化和清理一些比较繁琐的资源)(了解)
invokeBeforeAll(CalculatorTest.class);
for (Method testMethod : findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest(); // 创建Test实例
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}
invokeAfterAll(CalculatorTest.class);
因为@BeforeAll
和@AfterAll
在所有@Test
方法运行前后仅运行一次,因此,它们只能初始化静态变量,例如:
public class DatabaseTest {
static Database db;
@BeforeAll
public static void initDatabase() {
db = createDb(...);
}
@AfterAll
public static void dropDatabase() {
...
}
}
总结:
-
对于实例变量,在
@BeforeEach
中初始化,在@AfterEach
中清理,它们在各个@Test
方法中互不影响,因为是不同的实例; -
对于静态变量,在
@BeforeAll
中初始化,在@AfterAll
中清理,它们在各个@Test
方法中均是唯一实例,会影响各个@Test
方法。 -
注意到每次运行一个
@Test
方法前,JUnit首先创建一个XxxTest
实例,因此,每个@Test
方法内部的成员变量都是独立的,不能也无法把成员变量的状态从一个@Test
方法带到另一个@Test
方法。