Testing Patterns trong Java
1. Lý thuyết và Khái niệm Cơ bản
1.1 Testing Pyramid
- Unit Testing: Kiểm thử đơn vị nhỏ nhất
- Integration Testing: Kiểm thử tích hợp giữa các component
- End-to-End Testing: Kiểm thử toàn bộ luồng
- Performance Testing: Kiểm thử hiệu năng
- Security Testing: Kiểm thử bảo mật
1.2 Testing Types
- Functional Testing
- Non-functional Testing
- Regression Testing
- Smoke Testing
- Acceptance Testing
2. Best Practices và Design Patterns
2.1 Unit Testing Pattern
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void whenCreateUser_thenReturnUser() {
// Arrange
User user = new User("test", "test@email.com");
when(userRepository.save(any(User.class))).thenReturn(user);
// Act
User result = userService.createUser(user);
// Assert
assertNotNull(result);
assertEquals("test", result.getUsername());
verify(userRepository).save(any(User.class));
}
}
2.2 Integration Testing Pattern
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void whenCreateUser_thenStatus201() throws Exception {
UserDTO userDTO = new UserDTO("test", "test@email.com");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userDTO)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("test"));
}
}
3. Anti-patterns và Common Pitfalls
3.1 Test Anti-patterns
- Testing Implementation Details
- Brittle Tests
- Test Duplication
- Complex Test Setup
- Ignored Tests
- Non-isolated Tests
3.2 Common Mistakes
// Bad Practice: Testing Implementation Details
@Test
void badTest() {
userService.createUser(user);
verify(userService).validateUser(user); // Testing internal implementation
}
// Good Practice: Testing Behavior
@Test
void goodTest() {
User result = userService.createUser(user);
assertNotNull(result);
assertEquals(user.getUsername(), result.getUsername());
}
4. Ví dụ Code Thực tế
4.1 Service Layer Testing
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentService paymentService;
@InjectMocks
private OrderService orderService;
@Test
void whenPlaceOrder_thenSuccess() {
// Arrange
Order order = new Order();
order.setItems(Arrays.asList(
new OrderItem("Item1", 100),
new OrderItem("Item2", 200)
));
when(paymentService.process(any())).thenReturn(true);
when(orderRepository.save(any())).thenReturn(order);
// Act
OrderResult result = orderService.placeOrder(order);
// Assert
assertTrue(result.isSuccess());
assertEquals(300, result.getTotalAmount());
verify(paymentService).process(any());
verify(orderRepository).save(any());
}
}
4.2 Repository Layer Testing
@DataJpaTest
public class OrderRepositoryTest {
@Autowired
private OrderRepository orderRepository;
@Test
void whenFindByStatus_thenReturnOrders() {
// Arrange
Order order1 = new Order(OrderStatus.PENDING);
Order order2 = new Order(OrderStatus.COMPLETED);
orderRepository.saveAll(Arrays.asList(order1, order2));
// Act
List<Order> pendingOrders = orderRepository.findByStatus(OrderStatus.PENDING);
// Assert
assertEquals(1, pendingOrders.size());
assertEquals(OrderStatus.PENDING, pendingOrders.get(0).getStatus());
}
}
5. Use Cases và Scenarios
5.1 Business Logic Testing
@Test
void testOrderDiscountCalculation() {
// Scenario 1: Order amount > 1000
Order largeOrder = new Order(1500.0);
assertEquals(150.0, orderService.calculateDiscount(largeOrder));
// Scenario 2: Order amount < 1000
Order smallOrder = new Order(500.0);
assertEquals(0.0, orderService.calculateDiscount(smallOrder));
// Scenario 3: VIP Customer
Order vipOrder = new Order(800.0);
vipOrder.setCustomerType(CustomerType.VIP);
assertEquals(80.0, orderService.calculateDiscount(vipOrder));
}
5.2 Edge Cases Testing
@Test
void testOrderValidation() {
// Empty order
assertThrows(ValidationException.class,
() -> orderService.validateOrder(new Order()));
// Negative amount
Order negativeOrder = new Order();
negativeOrder.setAmount(-100.0);
assertThrows(ValidationException.class,
() -> orderService.validateOrder(negativeOrder));
// Maximum order amount
Order maxOrder = new Order();
maxOrder.setAmount(Double.MAX_VALUE);
assertThrows(ValidationException.class,
() -> orderService.validateOrder(maxOrder));
}
@Test
void performanceTest() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// Execute operation multiple times
IntStream.range(0, 1000).forEach(i -> {
orderService.processOrder(new Order());
});
stopWatch.stop();
assertTrue(stopWatch.getTotalTimeMillis() < 5000);
}
6.2 Load Testing
@Test
void loadTest() throws InterruptedException {
int threadCount = 10;
int requestsPerThread = 100;
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
AtomicInteger successCount = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < requestsPerThread; j++) {
orderService.processOrder(new Order());
successCount.incrementAndGet();
}
} finally {
latch.countDown();
}
});
}
latch.await(1, TimeUnit.MINUTES);
assertEquals(threadCount * requestsPerThread, successCount.get());
}
7. Security Considerations
7.1 Security Testing
@Test
void securityTest() {
// Test authentication
assertThrows(AuthenticationException.class,
() -> userService.getUserDetails(null));
// Test authorization
User user = new User("user", Role.USER);
assertThrows(AccessDeniedException.class,
() -> adminService.deleteUser(user));
// Test input validation
assertThrows(ValidationException.class,
() -> userService.createUser(new User("<script>alert('xss')</script>")));
}
8. Testing Strategies
8.1 Test Driven Development (TDD)
// Step 1: Write failing test
@Test
void calculateOrderTotal() {
Order order = new Order(Arrays.asList(
new OrderItem("Item1", 100),
new OrderItem("Item2", 200)
));
assertEquals(300, orderService.calculateTotal(order));
}
// Step 2: Implement the feature
public class OrderService {
public double calculateTotal(Order order) {
return order.getItems().stream()
.mapToDouble(OrderItem::getPrice)
.sum();
}
}
// Step 3: Refactor if needed
8.2 Behavior Driven Development (BDD)
@ExtendWith(MockitoExtension.class)
public class OrderProcessingTest {
@Test
void givenValidOrder_whenProcess_thenSuccess() {
// Given
Order order = createValidOrder();
// When
OrderResult result = orderService.process(order);
// Then
assertTrue(result.isSuccess());
assertEquals(OrderStatus.COMPLETED, result.getStatus());
}
}
9. Monitoring và Troubleshooting
9.1 Test Logging
@Test
void testWithLogging() {
Logger logger = LoggerFactory.getLogger(OrderServiceTest.class);
try {
// Test execution
Order result = orderService.processOrder(new Order());
logger.info("Test completed successfully: {}", result);
} catch (Exception e) {
logger.error("Test failed: ", e);
fail("Test failed with exception");
}
}
9.2 Test Reports
@ExtendWith(TestReporter.class)
public class TestWithReporting {
@Test
void reportingTest(TestReporter testReporter) {
// Capture test metrics
Map<String, String> metrics = new HashMap<>();
metrics.put("executionTime", "100ms");
metrics.put("memory", "50MB");
testReporter.publishEntry(metrics);
}
}
10. References và Further Reading
10.1 Testing Resources
- JUnit 5 User Guide
- Mockito Documentation
- TestContainers Documentation
- Spring Testing Reference
- Clean Code by Robert C. Martin
- Test Driven Development by Kent Beck
10.2 Best Practices Resources
- Testing Java Microservices
- Practical Unit Testing
- Continuous Testing in DevOps
- Performance Testing Guidelines
- Security Testing Best Practices