开发基础知识 - JUnit 5

Introduction

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class CalculatorTest {

// @Test 注解表明这是一个测试方法
@Test
void testAddition() {
// 创建被测试类的实例
Calculator calculator = new Calculator();

// 执行被测试的方法
int result = calculator.add(2, 3);

// 使用断言 (Assertion) 来验证结果是否符合预期
assertEquals(5, result, "2 + 3 应该等于 5");
}
}

注解

  • @Test
    它声明了一个方法是一个标准的测试方法。方法应该是 void 类型,方法不应该是 private 的,JUnit 会自动创建测试类的实例来运行这些方法。

  • @BeforeEach & @AfterEach
    这两个注解用于在每个 @Test 方法执行前后设置和清理环境。这对于确保测试之间的独立性至关重要。

  • @BeforeAll & @AfterAll
    这两个注解在当前测试类的所有测试方法运行前后只执行一次。

  • @DisplayName
    默认情况下,测试报告会显示方法名。@DisplayName 允许你提供一个更具描述性的自定义名称。
    比如:@DisplayName("用户名和密码正确时应认证成功")

断言

位于 org.junit.jupiter.api.Assertions下。

  • assertEquals(expected, actual)
  • assertNotEquals(unexpected, actual)
  • assertTrue(boolean condition)
  • assertNull(Object actual)
  • assertArrayEquals(expectedArray, actualArray)
  • assertThrows(expectedType, executable)
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    void testDivisionByZero() {
    Calculator calculator = new Calculator();
    // 验证当除数为0时,是否会抛出 ArithmeticException 异常
    assertThrows(ArithmeticException.class, () -> {
    calculator.divide(1, 0);
    });
    }

Mockito

为什么需要 Mocking

单元测试强调 隔离性。当你测试一个类 A 时,如果它依赖于另一个复杂的类 B(例如,B 可能需要访问数据库、调用网络接口),你不希望因为 B 的问题(如数据库连接失败)导致 A 的测试失败。
Mocking 技术就是为了解决这个问题。它允许你创建一个 Mock 对象,你可以指定它的方法被调用时应该返回什么值或执行什么操作,从而将测试目标从其依赖项中隔离开来。

Mock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 被测试的类
public class UserService {
private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public String getUserGreeting(int userId) {
String userName = userRepository.findUserNameById(userId);
if (userName == null) {
return "Hello, Guest!";
}
return "Hello, " + userName + "!";
}
}

public interface UserRepository {
String findUserNameById(int userId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ExtendWith(MockitoExtension.class) // 让 Mockito 注解生效
class UserServiceTest {

@Mock // 1. 创建一个 UserRepository 的 Mock 对象
private UserRepository mockUserRepository;

@InjectMocks // 2. 创建 UserService 实例,并自动注入上面 @Mock 标记的对象
private UserService userService;

@Test
void testGreetingForExistingUser() {
// 3. Stubbing: 定义 Mock 对象的行为
// 当调用 mockUserRepository 的 findUserNameById 方法并传入参数 1 时,返回 "Alice"
when(mockUserRepository.findUserNameById(1)).thenReturn("Alice");

// 执行测试
String greeting = userService.getUserGreeting(1);

// 断言结果
assertEquals("Hello, Alice!", greeting);
}
}

Spy

  • Mock:当你创建一个 Mock 对象时,你创建的是一个目标类的“空壳子”。调用 Mock 对象的任何方法,除非你用 when(...).thenReturn(...) 对其行为进行了stubbing,否则它只会返回该方法返回类型的默认值(null、0、false、空集合等)。

  • Spy:当你创建一个 Spy 对象时,你实际上是创建了一个真实的对象实例。调用 Spy 对象的任何方法,默认会直接调用真实对象的原始方法,并返回真实的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class OrderServiceTest {
private OrderService spyOrderService;

@BeforeEach
void setUp() {
spyOrderService = Mockito.spy(new OrderService());
}

@Test
void testPlaceOrder_Successfully() {
// 错误的方式,会抛出 UnsupportedOperationException,因为它先执行了真实的 saveToDatabase 方法
// when(spyOrderService.saveToDatabase(anyString())).then...

// 正确的方式:使用 do...when... 语法
doNothing().when(spyOrderService).saveToDatabase(anyString());

String result = spyOrderService.placeOrder(101, 5);
assertEquals("下单成功", result);
}
  • when(spy.method()).thenReturn(...) 的形式会先执行真实的方法。
  • doReturn/doThrow/doAnswer 避免执行真实方法带来的副作用。

开发基础知识 - JUnit 5
https://thiefcat.github.io/2025/08/10/SWE-basic/tools/junit/
Author
小贼猫
Posted on
August 10, 2025
Licensed under