单元测试是软件开发中针对程序模块(最小单元)进行的正确性检验。在 Java 中,我们通常使用 JUnit 框架来编写和运行单元测试。以下是一个详细的指南:
1. 引入依赖
首先,需要在项目中引入 JUnit 的依赖。如果你使用 Maven,在 pom.xml 文件中添加:
xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version> <!-- 使用最新稳定版 -->
<scope>test</scope>
</dependency>
2. 创建被测类
假设我们有一个简单的字符串工具类:
java
public class StringUtils {
// 反转字符串
public static String reverse(String input) {
if (input == null) {
return null;
}
return new StringBuilder(input).reverse().toString();
}
// 判断字符串是否为空
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}
3. 创建测试类
在 src/test/java 目录下创建对应的测试类:
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StringUtilsTest {
@Test
void testReverse() {
// 正常情况
assertEquals("cba", StringUtils.reverse("abc"));
// 空字符串
assertEquals("", StringUtils.reverse(""));
// null 值
assertNull(StringUtils.reverse(null));
}
@Test
void testIsEmpty() {
assertTrue(StringUtils.isEmpty(null)); // null
assertTrue(StringUtils.isEmpty("")); // 空字符串
assertTrue(StringUtils.isEmpty(" ")); // 空白字符
assertFalse(StringUtils.isEmpty("hello")); // 非空字符串
}
}
4. 常用注解
@Test:标记方法为测试方法@BeforeEach:每个测试方法前执行@AfterEach:每个测试方法后执行@BeforeAll:所有测试方法前执行(静态方法)@AfterAll:所有测试方法后执行(静态方法)@Disabled:禁用测试方法
java
@BeforeEach
void setup() {
// 初始化测试资源
}
@AfterEach
void teardown() {
// 清理资源
}
5. 断言方法
assertEquals(expected, actual):验证相等assertTrue(condition):验证为真assertFalse(condition):验证为假assertNull(object):验证为 nullassertNotNull(object):验证非 nullassertThrows(Exception.class, () -> {}):验证抛出异常
6. 参数化测试
使用 @ParameterizedTest 简化多数据测试:
java
@ParameterizedTest
@ValueSource(strings = {"", " ", "test"})
void testIsEmptyWithParams(String input) {
boolean expected = input == null || input.trim().isEmpty();
assertEquals(expected, StringUtils.isEmpty(input));
}
7. Mockito 模拟依赖
当测试需要隔离外部依赖时,使用 Mockito:
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway;
@InjectMocks
private OrderService orderService;
@Test
void testPlaceOrder() {
// 设置模拟行为
when(paymentGateway.process(anyDouble())).thenReturn(true);
Order order = new Order(100.0);
assertTrue(orderService.placeOrder(order));
// 验证模拟调用
verify(paymentGateway).process(100.0);
}
}
8. 测试命名规范
建议使用 MethodName_StateUnderTest_ExpectedBehavior 模式:
java
@Test
void reverse_NullInput_ReturnsNull() {
assertNull(StringUtils.reverse(null));
}
9. 测试覆盖率
使用 Jacoco 生成覆盖率报告(Maven 配置示例):
xml
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>generate-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
10. 最佳实践
- 测试代码保持与生产代码同等质量
- 每个测试方法只验证一个行为
- 使用
Given-When-Then结构:
java
@Test
void shouldReturnNullWhenInputIsNull() {
// Given
String input = null;
// When
String result = StringUtils.reverse(input);
// Then
assertNull(result);
}
- 避免测试私有方法(通过公共方法间接测试)
- 定期运行测试(建议集成到 CI/CD 流程)