项目测试
1. 单元测试
1.1 Service 测试
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
@Test
void createUser_Success() {
UserRequest request = new UserRequest(
"testuser",
"password123",
"test@example.com",
"13800138000"
);
User savedUser = new User();
savedUser.setId(1L);
savedUser.setUsername("testuser");
when(userRepository.existsByUsername("testuser")).thenReturn(false);
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
User result = userService.create(request);
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getUsername()).isEqualTo("testuser");
verify(userRepository).save(any(User.class));
}
@Test
void createUser_DuplicateUsername_ThrowsException() {
UserRequest request = new UserRequest(
"existinguser",
"password123",
"test@example.com",
null
);
when(userRepository.existsByUsername("existinguser")).thenReturn(true);
assertThatThrownBy(() -> userService.create(request))
.isInstanceOf(BusinessException.class)
.hasMessage("用户名已存在");
}
@Test
void findById_Success() {
User user = new User();
user.setId(1L);
user.setUsername("testuser");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
User result = userService.findById(1L);
assertThat(result.getUsername()).isEqualTo("testuser");
}
@Test
void findById_NotFound_ThrowsException() {
when(userRepository.findById(999L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> userService.findById(999L))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessage("用户不存在");
}
}1.2 Repository 测试
java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void findByUsername_Success() {
User user = new User();
user.setUsername("testuser");
user.setPassword("password");
user.setEmail("test@example.com");
entityManager.persistAndFlush(user);
Optional<User> found = userRepository.findByUsername("testuser");
assertThat(found).isPresent();
assertThat(found.get().getEmail()).isEqualTo("test@example.com");
}
@Test
void findByUsername_NotFound() {
Optional<User> found = userRepository.findByUsername("nonexistent");
assertThat(found).isEmpty();
}
@Test
void existsByUsername_True() {
User user = new User();
user.setUsername("existinguser");
user.setPassword("password");
user.setEmail("existing@example.com");
entityManager.persistAndFlush(user);
boolean exists = userRepository.existsByUsername("existinguser");
assertThat(exists).isTrue();
}
}2. 集成测试
2.1 Controller 测试
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void createUser_Success() throws Exception {
UserRequest request = new UserRequest(
"testuser",
"Password123",
"test@example.com",
null
);
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("test@example.com");
when(userService.create(any(UserRequest.class))).thenReturn(user);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.username").value("testuser"));
}
@Test
void getUser_Success() throws Exception {
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("test@example.com");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(1))
.andExpect(jsonPath("$.data.username").value("testuser"));
}
@Test
void getUser_NotFound() throws Exception {
when(userService.findById(999L))
.thenThrow(new ResourceNotFoundException("用户不存在"));
mockMvc.perform(get("/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code").value(404));
}
@Test
void createUser_ValidationFail() throws Exception {
UserRequest request = new UserRequest("", "", "invalid-email", null);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
}2.2 服务集成测试
java
@SpringBootTest
@Transactional
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private UserRepository userRepository;
private User testUser;
private Product testProduct;
@BeforeEach
void setUp() {
orderRepository.deleteAll();
productRepository.deleteAll();
userRepository.deleteAll();
testUser = new User();
testUser.setUsername("testuser");
testUser.setPassword("password");
testUser.setEmail("test@example.com");
testUser = userRepository.save(testUser);
testProduct = new Product();
testProduct.setName("Test Product");
testProduct.setPrice(new BigDecimal("99.99"));
testProduct.setStock(100);
testProduct = productRepository.save(testProduct);
}
@Test
void createOrder_Success() {
OrderItemRequest item = new OrderItemRequest(
testProduct.getId(),
2
);
OrderRequest request = new OrderRequest(
testUser.getId(),
List.of(item),
"CREDIT_CARD"
);
Order order = orderService.create(request);
assertThat(order.getId()).isNotNull();
assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING);
assertThat(order.getItems()).hasSize(1);
}
}3. 契约测试
3.1 添加依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>3.2 契约定义
groovy
// contracts/user-service/shouldReturnUser.groovy
Contract.make {
description "Should return user by id"
request {
method GET()
url "/users/1"
}
response {
status OK()
body([
code: 200,
message: "success",
data: [
id: 1,
username: "testuser",
email: "test@example.com"
]
])
}
}3.3 基类
java
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class BaseTestClass {
@Autowired
private UserController userController;
@Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(userController);
}
}4. 性能测试
4.1 JMeter 测试
xml
<!-- JMeter 测试计划 -->
<ThreadGroup>
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<stringProp name="ThreadGroup.duration">60</stringProp>
<HTTPSamplerProxy>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.path">/api/users</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
</ThreadGroup>4.2 Gatling 测试
scala
class UserSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://localhost:8080")
.acceptHeader("application/json")
val scn = scenario("Get Users")
.exec(http("get_users")
.get("/api/users")
.check(status.is(200)))
setUp(
scn.inject(
constantUsersPerSec(10) during (60 seconds)
)
).protocols(httpProtocol)
}5. 测试覆盖率
5.1 JaCoCo 配置
xml
<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>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>5.2 运行测试
bash
mvn clean test jacoco:report6. 小结
本章完成了项目测试:
| 内容 | 要点 |
|---|---|
| 单元测试 | JUnit 5、Mockito |
| 集成测试 | @SpringBootTest、MockMvc |
| 契约测试 | Spring Cloud Contract |
| 性能测试 | JMeter、Gatling |
| 测试覆盖率 | JaCoCo |
下一章将学习项目部署。