开发基础知识 - JUnit 5
Introduction
一个简单的例子:
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)
@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
// 被测试的类
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);
}@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 对象的任何方法,默认会直接调用真实对象的原始方法,并返回真实的结果。
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
http://example.com/2025/08/10/JAVA/junit/