单元测试指南
单元测试是软件开发中针对程序模块(最小单元)进行的正确性检验。在 Java 中,我们通常使用 JUnit 框架来编写和运行单元测试。以下是一个详细的指南:
1. 引入依赖
首先,需要在项目中引入 JUnit 的依赖。如果你使用 Maven,在 pom.xml 文件中添加:
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.10.0</version> <!-- 使用最新稳定版 --><scope>test</scope>
</dependency>
2. 创建被测类
假设我们有一个简单的字符串工具类:
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 目录下创建对应的测试类:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;class StringUtilsTest {@Testvoid testReverse() {// 正常情况assertEquals("cba", StringUtils.reverse("abc"));// 空字符串assertEquals("", StringUtils.reverse(""));// null 值assertNull(StringUtils.reverse(null));}@Testvoid testIsEmpty() {assertTrue(StringUtils.isEmpty(null)); // nullassertTrue(StringUtils.isEmpty("")); // 空字符串assertTrue(StringUtils.isEmpty(" ")); // 空白字符assertFalse(StringUtils.isEmpty("hello")); // 非空字符串}
}
4. 常用注解
@Test:标记方法为测试方法@BeforeEach:每个测试方法前执行@AfterEach:每个测试方法后执行@BeforeAll:所有测试方法前执行(静态方法)@AfterAll:所有测试方法后执行(静态方法)@Disabled:禁用测试方法
@BeforeEach
void setup() {// 初始化测试资源
}@AfterEach
void teardown() {// 清理资源
}
5. 断言方法
assertEquals(expected, actual):验证相等assertTrue(condition):验证为真assertFalse(condition):验证为假assertNull(object):验证为 nullassertNotNull(object):验证非 nullassertThrows(Exception.class, () -> {}):验证抛出异常
6. 参数化测试
使用 @ParameterizedTest 简化多数据测试:
@ParameterizedTest
@ValueSource(strings = {"", " ", "test"})
void testIsEmptyWithParams(String input) {boolean expected = input == null || input.trim().isEmpty();assertEquals(expected, StringUtils.isEmpty(input));
}
7. Mockito 模拟依赖
当测试需要隔离外部依赖时,使用 Mockito:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {@Mockprivate PaymentGateway paymentGateway;@InjectMocksprivate OrderService orderService;@Testvoid 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 模式:
@Test
void reverse_NullInput_ReturnsNull() {assertNull(StringUtils.reverse(null));
}
9. 测试覆盖率
使用 Jacoco 生成覆盖率报告(Maven 配置示例):
<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结构:
@Test
void shouldReturnNullWhenInputIsNull() {// GivenString input = null;// WhenString result = StringUtils.reverse(input);// ThenassertNull(result);
}
- 避免测试私有方法(通过公共方法间接测试)
- 定期运行测试(建议集成到 CI/CD 流程)
