本文目录导读:

我来为您提供一个完整的Java自动化测试案例,涵盖单元测试、集成测试和UI自动化测试。
项目结构
src/
├── main/
│ └── java/
│ └── com/example/
│ ├── model/
│ │ └── User.java
│ ├── service/
│ │ ├── UserService.java
│ │ └── impl/
│ │ └── UserServiceImpl.java
│ └── controller/
│ └── UserController.java
└── test/
└── java/
└── com/example/
├── service/
│ └── UserServiceTest.java
├── controller/
│ └── UserControllerTest.java
└── ui/
└── UserLoginUITest.java
依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>automation-testing</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.version>5.10.0</junit.version>
<mockito.version>5.7.0</mockito.version>
<selenium.version>4.15.0</selenium.version>
<rest-assured.version>5.4.0</rest-assured.version>
<spring-boot.version>2.7.17</spring-boot.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
</dependency>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
<!-- H2 Database for testing -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
业务代码
User.java
package com.example.model;
import java.time.LocalDateTime;
import java.util.Objects;
public class User {
private Long id;
private String username;
private String email;
private String password;
private Integer age;
private LocalDateTime createdAt;
public User() {}
public User(String username, String email, String password, Integer age) {
this.username = username;
this.email = email;
this.password = password;
this.age = age;
this.createdAt = LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, username, email);
}
}
UserService.java
package com.example.service;
import com.example.model.User;
import java.util.List;
import java.util.Optional;
public interface UserService {
User createUser(User user);
Optional<User> getUserById(Long id);
List<User> getAllUsers();
User updateUser(Long id, User user);
void deleteUser(Long id);
boolean existsByEmail(String email);
}
UserServiceImpl.java
package com.example.service.impl;
import com.example.model.User;
import com.example.service.UserService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class UserServiceImpl implements UserService {
private final ConcurrentHashMap<Long, User> userStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@Override
public User createUser(User user) {
// 业务逻辑验证
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
throw new IllegalArgumentException("邮箱格式不正确");
}
if (user.getPassword() == null || user.getPassword().length() < 6) {
throw new IllegalArgumentException("密码长度不能少于6位");
}
// 检查邮箱是否已存在
if (existsByEmail(user.getEmail())) {
throw new IllegalArgumentException("邮箱已被注册");
}
// 生成ID并存储
user.setId(idGenerator.getAndIncrement());
userStore.put(user.getId(), user);
return user;
}
@Override
public Optional<User> getUserById(Long id) {
return Optional.ofNullable(userStore.get(id));
}
@Override
public List<User> getAllUsers() {
return new ArrayList<>(userStore.values());
}
@Override
public User updateUser(Long id, User user) {
User existingUser = userStore.get(id);
if (existingUser == null) {
throw new IllegalArgumentException("用户不存在");
}
user.setId(id);
userStore.put(id, user);
return user;
}
@Override
public void deleteUser(Long id) {
userStore.remove(id);
}
@Override
public boolean existsByEmail(String email) {
return userStore.values().stream()
.anyMatch(user -> user.getEmail().equals(email));
}
}
单元测试用例
UserServiceTest.java
package com.example.service;
import com.example.model.User;
import com.example.service.impl.UserServiceImpl;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("用户服务单元测试")
class UserServiceTest {
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserServiceImpl();
}
@AfterEach
void tearDown() {
userService = null;
}
@Nested
@DisplayName("创建用户测试")
class CreateUserTest {
@Test
@DisplayName("创建有效用户")
void createValidUser() {
// Arrange
User user = new User("john_doe", "john@example.com", "password123", 25);
// Act
User createdUser = userService.createUser(user);
// Assert
assertAll("验证用户创建成功",
() -> assertNotNull(createdUser.getId(), "用户ID不应为空"),
() -> assertEquals("john_doe", createdUser.getUsername(), "用户名不匹配"),
() -> assertEquals("john@example.com", createdUser.getEmail(), "邮箱不匹配"),
() -> assertNotNull(createdUser.getCreatedAt(), "创建时间不应为空")
);
}
@Test
@DisplayName("创建用户时用户名为空")
void createUserWithEmptyUsername() {
// Arrange
User user = new User("", "john@example.com", "password123", 25);
// Act & Assert
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.createUser(user),
"应该抛出异常"
);
assertEquals("用户名不能为空", exception.getMessage());
}
@Test
@DisplayName("创建用户时邮箱格式不正确")
void createUserWithInvalidEmail() {
// Arrange
User user = new User("john", "invalid-email", "password123", 25);
// Act & Assert
assertThatThrownBy(() -> userService.createUser(user))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("邮箱格式不正确");
}
@Test
@DisplayName("创建用户时密码太短")
void createUserWithShortPassword() {
// Arrange
User user = new User("john", "john@example.com", "123", 25);
// Act & Assert
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.createUser(user)
);
assertEquals("密码长度不能少于6位", exception.getMessage());
}
@Test
@DisplayName("创建重复邮箱用户")
void createDuplicateEmail() {
// Arrange
User user1 = new User("john", "john@example.com", "password123", 25);
User user2 = new User("jane", "john@example.com", "password456", 30);
userService.createUser(user1);
// Act & Assert
assertThatThrownBy(() -> userService.createUser(user2))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("邮箱已被注册");
}
}
@Nested
@DisplayName("查询用户测试")
class GetUserTest {
@Test
@DisplayName("按ID查询存在的用户")
void getUserById() {
// Arrange
User user = new User("john", "john@example.com", "password123", 25);
User created = userService.createUser(user);
// Act
User found = userService.getUserById(created.getId()).orElse(null);
// Assert
assertNotNull(found);
assertEquals(created.getId(), found.getId());
assertEquals("john", found.getUsername());
}
@Test
@DisplayName("查询不存在的用户")
void getUserByIdNotFound() {
// Act
var result = userService.getUserById(999L);
// Assert
assertTrue(result.isEmpty(), "应该返回空Optional");
}
@Test
@DisplayName("获取所有用户")
void getAllUsers() {
// Arrange
User user1 = new User("john", "john@example.com", "password123", 25);
User user2 = new User("jane", "jane@example.com", "password456", 30);
userService.createUser(user1);
userService.createUser(user2);
// Act
var users = userService.getAllUsers();
// Assert
assertEquals(2, users.size(), "应该有2个用户");
assertThat(users)
.extracting(User::getUsername)
.containsExactlyInAnyOrder("john", "jane");
}
}
@Nested
@DisplayName("更新和删除用户测试")
class UpdateAndDeleteUserTest {
@Test
@DisplayName("更新存在的用户")
void updateUser() {
// Arrange
User user = new User("john", "john@example.com", "password123", 25);
User created = userService.createUser(user);
User updatedData = new User("john_updated", "john_updated@example.com", "newpassword123", 30);
// Act
User updated = userService.updateUser(created.getId(), updatedData);
// Assert
assertAll(
() -> assertEquals(created.getId(), updated.getId(), "ID应该保持不变"),
() -> assertEquals("john_updated", updated.getUsername(), "用户名应该已更新"),
() -> assertEquals("john_updated@example.com", updated.getEmail(), "邮箱应该已更新")
);
}
@Test
@DisplayName("更新不存在的用户")
void updateNonExistentUser() {
// Arrange
User user = new User("john", "john@example.com", "password123", 25);
// Act & Assert
assertThatThrownBy(() -> userService.updateUser(999L, user))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("用户不存在");
}
@Test
@DisplayName("删除用户")
void deleteUser() {
// Arrange
User user = new User("john", "john@example.com", "password123", 25);
User created = userService.createUser(user);
// Act
userService.deleteUser(created.getId());
// Assert
assertTrue(userService.getUserById(created.getId()).isEmpty(), "删除后用户应该不存在");
}
}
}
API 自动化测试
UserControllerTest.java
package com.example.controller;
import com.example.model.User;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("用户控制器API测试")
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserService userService;
@Test
@DisplayName("POST /api/users - 创建用户")
void createUser() throws Exception {
// Arrange
User user = new User("testuser", "test@example.com", "password123", 25);
// Act & Assert
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username", is("testuser")))
.andExpect(jsonPath("$.email", is("test@example.com")))
.andExpect(jsonPath("$.id", notNullValue()));
}
@Test
@DisplayName("GET /api/users/{id} - 获取用户")
void getUserById() throws Exception {
// Arrange
User user = new User("testuser", "test@example.com", "password123", 25);
User created = userService.createUser(user);
// Act & Assert
mockMvc.perform(get("/api/users/{id}", created.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username", is("testuser")))
.andExpect(jsonPath("$.email", is("test@example.com")));
}
@Test
@DisplayName("GET /api/users/{id} - 用户不存在")
void getUserByIdNotFound() throws Exception {
mockMvc.perform(get("/api/users/{id}", 999L))
.andExpect(status().isNotFound());
}
@Test
@DisplayName("DELETE /api/users/{id} - 删除用户")
void deleteUser() throws Exception {
// Arrange
User user = new User("testuser", "test@example.com", "password123", 25);
User created = userService.createUser(user);
// Act & Assert
mockMvc.perform(delete("/api/users/{id}", created.getId()))
.andExpect(status().isNoContent());
}
}
UI 自动化测试(Selenium)
UserLoginUITest.java
package com.example.ui;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.List;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("用户登录界面自动化测试")
class UserLoginUITest {
private WebDriver driver;
private WebDriverWait wait;
@BeforeAll
void setUpAll() {
// 使用WebDriverManager自动管理驱动
WebDriverManager.chromedriver().setup();
}
@BeforeEach
void setUp() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // 无头模式
options.addArguments("--disable-gpu");
options.addArguments("--no-sandbox");
driver = new ChromeDriver(options);
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 设置浏览器大小
driver.manage().window().maximize();
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
@Test
@DisplayName("用户登录成功")
void testSuccessfulLogin() {
// 导航到登录页面
driver.get("http://localhost:8080/login");
// 填写登录表单
WebElement usernameInput = wait.until(
ExpectedConditions.presenceOfElementLocated(By.id("username"))
);
usernameInput.sendKeys("testuser");
WebElement passwordInput = driver.findElement(By.id("password"));
passwordInput.sendKeys("password123");
// 提交表单
WebElement submitButton = driver.findElement(By.id("login-button"));
submitButton.click();
// 等待跳转并验证成功消息
WebElement successMessage = wait.until(
ExpectedConditions.presenceOfElementLocated(By.className("success-message"))
);
Assertions.assertTrue(
successMessage.getText().contains("登录成功"),
"应该显示登录成功消息"
);
}
@Test
@DisplayName("用户登录失败-密码错误")
void testLoginFailureWithWrongPassword() {
// 导航到登录页面
driver.get("http://localhost:8080/login");
// 填写表单
WebElement usernameInput = driver.findElement(By.id("username"));
usernameInput.sendKeys("testuser");
WebElement passwordInput = driver.findElement(By.id("password"));
passwordInput.sendKeys("wrongpassword");
// 提交
driver.findElement(By.id("login-button")).click();
// 验证错误消息
WebElement errorMessage = wait.until(
ExpectedConditions.presenceOfElementLocated(By.className("error-message"))
);
Assertions.assertTrue(
errorMessage.isDisplayed(),
"应该显示错误消息"
);
List<WebElement> errorMessages = driver.findElements(By.className("error-message"));
Assertions.assertEquals(1, errorMessages.size(), "应该只有一个错误消息");
}
@Test
@DisplayName("登录页面表单验证")
void testLoginFormValidation() {
driver.get("http://localhost:8080/login");
// 不填写用户名和密码,直接点击登录
driver.findElement(By.id("login-button")).click();
// 等待并在表单验证错误
List<WebElement> validationErrors = wait.until(
ExpectedConditions.presenceOfAllElementsLocatedBy(
By.cssSelector(".validation-error")
)
);
Assertions.assertTrue(
validationErrors.size() >= 2,
"应该至少有两个验证错误(用户名和密码)"
);
// 验证具体错误消息
WebElement usernameError = driver.findElement(By.id("username-error"));
Assertions.assertEquals(
"请输入用户名",
usernameError.getText(),
"应该显示用户名验证错误"
);
WebElement passwordError = driver.findElement(By.id("password-error"));
Assertions.assertEquals(
"请输入密码",
passwordError.getText(),
"应该显示密码验证错误"
);
}
@Test
@DisplayName("登录页面UI元素验证")
void testLoginPageElements() {
driver.get("http://localhost:8080/login");
// 验证页面标题
Assertions.assertEquals(
"系统登录",
driver.getTitle(),
"页面标题应该正确"
);
// 验证表单元素
WebElement form = driver.findElement(By.id("login-form"));
Assertions.assertTrue(form.isDisplayed(), "登录表单应该可见");
// 验证表单字段
WebElement usernameInput = driver.findElement(By.id("username"));
Assertions.assertTrue(usernameInput.isEnabled(), "用户名输入框应该可用");
Assertions.assertEquals("text", usernameInput.getAttribute("type"));
Assertions.assertTrue(usernameInput.getAttribute("required") != null);
WebElement passwordInput = driver.findElement(By.id("password"));
Assertions.assertEquals("password", passwordInput.getAttribute("type"));
Assertions.assertTrue(passwordInput.getAttribute("required") != null);
// 验证登录按钮样式
WebElement loginButton = driver.findElement(By.id("login-button"));
Assertions.assertEquals("submit", loginButton.getAttribute("type"));
Assertions.assertTrue(loginButton.getAttribute("class").contains("primary"));
}
@Test
@DisplayName("快速输入密码显示/隐藏测试")
void testPasswordVisibilityToggle() {
driver.get("http://localhost:8080/login");
// 输入密码
WebElement passwordInput = driver.findElement(By.id("password"));
passwordInput.sendKeys("secretpassword");
// 验证密码字段是密码类型
Assertions.assertEquals(
"password",
passwordInput.getAttribute("type"),
"初始应该是密码类型"
);
// 点击显示/隐藏按钮
WebElement toggleButton = driver.findElement(By.id("toggle-password"));
toggleButton.click();
// 验证密码字段变为文本类型
String typeAfterToggle = passwordInput.getAttribute("type");
Assertions.assertEquals(
"text",
typeAfterToggle,
"点击后应该是文本类型"
);
// 验证值保持不变
Assertions.assertEquals(
"secretpassword",
passwordInput.getAttribute("value"),
"密码值应该保持不变"
);
}
}
运行测试
Maven 命令
# 运行所有测试 mvn test # 运行指定测试类 mvn test -Dtest=UserServiceTest # 运行指定测试方法 mvn test -Dtest=UserServiceTest#createValidUser # 运行特定包的测试 mvn test -Dtest="com.example.service.*" # 跳过测试 mvn clean install -DskipTests # 生成测试报告 mvn surefire-report:report
测试配置 (test/resources/application.yml)
spring:
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
server:
port: 0 # 随机端口
logging:
level:
org.springframework: INFO
com.example: DEBUG
测试报告示例
JUnit 测试报告
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.service.UserServiceTest
[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123s
[INFO] Running com.example.controller.UserControllerTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.456s
[INFO] Running com.example.ui.UserLoginUITest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.234s
[INFO] -------------------------------------------------------
[INFO] Results:
[INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0
[INFO] -------------------------------------------------------
[INFO] BUILD SUCCESS
这个完整的Java自动化测试案例涵盖了:
- 单元测试:使用JUnit 5和AssertJ验证业务逻辑
- 集成测试:使用MockMvc测试REST API
- UI测试:使用Selenium WebDriver测试用户界面
- 测试报告:Allure或Surefire生成详细测试报告
您可以根据实际需求调整测试场景和用例,需要更多特定功能的示例吗?