Java测试实战:单元测试到集成测试的完整指南

测试是保证代码质量的重要手段,本文将详细介绍Java测试的各个方面,包括单元测试、集成测试、性能测试等。

单元测试

JUnit 5基础

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@DisplayName("用户服务测试")
public class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }
    
    @Test
    @DisplayName("测试创建用户")
    void testCreateUser() {
        // 准备测试数据
        User user = new User("test", "test@example.com");
        when(userRepository.save(any(User.class))).thenReturn(user);
        
        // 执行测试
        User result = userService.createUser(user);
        
        // 验证结果
        assertNotNull(result);
        assertEquals("test", result.getUsername());
        verify(userRepository).save(any(User.class));
    }
}

Mockito使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
    
    @Mock
    private PaymentService paymentService;
    
    @Mock
    private InventoryService inventoryService;
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    void testCreateOrder() {
        // 设置mock行为
        when(inventoryService.checkStock(anyString())).thenReturn(true);
        when(paymentService.processPayment(anyDouble())).thenReturn(true);
        
        // 执行测试
        Order order = new Order("product1", 100.0);
        boolean result = orderService.createOrder(order);
        
        // 验证结果
        assertTrue(result);
        verify(inventoryService).checkStock("product1");
        verify(paymentService).processPayment(100.0);
    }
}

集成测试

Spring Boot Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    void testCreateUser() throws Exception {
        User user = new User("test", "test@example.com");
        
        mockMvc.perform(post("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.username").value("test"))
            .andExpect(jsonPath("$.email").value("test@example.com"));
    }
}

TestContainers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Testcontainers
@SpringBootTest
public class UserRepositoryTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");
    
    @DynamicPropertySource
    static void registerPgProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testSaveUser() {
        User user = new User("test", "test@example.com");
        User savedUser = userRepository.save(user);
        
        assertNotNull(savedUser.getId());
        assertEquals("test", savedUser.getUsername());
    }
}

性能测试

JMH基准测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class StringConcatenationBenchmark {
    
    @Benchmark
    public String testStringConcat() {
        String result = "";
        for (int i = 0; i < 100; i++) {
            result += i;
        }
        return result;
    }
    
    @Benchmark
    public String testStringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            sb.append(i);
        }
        return sb.toString();
    }
    
    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
            .include(StringConcatenationBenchmark.class.getSimpleName())
            .forks(1)
            .build();
        new Runner(opt).run();
    }
}

测试覆盖率

JaCoCo配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.7</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>

测试数据准备

测试数据构建器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class UserBuilder {
    private String username = "defaultUser";
    private String email = "default@example.com";
    private String password = "password123";
    
    public static UserBuilder aUser() {
        return new UserBuilder();
    }
    
    public UserBuilder withUsername(String username) {
        this.username = username;
        return this;
    }
    
    public UserBuilder withEmail(String email) {
        this.email = email;
        return this;
    }
    
    public User build() {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(password);
        return user;
    }
}

// 使用示例
@Test
void testUserValidation() {
    User user = UserBuilder.aUser()
        .withUsername("testUser")
        .withEmail("test@example.com")
        .build();
        
    assertTrue(userValidator.validate(user));
}

测试最佳实践

1. 测试命名规范

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class OrderServiceTest {
    @Test
    void shouldCreateOrderSuccessfully_whenValidOrderData() {
        // 测试代码
    }
    
    @Test
    void shouldThrowException_whenInvalidOrderAmount() {
        // 测试代码
    }
}

2. 测试隔离

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class DatabaseTest {
    
    @BeforeEach
    void setUp() {
        // 清理测试数据
        databaseCleaner.clean();
    }
    
    @Test
    void testDataOperation() {
        // 测试代码
    }
}

3. 异常测试

1
2
3
4
5
6
7
8
@Test
void shouldThrowException_whenUserNotFound() {
    String username = "nonexistent";
    
    assertThrows(UserNotFoundException.class, () -> {
        userService.getUser(username);
    });
}

测试自动化

持续集成配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# .github/workflows/test.yml
name: Java CI with Maven

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        
    - name: Run tests
      run: mvn test
      
    - name: Generate coverage report
      run: mvn jacoco:report

总结

完善的测试策略对于保证代码质量至关重要,通过合理运用各种测试工具和框架,可以有效提高代码的可靠性和可维护性。

参考资料

  • JUnit 5用户指南
  • Mockito参考文档
  • Spring Boot测试指南
  • Java性能测试实战
使用绝夜之城强力驱动