JUnit Jupiter Assertions 是 JUnit 5 测试框架的核心断言库,用于在测试中验证预期结果与实际结果是否一致。它提供了一组丰富、灵活的静态方法,使得测试断言更加直观和易读。
主要特点
-
静态导入:推荐静态导入断言方法
-
丰富的断言类型:支持多种数据类型和条件
-
失败消息定制:支持自定义失败消息
-
流式API:部分断言支持链式调用
-
Lambda支持:延迟消息评估
与 JUnit 4 的对比
| 特性 | JUnit 4 (org.junit.Assert) | JUnit 5 (org.junit.jupiter.api.Assertions) |
|---|---|---|
| 包名 | org.junit.Assert | org.junit.jupiter.api.Assertions |
| 方法命名 | 驼峰命名 (assertEquals) | 同样驼峰命名,但更多方法 |
| 超时测试 | @Test(timeout = ...) | assertTimeout() |
| 异常测试 | @Test(expected = ...) | assertThrows() |
| 分组断言 | 无 | assertAll() |
| Lambda支持 | 无 | 支持延迟消息 |
| 实例检查 | 无 | assertInstanceOf() |
断言方法
基本断言
java
import static org.junit.jupiter.api.Assertions.*;
// 相等性断言
assertEquals(expected, actual);
assertEquals(expected, actual, "自定义失败消息");
assertEquals(expected, actual, () -> "延迟评估的消息");
// 不相等断言
assertNotEquals(unexpected, actual);
// 对象引用断言
assertSame(expected, actual); // 同一对象
assertNotSame(unexpected, actual); // 不同对象
// 空值检查
assertNull(actual);
assertNotNull(actual);
布尔和条件断言
java
// 条件断言
assertTrue(condition);
assertFalse(condition);
// 基于Supplier的条件断言
assertTrue(() -> condition);
assertTrue(condition, () -> "失败时显示的消息");
异常断言
java
// 验证是否抛出指定异常
Exception exception = assertThrows(
ExpectedExceptionType.class,
() -> codeThatShouldThrowException()
);
// 获取异常详细信息
assertEquals("Expected message", exception.getMessage());
// 验证没有抛出异常
assertDoesNotThrow(() -> codeThatShouldNotThrowException());
超时断言
java
// 验证执行时间不超过指定时间
assertTimeout(Duration.ofSeconds(1), () -> {
// 测试代码
});
// 可中断的超时断言(超时后继续执行)
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
// 测试代码
});
数组和可迭代对象断言
java
// 数组断言
assertArrayEquals(expectedArray, actualArray);
// 可迭代对象断言
Iterable<String> expected = Arrays.asList("a", "b", "c");
Iterable<String> actual = Arrays.asList("a", "b", "c");
assertIterableEquals(expected, actual);
// 行迭代器断言(忽略空白和排序)
assertLinesMatch(List<String> expectedLines, List<String> actualLines);
复合断言(assertAll)
java
@Test
void multipleAssertions() {
Person person = new Person("zhangsan", "lisi");
assertAll("person",
() -> assertEquals("zhangsan", person.getFirstName()),
() -> assertEquals("lisi", person.getLastName()),
() -> assertNotNull(person.getId())
);
}
// 所有断言都会执行,即使某些失败
失败断言
java
// 直接使测试失败
fail("测试应该失败");
fail(() -> "延迟评估的失败消息");
assertInstanceOf
java
@Test
void testInstanceOf() {
Object obj = "Hello";
// 验证对象是指定类型的实例
String result = assertInstanceOf(String.class, obj);
assertEquals("Hello", result);
// 带自定义消息
assertInstanceOf(String.class, obj, "应该是字符串类型");
}
assertNotEquals 的 Delta 支持
java
@Test
void testNotEqualsWithDelta() {
// 浮点数不相等断言,带误差范围
assertNotEquals(1.0, 1.1, 0.01);
// 这行会失败,因为 1.0 和 1.1 的差 > 0.01
}
使用 Lambda 表达式延迟消息评估
java
@Test
void testWithExpensiveMessage() {
String actual = expensiveOperation();
// 好的做法:消息延迟评估
assertEquals("expected", actual,
() -> "失败消息,只在实际失败时计算: " + expensiveMessage());
// 避免的做法:总是计算消息
assertEquals("expected", actual,
"失败消息: " + expensiveMessage()); // 即使测试通过也会计算
}
使用 assertAll 进行分组断言
java
@Test
void testPerson() {
Person person = service.findPerson(1);
// 所有断言都会执行,提供完整的问题视图
assertAll("person properties",
() -> assertNotNull(person.getId(), "id should not be null"),
() -> assertEquals("John", person.getFirstName()),
() -> assertEquals(30, person.getAge()),
() -> assertTrue(person.isActive())
);
}
异常测试
java
@Test
void testException() {
// 验证异常类型和消息
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> validator.validate(null),
"Expected validate() to throw"
);
// 进一步验证异常详情
assertEquals("Value cannot be null", exception.getMessage());
assertEquals("VALIDATION_ERROR", exception.getErrorCode());
}
java
public class CustomAssertions {
public static void assertValidEmail(String email) {
assertNotNull(email, "Email should not be null");
assertTrue(email.contains("@"),
() -> "Email should contain '@': " + email);
assertTrue(email.length() >= 5,
() -> "Email should be at least 5 chars: " + email);
}
public static void assertBetween(int value, int min, int max) {
assertAll(
() -> assertTrue(value >= min,
() -> String.format("%d should be >= %d", value, min)),
() -> assertTrue(value <= max,
() -> String.format("%d should be <= %d", value, max))
);
}
}
// 使用自定义断言
@Test
void testCustomAssertions() {
CustomAssertions.assertValidEmail("zhangsan@demo.com");
CustomAssertions.assertBetween(age, 18, 65);
}
实际示例
User
java
import java.time.LocalDateTime;
import java.util.UUID;
/**
* 用户认证系统 - 功能级测试
* 测试用户注册、登录、密码重置等核心功能
*/
// 用户实体类
public class User {
private String id;
private String username;
private String email;
private String hashedPassword;
private boolean isActive;
private boolean isLocked;
private int failedLoginAttempts;
private LocalDateTime createdAt;
private LocalDateTime lastLoginAt;
public User(String username, String email, String hashedPassword) {
this.id = UUID.randomUUID().toString();
this.username = username;
this.email = email;
this.hashedPassword = hashedPassword;
this.isActive = true;
this.isLocked = false;
this.failedLoginAttempts = 0;
this.createdAt = LocalDateTime.now();
}
public boolean verifyPassword(String password) {
// 简化的密码验证(实际应使用BCrypt等)
return this.hashedPassword.equals(hashPassword(password));
}
public void recordSuccessfulLogin() {
this.lastLoginAt = LocalDateTime.now();
this.failedLoginAttempts = 0;
this.isLocked = false;
}
public void recordFailedLogin() {
this.failedLoginAttempts++;
if (this.failedLoginAttempts >= 5) {
this.isLocked = true;
}
}
public void resetPassword(String newHashedPassword) {
this.hashedPassword = newHashedPassword;
this.failedLoginAttempts = 0;
this.isLocked = false;
}
public void deactivate() {
this.isActive = false;
}
public void activate() {
this.isActive = true;
}
private String hashPassword(String password) {
// 简化版本 - 实际应使用安全的哈希算法
return "HASHED_" + password;
}
// Getters
public String getId() {
return id;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public boolean isActive() {
return isActive;
}
public boolean isLocked() {
return isLocked;
}
public int getFailedLoginAttempts() {
return failedLoginAttempts;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public LocalDateTime getLastLoginAt() {
return lastLoginAt;
}
}
UserRepository
java
import java.util.HashMap;
import java.util.Map;
// 用户仓库(模拟数据库)
public class UserRepository {
private Map<String, User> usersById = new HashMap<>();
private Map<String, User> usersByUsername = new HashMap<>();
private Map<String, User> usersByEmail = new HashMap<>();
public User save(User user) {
usersById.put(user.getId(), user);
usersByUsername.put(user.getUsername(), user);
usersByEmail.put(user.getEmail(), user);
return user;
}
public User findById(String id) {
return usersById.get(id);
}
public User findByUsername(String username) {
return usersByUsername.get(username);
}
public User findByEmail(String email) {
return usersByEmail.get(email);
}
public boolean existsByUsername(String username) {
return usersByUsername.containsKey(username);
}
public boolean existsByEmail(String email) {
return usersByEmail.containsKey(email);
}
public void delete(String id) {
User user = usersById.remove(id);
if (user != null) {
usersByUsername.remove(user.getUsername());
usersByEmail.remove(user.getEmail());
}
}
}
UserService
java
// 用户服务 - 核心业务逻辑
public class UserService {
private UserRepository userRepository;
private EmailValidator emailValidator;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
this.emailValidator = new EmailValidator();
}
public User register(String username, String email, String password) {
// 验证输入
validateRegistrationInput(username, email, password);
// 检查唯一性
if (userRepository.existsByUsername(username)) {
throw new IllegalArgumentException("用户名已存在: " + username);
}
if (userRepository.existsByEmail(email)) {
throw new IllegalArgumentException("邮箱已被注册: " + email);
}
// 创建用户
String hashedPassword = hashPassword(password);
User user = new User(username, email, hashedPassword);
return userRepository.save(user);
}
public User login(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new SecurityException("用户不存在或密码错误");
}
if (!user.isActive()) {
throw new SecurityException("账户已被禁用");
}
if (user.isLocked()) {
throw new SecurityException("账户已被锁定,请重置密码");
}
if (user.verifyPassword(password)) {
user.recordSuccessfulLogin();
return user;
} else {
user.recordFailedLogin();
throw new SecurityException("用户名或密码错误");
}
}
public void resetPassword(String email, String newPassword) {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new IllegalArgumentException("邮箱未注册: " + email);
}
validatePassword(newPassword);
String hashedPassword = hashPassword(newPassword);
user.resetPassword(hashedPassword);
}
public void updateEmail(String userId, String newEmail) {
User user = userRepository.findById(userId);
if (user == null) {
throw new IllegalArgumentException("用户不存在");
}
if (!emailValidator.isValid(newEmail)) {
throw new IllegalArgumentException("无效的邮箱格式: " + newEmail);
}
if (userRepository.existsByEmail(newEmail) &&
!user.getEmail().equals(newEmail)) {
throw new IllegalArgumentException("邮箱已被使用: " + newEmail);
}
userRepository.delete(user.getId());
user = new User(user.getUsername(), newEmail, user.getUsername());
userRepository.save(user);
}
private void validateRegistrationInput(String username, String email, String password) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (username.length() < 3 || username.length() > 20) {
throw new IllegalArgumentException("用户名长度必须在3-20个字符之间");
}
if (!username.matches("^[a-zA-Z0-9_]+$")) {
throw new IllegalArgumentException("用户名只能包含字母、数字和下划线");
}
if (!emailValidator.isValid(email)) {
throw new IllegalArgumentException("无效的邮箱地址: " + email);
}
validatePassword(password);
}
private void validatePassword(String password) {
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("密码长度至少8位");
}
// 检查密码强度
boolean hasUpperCase = !password.equals(password.toLowerCase());
boolean hasLowerCase = !password.equals(password.toUpperCase());
boolean hasDigit = password.matches(".*\\d.*");
if (!hasUpperCase || !hasLowerCase || !hasDigit) {
throw new IllegalArgumentException(
"密码必须包含大小写字母和数字"
);
}
}
private String hashPassword(String password) {
// 简化版本 - 实际应使用BCrypt
return "HASHED_" + password;
}
public void deactivateUser(String userId) {
User user = userRepository.findById(userId);
if (user != null) {
user.deactivate();
}
}
public void activateUser(String userId) {
User user = userRepository.findById(userId);
if (user != null) {
user.activate();
}
}
}
UserServiceFunctionalTest
java
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
// 功能级测试类
@DisplayName("用户认证系统功能测试")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserServiceFunctionalTest {
private UserRepository userRepository;
private UserService userService;
private User testUser;
@BeforeAll
void initializeServices() {
userRepository = new UserRepository();
userService = new UserService(userRepository);
}
@BeforeEach
void setUpTestUser() {
// 清理之前的测试数据
userRepository = new UserRepository();
userService = new UserService(userRepository);
// 创建一个测试用户
testUser = userService.register("testuser", "test@example.com", "Password123");
}
@Test
@DisplayName("TC001: 用户成功注册")
void testUserRegistrationSuccess() {
// Given
String username = "newuser";
String email = "newuser@example.com";
String password = "StrongPass123";
// When
User registeredUser = userService.register(username, email, password);
// Then
assertAll("注册用户验证",
() -> assertNotNull(registeredUser, "用户应该成功创建"),
() -> assertEquals(username, registeredUser.getUsername(), "用户名应该匹配"),
() -> assertEquals(email, registeredUser.getEmail(), "邮箱应该匹配"),
() -> assertTrue(registeredUser.isActive(), "新用户应该处于激活状态"),
() -> assertFalse(registeredUser.isLocked(), "新用户不应该被锁定"),
() -> assertEquals(0, registeredUser.getFailedLoginAttempts(), "失败登录次数应该为0"),
() -> assertNotNull(registeredUser.getCreatedAt(), "创建时间不应该为空")
);
// 验证用户已保存到仓库
User foundUser = userRepository.findByUsername(username);
assertNotNull(foundUser, "用户应该能从仓库中找到");
assertEquals(registeredUser.getId(), foundUser.getId(), "用户ID应该匹配");
}
@Test
@DisplayName("TC002: 注册时用户名重复")
void testDuplicateUsernameRegistration() {
// Given
String existingUsername = testUser.getUsername();
String newEmail = "another@example.com";
String password = "Password123";
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.register(existingUsername, newEmail, password)
);
assertEquals("用户名已存在: " + existingUsername, exception.getMessage());
}
@Test
@DisplayName("TC003: 注册时邮箱重复")
void testDuplicateEmailRegistration() {
// Given
String newUsername = "differentuser";
String existingEmail = testUser.getEmail();
String password = "Password123";
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.register(newUsername, existingEmail, password)
);
assertEquals("邮箱已被注册: " + existingEmail, exception.getMessage());
}
@ParameterizedTest(name = "无效用户名: {0}")
@ValueSource(strings = {"ab", "", " ", "verylongusernamethatiswaytoolong", "user@name"})
@DisplayName("TC004: 注册时用户名格式无效")
void testInvalidUsernameRegistration(String invalidUsername) {
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.register(invalidUsername, "test@example.com", "Password123")
);
assertNotNull(exception.getMessage());
assertTrue(
exception.getMessage().contains("用户名") ||
exception.getMessage().contains("空") ||
exception.getMessage().contains("长度") ||
exception.getMessage().contains("字符"),
"异常消息应该描述用户名问题: " + exception.getMessage()
);
}
@ParameterizedTest(name = "无效密码: {0}")
@ValueSource(strings = {"short", "12345678", "abcdefgh", "ABCDEFGH", "noDigit", "NODIGIT"})
@DisplayName("TC005: 注册时密码强度不足")
void testWeakPasswordRegistration(String weakPassword) {
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.register("newuser", "test@example.com", weakPassword)
);
assertNotNull(exception.getMessage());
assertTrue(
exception.getMessage().contains("密码") ||
exception.getMessage().contains("长度") ||
exception.getMessage().contains("字母") ||
exception.getMessage().contains("数字"),
"异常消息应该描述密码问题: " + exception.getMessage()
);
}
@Test
@DisplayName("TC006: 用户成功登录")
void testSuccessfulLogin() {
// Given
String username = testUser.getUsername();
String correctPassword = "Password123";
// 记录登录前的状态
LocalDateTime beforeLogin = LocalDateTime.now();
// When
User loggedInUser = userService.login(username, correctPassword);
// Then
assertAll("登录验证",
() -> assertNotNull(loggedInUser, "登录应该返回用户对象"),
() -> assertEquals(testUser.getId(), loggedInUser.getId(), "用户ID应该匹配"),
() -> assertEquals(0, loggedInUser.getFailedLoginAttempts(), "失败登录次数应该重置为0"),
() -> assertFalse(loggedInUser.isLocked(), "用户不应该被锁定"),
() -> assertNotNull(loggedInUser.getLastLoginAt(), "最后登录时间应该被设置"),
() -> assertTrue(
loggedInUser.getLastLoginAt().isAfter(beforeLogin) ||
loggedInUser.getLastLoginAt().equals(beforeLogin),
"最后登录时间应该在登录之后或同时"
)
);
}
@Test
@DisplayName("TC007: 登录时密码错误")
void testLoginWithWrongPassword() {
// Given
String username = testUser.getUsername();
String wrongPassword = "WrongPassword123";
// When & Then
SecurityException exception = assertThrows(
SecurityException.class,
() -> userService.login(username, wrongPassword)
);
assertEquals("用户名或密码错误", exception.getMessage());
// 验证失败登录被记录
User userAfterFailedLogin = userRepository.findByUsername(username);
assertEquals(1, userAfterFailedLogin.getFailedLoginAttempts(), "失败登录次数应该增加");
}
@Test
@DisplayName("TC008: 连续5次密码错误导致账户锁定")
void testAccountLockAfterMultipleFailedAttempts() {
// Given
String username = testUser.getUsername();
String wrongPassword = "WrongPassword123";
// When - 连续5次错误登录
for (int i = 1; i <= 5; i++) {
SecurityException exception = assertThrows(
SecurityException.class,
() -> userService.login(username, wrongPassword),
"第" + i + "次登录应该失败"
);
User user = userRepository.findByUsername(username);
if (i < 5) {
assertEquals("用户名或密码错误", exception.getMessage());
assertFalse(user.isLocked(), "前4次失败不应该锁定账户");
assertEquals(i, user.getFailedLoginAttempts(), "失败次数应该是" + i);
} else {
assertEquals("用户名或密码错误", exception.getMessage());
assertTrue(user.isLocked(), "第5次失败应该锁定账户");
assertEquals(5, user.getFailedLoginAttempts());
}
}
// Then - 第6次尝试应该显示账户锁定
SecurityException lockedException = assertThrows(
SecurityException.class,
() -> userService.login(username, "Password123")
);
assertEquals("账户已被锁定,请重置密码", lockedException.getMessage());
}
@Test
@DisplayName("TC009: 登录已禁用账户")
void testLoginDeactivatedAccount() {
// Given
userService.deactivateUser(testUser.getId());
// When & Then
SecurityException exception = assertThrows(
SecurityException.class,
() -> userService.login(testUser.getUsername(), "Password123")
);
assertEquals("账户已被禁用", exception.getMessage());
}
@Test
@DisplayName("TC010: 成功重置密码")
void testSuccessfulPasswordReset() {
// Given
String email = testUser.getEmail();
String newPassword = "NewPass123!";
// 假设账户因多次失败被锁定
for (int i = 0; i < 5; i++) {
try {
userService.login(testUser.getUsername(), "WrongPass");
} catch (SecurityException e) {
// 预期中的失败
}
}
User userBeforeReset = userRepository.findByEmail(email);
assumeTrue(userBeforeReset.isLocked(), "账户应该被锁定");
assertEquals(5, userBeforeReset.getFailedLoginAttempts());
// When
userService.resetPassword(email, newPassword);
// Then
User userAfterReset = userRepository.findByEmail(email);
assertAll("密码重置验证",
() -> assertFalse(userAfterReset.isLocked(), "重置后应该解锁账户"),
() -> assertEquals(0, userAfterReset.getFailedLoginAttempts(), "失败次数应该重置"),
() -> assertTrue(userAfterReset.verifyPassword(newPassword), "新密码应该生效")
);
// 验证可以用新密码登录
User loggedInUser = userService.login(testUser.getUsername(), newPassword);
assertNotNull(loggedInUser);
}
@Test
@DisplayName("TC011: 重置不存在的邮箱密码")
void testResetPasswordForNonExistentEmail() {
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.resetPassword("nonexistent@example.com", "NewPass123")
);
assertEquals("邮箱未注册: nonexistent@example.com", exception.getMessage());
}
@Test
@DisplayName("TC012: 成功更新邮箱")
void testUpdateEmailSuccess() {
// Given
String userId = testUser.getId();
String newEmail = "updated@example.com";
// When
userService.updateEmail(userId, newEmail);
// Then
User updatedUser = userRepository.findById(userId);
assertAll("邮箱更新验证",
() -> assertNotNull(updatedUser, "用户应该存在"),
() -> assertEquals(newEmail, updatedUser.getEmail(), "邮箱应该被更新"),
() -> assertEquals(testUser.getUsername(), updatedUser.getUsername(), "用户名应该不变")
);
// 验证可以通过新邮箱找到用户
User userByNewEmail = userRepository.findByEmail(newEmail);
assertNotNull(userByNewEmail);
assertEquals(userId, userByNewEmail.getId());
}
@Test
@DisplayName("TC013: 更新为已存在的邮箱")
void testUpdateToExistingEmail() {
// Given - 创建第二个用户
User anotherUser = userService.register("anotheruser", "another@example.com", "Password123");
String existingEmail = anotherUser.getEmail();
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.updateEmail(testUser.getId(), existingEmail)
);
assertEquals("邮箱已被使用: " + existingEmail, exception.getMessage());
}
@ParameterizedTest(name = "无效邮箱: {0}")
@CsvSource({
"invalid-email",
"user@",
"@domain.com",
"user@.com",
"user@domain.",
"user name@domain.com"
})
@DisplayName("TC014: 更新为无效邮箱格式")
void testUpdateToInvalidEmail(String invalidEmail) {
// When & Then
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.updateEmail(testUser.getId(), invalidEmail)
);
assertEquals("无效的邮箱格式: " + invalidEmail, exception.getMessage());
}
@Test
@DisplayName("TC015: 激活和禁用账户")
void testActivateAndDeactivateAccount() {
// Given
String userId = testUser.getId();
// When - 禁用账户
userService.deactivateUser(userId);
// Then
User deactivatedUser = userRepository.findById(userId);
assertFalse(deactivatedUser.isActive(), "账户应该被禁用");
// When - 尝试登录已禁用账户
SecurityException loginException = assertThrows(
SecurityException.class,
() -> userService.login(testUser.getUsername(), "Password123")
);
assertEquals("账户已被禁用", loginException.getMessage());
// When - 重新激活账户
userService.activateUser(userId);
// Then
User reactivatedUser = userRepository.findById(userId);
assertTrue(reactivatedUser.isActive(), "账户应该被重新激活");
// When - 登录应该成功
User loggedInUser = userService.login(testUser.getUsername(), "Password123");
assertNotNull(loggedInUser);
}
@Nested
@DisplayName("邮箱验证器测试")
class EmailValidatorTest {
@ParameterizedTest(name = "有效邮箱: {0}")
@ValueSource(strings = {
"user@example.com",
"first.last@domain.co.uk",
"user123@sub.domain.com",
"user+tag@example.com",
"u@example.com"
})
void testValidEmails(String validEmail) {
EmailValidator validator = new EmailValidator();
assertTrue(validator.isValid(validEmail),
validEmail + " 应该是有效的邮箱");
}
@ParameterizedTest(name = "无效邮箱: {0}")
@ValueSource(strings = {
"invalid-email",
"user@",
"@domain.com",
"user@.com",
"user@domain.",
"",
" ",
})
void testInvalidEmails(String invalidEmail) {
EmailValidator validator = new EmailValidator();
assertFalse(validator.isValid(invalidEmail),
invalidEmail + " 应该是无效的邮箱");
}
}
@Test
@DisplayName("TC016: 端到端用户旅程测试")
void testEndToEndUserJourney() {
// 1. 用户注册
User newUser = userService.register(
"journeyuser",
"journey@example.com",
"JourneyPass123"
);
assertNotNull(newUser);
assertEquals("journeyuser", newUser.getUsername());
// 2. 用户登录
User loggedInUser = userService.login("journeyuser", "JourneyPass123");
assertNotNull(loggedInUser);
assertEquals(newUser.getId(), loggedInUser.getId());
// 3. 用户故意输错密码3次
for (int i = 0; i < 3; i++) {
assertThrows(SecurityException.class,
() -> userService.login("journeyuser", "WrongPass"));
}
// 4. 验证账户未锁定
User userAfterThreeFails = userRepository.findByUsername("journeyuser");
assertEquals(3, userAfterThreeFails.getFailedLoginAttempts());
assertFalse(userAfterThreeFails.isLocked());
// 5. 用正确密码登录,重置失败计数
User successfulLogin = userService.login("journeyuser", "JourneyPass123");
assertEquals(0, successfulLogin.getFailedLoginAttempts());
// 6. 更新邮箱
userService.updateEmail(newUser.getId(), "newjourney@example.com");
User userWithNewEmail = userRepository.findById(newUser.getId());
assertEquals("newjourney@example.com", userWithNewEmail.getEmail());
// 7. 禁用账户
userService.deactivateUser(newUser.getId());
assertThrows(SecurityException.class,
() -> userService.login("journeyuser", "JourneyPass123"));
// 8. 重新激活并登录
userService.activateUser(newUser.getId());
User finalLogin = userService.login("journeyuser", "JourneyPass123");
assertNotNull(finalLogin);
System.out.println("✓ 端到端用户旅程测试完成");
}
@Test
@DisplayName("TC017: 并发用户注册测试")
void testConcurrentUserRegistrations() throws InterruptedException {
final int numberOfThreads = 10;
Thread[] threads = new Thread[numberOfThreads];
final boolean[] registrationResults = new boolean[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
final int index = i;
threads[i] = new Thread(() -> {
try {
String username = "concurrentuser" + index;
String email = "user" + index + "@test.com";
userService.register(username, email, "Password123");
registrationResults[index] = true;
} catch (Exception e) {
registrationResults[index] = false;
}
});
}
// 启动所有线程
for (Thread thread : threads) {
thread.start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
// 验证所有注册都成功
int successfulRegistrations = 0;
for (boolean result : registrationResults) {
if (result) successfulRegistrations++;
}
assertEquals(numberOfThreads, successfulRegistrations,
"所有并发注册都应该成功");
// 验证所有用户都被创建
for (int i = 0; i < numberOfThreads; i++) {
User user = userRepository.findByUsername("concurrentuser" + i);
assertNotNull(user, "用户 " + i + " 应该存在");
}
}
}
EmailValidator
java
import java.util.regex.Pattern;
// 邮箱验证器
public class EmailValidator {
private static final String EMAIL_REGEX =
"^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
private static final Pattern pattern = Pattern.compile(EMAIL_REGEX);
public boolean isValid(String email) {
if (email == null) return false;
return pattern.matcher(email).matches();
}
}
AuthSystemTestRunner 运行所有测试的主类
java
// 运行所有测试的主类
class AuthSystemTestRunner {
public static void main(String[] args) {
System.out.println("=== 用户认证系统功能测试开始 ===");
// 可以在这里添加一些手动测试
UserRepository repo = new UserRepository();
UserService service = new UserService(repo);
try {
// 手动测试1: 注册用户
User user = service.register("manualuser", "manual@test.com", "Manual123");
System.out.println("✓ 手动测试1: 用户注册成功 - " + user.getUsername());
// 手动测试2: 登录用户
User loggedIn = service.login("manualuser", "Manual123");
System.out.println("✓ 手动测试2: 用户登录成功");
// 手动测试3: 错误密码
try {
service.login("manualuser", "WrongPassword");
System.out.println("✗ 手动测试3: 应该抛出异常");
} catch (SecurityException e) {
System.out.println("✓ 手动测试3: 密码错误被正确拒绝");
}
System.out.println("\n=== 所有手动测试通过 ===");
} catch (Exception e) {
System.out.println("✗ 手动测试失败: " + e.getMessage());
e.printStackTrace();
}
}
}
