JUnit
2023-04-15
学习教程:https://www.w3cschool.cn/junit/
JUnit 是一个 Java 编程语言的单元测试框架。Junit也就是所谓的白盒测试,能快速完成单元测试(Unit Test,又称为模块测试)。
特点
- 提供注解来识别测试方法。
- 提供断言来测试预期结果。
- 提供测试运行来运行测试。
简单的示例
创建一个Demo类
public class Demo {
public static int add(int a, int b) {
return a+b;
}
}
创建一个相应的Test类
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class DemoTest {
@Test
public void testAdd() {
assertEquals(5,Demo.add(1,4));
}
}
创建Test Runner 类
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class TestRunner {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(DemoTest.class);
for (Failure failure: result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
运行TestRunner的结果:
// 验证通过
true
// 故意模拟验证失败的情况,执行结果:
testAdd(DemoTest): expected:<3> but was:<5>
false
基本用法
Test类:
- 类名以Test为待测试类名的后缀
- 测试方法以test为方法名的前缀
- 单独测试某方法,可以在该测试方法处点右键进行单独测试;如果要运行该测试类的所有测试方法,可以借助测试运行器TestRunner(如上面的示例)(或者直接右键运行该测试类)
常用注解
- @Test: 表示为测试方法
- @BeforeClass: static方法,会在所有方法执行前执行
- @AfterClass: 和上面类似,在所有方法执行后执行
- @Before: 每个测试方法执行前会执行
- @After: 每个测试方法执行后会执行
- @Ignore: 会被测试运行器忽略
- @RunWith: 可以更改测试运行器
JUnitCore
在测试运行器中,使用JUnitCore
类来执行。对于只有一次的测试运行,可以使用静态方法 runClasses(Class[])
。
Junit API
JUnit 中的最重要的程序包是
junit.framework
它包含了所有的核心类。
Assert
import org.junit.Assert;
包含检验各种条件是否符合的方法,若不符合则抛错
TestCase
import junit.framework.TestCase;
定义了运行多重测试的固定装置
import org.junit.Test;
import junit.framework.TestCase;
public class DemoTest extends TestCase{
@Test
public void testTestCase() {
System.out.println("No of Test Case = "+ this.countTestCases());
System.out.println("Test Case Name = "+ this.getName());
this.setName("newName");
System.out.println("Test Case Name = "+ this.getName());
}
}
执行结果:
No of Test Case = 1
Test Case Name = testTestCase
Test Case Name = newName
true
TestResult
import junit.framework.TestResult;
集合了执行测试样例的所有结果
TestSuite
import junit.framework.TestSuite;
表示测试的集合
测试套件
测试套件是一种将多个测试类组合在一起执行的方式,以便对整个应用程序或系统进行更全面的测试。测试套件类似于一个集合,它将多个测试类组合在一起,每个测试类都是一个测试套件的一部分。测试套件会依次运行其中包含的所有测试类,并输出整个测试过程的结果。
如何使用
需要编写一个特殊的测试类来定义测试套件,这个测试类需要使用@RunWith(Suite.class)
注解来运行测试套件。在测试类中,你需要使用@Suite.SuiteClasses
注解来指定包含在测试套件中的测试类。
例如:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestEmployeeDetails.class,
TestEmployeeSalary.class
})
public class EmployeeTestSuite {
// 这个类没有测试方法,只是用来指定包含在测试套件中的测试类
}
其中,EmployeeTestSuite
是一个定义测试套件的测试类,包含两个测试类TestEmployeeDetails
和TestEmployeeSalary
。如果你运行EmployeeTestSuite
,JUnit将依次运行TestEmployeeDetails
和TestEmployeeSalary
中的所有测试方法,并输出测试结果。
注意:一定要保证测试套件之间没有循环包含关系,否则会有死循环!
忽略测试
@Ignore
: 可以修饰测试类或测试方法,使得此测试类或测试方法不会被执行到
注意:JUnit5 中@Igonre不起作用,一个替换方案是使用@Disabled
时间测试
一般用于测试某方法是否执行时间过长,设定timeout
参数用于指定该测试方法的最大执行时间,单位为毫秒。如果测试时执行超过该时长,则会中止测试方法并标记为失败。例子如下:
@Test(timeout = 1000)
public void testTimeout() throws InterruptedException { // 等待 2 秒钟,超时时间为 1 秒钟
Thread.sleep(2000);
}
异常测试
设定expected
参数,用于验证待测试的方法抛的异常是否符合预期。例子如下:
@Test(expected = ArithmeticException.class)
public void testPrintMessage() {
...
}
参数化测试
使用不同的值多次测试同一个方法,简化测试过程。构造特点:
- 用
@RunWith(Parameterized.class)
通知JUnit这是一个参数化测试类 @Parameterized.Parameters
使用该注释构造公共的静态方法,返回一个对象集合作为测试数据集合- 创建一个构造方法,与测试数据相匹配
例子如下:
@RunWith(Parameterized.class)
public class CalculatorTest {
private int num1;
private int num2;
private int expectedResult;
private Calculator calculator;
@Parameterized.Parameters
public static Collection<Object[]> input() {
return Arrays.asList(new Object[][]{
{2, 3, 5},
{5, 7, 12},
{9, 4, 13},
{8, 1, 9},
{6, 2, 8}
});
}
public CalculatorTest(int num1, int num2, int expectedResult) {
this.num1 = num1;
this.num2 = num2;
this.expectedResult = expectedResult;
}
@Before
public void setUp() {
calculator = new Calculator();
}
@Test
public void testAdd() {
int result = calculator.add(num1, num2);
assertEquals(expectedResult, result);
}
}
实际项目中的应用
比如在一个Springboot+mybatisPlus的项目中,要如何利用JUnit测试在ServiceImpl
中实现的各种方法呢?这里我直接给出ChatGPT给我的回答吧:
假设有一个名为UserServiceImpl
的实现类,实现了UserService
接口,该接口定义了数据库表user
的增删改查方法,我们可以为其编写JUnit测试方法。
这里我们为UserServiceImpl
中的每一个方法编写了一个对应的测试方法,其中使用了JUnit提供的Assertions
断言方法,比如Assertions.assertTrue()
和Assertions.assertNotNull()
,用来判断测试结果是否正确。注意,在测试方法上要加上@Test
注解,以标识这是一个JUnit测试方法,同时需要使用@SpringBootTest
注解来启动Spring容器,以便能够注入UserService
实例。
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void testSaveUser() {
User user = new User();
user.setName("test");
user.setAge(18);
boolean result = userService.save(user);
Assertions.assertTrue(result);
}
@Test
public void testUpdateUser() {
User user = userService.getById(1L);
user.setAge(20);
boolean result = userService.updateById(user);
Assertions.assertTrue(result);
}
@Test
public void testDeleteUser() {
boolean result = userService.removeById(1L);
Assertions.assertTrue(result);
}
@Test
public void testGetUserById() {
User user = userService.getById(2L);
Assertions.assertNotNull(user);
}
@Test
public void testGetUserPage() {
Page<User> page = new Page<>(1, 10);
Page<User> userPage = userService.page(page, null);
Assertions.assertNotNull(userPage);
}
}
测试如何不影响真实的表数据
当我们使用JUnit测试时,例如插入数据,必然会往表中插入脏数据。那么,如何在不影响真实表数据的情况下,完成测试呢?
- 使用注解
@Transactional
实现事务回滚 - 在测试完成后,使用
@After
注解执行清空数据等操作 - 在测试方法中写清空数据的操作
测试时如何指定id
当我们使用JUnit测试删除或更新操作时,往往需要指定操作id。这里也给出两种常用做法:
- 写死数据库中已有的某id来测
- 通过
@Before
注解,在方法中预置记录并在后续测试方法中使用此id。但注意最后要清理此脏数据。
进阶使用
- 使用
ant
运行junit,可以简化测试过程、生成测试报告等。等用到了再学习吧 - 测试报告生成:除了使用
ant
生成测试报告外,有些IDE也支持生成测试报告。比如在IDEA中执行时选择“Run xxx with Coverage”
,然后就可以导出测试报告 - 还有其他基于JUnit的框架扩展,如JWebUnit、MockObject