测试是保证代码质量的重要手段,本文将详细介绍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性能测试实战