Skip to content

🧪 单元测试

单元测试概述

单元测试是测试代码的最小单元,确保代码的正确性。

JUnit 5(Jupiter)

基本使用

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    Calculator calc;

    @BeforeEach void setUp() { calc = new Calculator(); }

    @Test void add() { assertEquals(8, calc.add(5, 3)); }

    @Test void subtract() { assertEquals(2, calc.subtract(5, 3)); }
}

常用注解速览

java
@BeforeAll   // 类级别前置(需static)
@AfterAll    // 类级别后置(需static)
@BeforeEach  // 方法级前置
@AfterEach   // 方法级后置
@DisplayName("描述性名称")
@Disabled("临时禁用原因")

参数化测试

java
@ParameterizedTest
@CsvSource({"2,3,5", "-1,1,0"})
void add_param(int a, int b, int sum) {
    assertEquals(sum, new Calculator().add(a, b));
}

断言与异常

java
assertAll(
    () -> assertEquals(9, Math.addExact(4,5)),
    () -> assertThrows(ArithmeticException.class, () -> Math.addExact(Integer.MAX_VALUE, 1))
);

超时与条件执行

java
@Test
void completesFast() {
    assertTimeout(Duration.ofMillis(200), () -> service.call());
}

@EnabledOnOs(OS.WINDOWS)
@Test void onlyOnWindows() {}

Mockito

Mock 对象

java
import static org.mockito.Mockito.*;

// 创建 Mock 对象
UserService userService = mock(UserService.class);

// 设置行为
when(userService.getUser(1)).thenReturn(new User("张三"));

// 验证调用
verify(userService).getUser(1);

行为校验与参数匹配

java
when(repo.findById(anyLong())).thenReturn(Optional.of(new User("张三")));
verify(repo, times(1)).findById(argThat(id -> id > 0));

部分 Mock 与 Spy

java
List<String> list = new ArrayList<>();
List<String> spy = spy(list);
doReturn(100).when(spy).size();

Spring 测试(片段)

java
@SpringBootTest
@AutoConfigureMockMvc
class ApiTest {
    @Autowired MockMvc mvc;

    @Test void get_ok() throws Exception {
        mvc.perform(get("/api/users/1")).andExpect(status().isOk());
    }
}

测试金字塔与策略(图示)

          端到端(E2E)
        ────────────────── 稀疏、覆盖关键路径
              服务/集成
        ────────────────── 组件交互、契约与持久化
                单元
        ────────────────── 数量最多、快速稳定

建议:单元测试覆盖核心逻辑;集成测试验证外部交互;E2E 只覆盖最关键用户旅程。

持续集成(CI)与覆盖率

  • 在 CI 中使用 mvn -T 1C -DskipITs testgradle test 并启用缓存。
  • 覆盖率工具:JaCoCo;门禁阈值示例 80%(按模块设置不同阈值更现实)。
xml
<!-- Maven Jacoco 片段 -->
<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.11</version>
  <executions>
    <execution>
      <goals>
        <goal>prepare-agent</goal>
      </goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals>
        <goal>report</goal>
      </goals>
    </execution>
  </executions>
</plugin>

快速失败与可测试性设计

  • 依赖倒置(端口/适配器)与注入,使得组件可 Mock;
  • 纯函数与小函数有利于高覆盖率与定位;
  • 避免静态全局状态与时间/线程随机性,抽象出时钟与执行器。

下一步

掌握了单元测试后,可以继续学习:


💡 提示:单元测试是保证代码质量的重要手段,应该养成编写单元测试的习惯