Skip to content

项目测试

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:report

6. 小结

本章完成了项目测试:

内容要点
单元测试JUnit 5、Mockito
集成测试@SpringBootTest、MockMvc
契约测试Spring Cloud Contract
性能测试JMeter、Gatling
测试覆盖率JaCoCo

下一章将学习项目部署。