本文目录导读:

我来详细介绍Java单元测试的编写方法和最佳实践。
单元测试基本结构
示例代码:待测试的类
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return a / b;
}
public boolean isEven(int number) {
return number % 2 == 0;
}
}
对应的单元测试
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@AfterEach
void tearDown() {
calculator = null;
}
@Test
@DisplayName("测试加法运算")
void testAdd() {
int result = calculator.add(5, 3);
assertEquals(8, result, "5 + 3 应该等于 8");
}
@Test
@DisplayName("测试除法运算")
void testDivide() {
int result = calculator.divide(10, 2);
assertEquals(5, result);
}
@Test
@DisplayName("测试除零异常")
void testDivideByZero() {
assertThrows(IllegalArgumentException.class,
() -> calculator.divide(10, 0));
}
@Test
@DisplayName("测试偶数判断")
void testIsEven() {
assertTrue(calculator.isEven(4));
assertFalse(calculator.isEven(3));
}
}
常用测试注解
import org.junit.jupiter.api.*;
class TestLifecycleDemo {
@BeforeAll
static void initAll() {
// 在所有测试方法执行前执行一次(必须为static)
System.out.println("初始化测试环境");
}
@AfterAll
static void cleanAll() {
// 在所有测试方法执行后执行一次(必须为static)
System.out.println("清理测试环境");
}
@BeforeEach
void init() {
// 每个测试方法执行前执行
System.out.println("准备测试数据");
}
@AfterEach
void clean() {
// 每个测试方法执行后执行
System.out.println("清理测试数据");
}
@Test
@DisplayName("示例测试方法")
void testExample() {
System.out.println("执行测试");
}
@Test
@Disabled("暂时跳过此测试")
void skippedTest() {
// 被跳过的测试
}
}
断言方法详解
class AssertionDemo {
@Test
void testAssertions() {
// 基本断言
assertEquals(5, 5, "值应该相等");
assertNotEquals(5, 10, "值不应该相等");
// 布尔断言
assertTrue(10 > 5, "条件应该为true");
assertFalse(5 > 10, "条件应该为false");
// 对象断言
String str1 = new String("hello");
String str2 = new String("hello");
assertEquals(str1, str2, "对象内容应该相等");
assertNotSame(str1, str2, "对象引用应该不同");
// 空值断言
String nullStr = null;
assertNull(nullStr, "对象应该为null");
assertNotNull("hello", "对象不应该为null");
// 数组断言
int[] expected = {1, 2, 3};
int[] actual = {1, 2, 3};
assertArrayEquals(expected, actual, "数组应该相等");
// 异常断言
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("测试异常");
});
// 多断言组合
assertAll("多个断言",
() -> assertEquals(1, 1),
() -> assertTrue(true),
() -> assertNotNull("test")
);
}
}
Mockito模拟测试
// 添加Maven依赖
/*
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
*/
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
// 待测试的服务类
class UserService {
private UserRepository userRepository;
private EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(String name, String email) {
User user = new User(name, email);
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(email);
return saved;
}
}
// Mockito测试
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testCreateUser() {
// 准备Mock数据
User testUser = new User("张三", "zhangsan@example.com");
when(userRepository.save(any(User.class))).thenReturn(testUser);
// 执行测试
User result = userService.createUser("张三", "zhangsan@example.com");
// 验证结果
assertEquals("张三", result.getName());
assertEquals("zhangsan@example.com", result.getEmail());
// 验证方法调用
verify(userRepository, times(1)).save(any(User.class));
verify(emailService, times(1)).sendWelcomeEmail("zhangsan@example.com");
}
@Test
void testCreateUserException() {
// 模拟异常
when(userRepository.save(any(User.class)))
.thenThrow(new RuntimeException("数据库异常"));
// 验证异常
assertThrows(RuntimeException.class,
() -> userService.createUser("张三", "zhangsan@example.com"));
// 验证邮件不被发送
verify(emailService, never()).sendWelcomeEmail(anyString());
}
}
Spring Boot集成测试
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("张三"))
.andExpect(jsonPath("$.email").value("zhangsan@example.com"));
}
@Test
void testCreateUser() throws Exception {
String userJson = "{\"name\":\"李四\",\"email\":\"lisi@example.com\"}";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("李四"));
}
}
测试数据驱动
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
class ParameterizedTestDemo {
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 8, 10})
void testEvenNumbers(int number) {
Calculator calculator = new Calculator();
assertTrue(calculator.isEven(number));
}
@ParameterizedTest
@CsvSource({
"1, 2, 3",
"10, 20, 30",
"100, 200, 300"
})
void testAddWithMultipleInputs(int a, int b, int expected) {
Calculator calculator = new Calculator();
assertEquals(expected, calculator.add(a, b));
}
@ParameterizedTest
@MethodSource("provideTestData")
void testWithMethodSource(String input, int expected) {
assertEquals(expected, input.length());
}
static Stream<Arguments> provideTestData() {
return Stream.of(
Arguments.of("hello", 5),
Arguments.of("world", 5),
Arguments.of("test", 4)
);
}
}
最佳实践总结
测试命名规范
// 方法命名:方法名_场景_期望结果
@Test
void calculateTotalPrice_whenNoDiscount_shouldReturnOriginalPrice() {
// ...
}
@Test
void calculateTotalPrice_whenVIPCustomer_shouldApplyDiscount() {
// ...
}
测试结构(AAA模式)
@Test
void testSomething() {
// Arrange(准备)
Calculator calculator = new Calculator();
int expected = 10;
// Act(执行)
int actual = calculator.add(5, 5);
// Assert(验证)
assertEquals(expected, actual);
}
注意事项
- 单一职责:每个测试方法只测试一个功能点
- 独立性:测试之间不应相互依赖
- 可重复性:同一测试多次执行结果应一致
- 明确性:测试名称应清晰表达测试意图
- 隔离性:使用Mock隔离外部依赖
- 覆盖率:关注核心逻辑和边界条件的覆盖
这就是Java单元测试的基本写法,实际开发中,建议结合项目的具体需求和框架选择合适的测试策略。