单元测试指南

单元测试是软件开发中针对程序模块(最小单元)进行的正确性检验。在 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):验证为 null
  • assertNotNull(object):验证非 null
  • assertThrows(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. 最佳实践

  1. 测试代码保持与生产代码同等质量
  2. 每个测试方法只验证一个行为
  3. 使用 Given-When-Then 结构:
java 复制代码
@Test
void shouldReturnNullWhenInputIsNull() {
    // Given
    String input = null;
    
    // When
    String result = StringUtils.reverse(input);
    
    // Then
    assertNull(result);
}
  1. 避免测试私有方法(通过公共方法间接测试)
  2. 定期运行测试(建议集成到 CI/CD 流程)
相关推荐
KmSH8umpK4 分钟前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第五篇
数据库·redis·分布式
lilihuigz8 分钟前
企业培训网站搭建指南:5步在WordPress上创建品牌学院
数据库
WL_Aurora16 分钟前
MySQL 5 卸载到 MySQL 8 安装完整指南(不踩坑版)
数据库·mysql
灰阳阳19 分钟前
MySQL的基本架构
数据库·mysql·架构
@小柯555m38 分钟前
MySql(高级操作符--Where in 和Not in)
数据库·sql·mysql
许彰午39 分钟前
CacheSQL(一):手写数据库的工程化重生
java·数据库·缓存
MmeD UCIZ39 分钟前
MySQL单表存多大的数据量比较合适
数据库·mysql
SarL EMEN1 小时前
mysql之联合索引
数据库·mysql
l1t1 小时前
DeepSeek总结的DuckDB anofox-forecast季节调整时间序列预测插件功能
开发语言·数据库
meta INGU1 小时前
mysql数据被误删的恢复方案
数据库·mysql