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));
}

6. Performance Considerations

6.1 Performance Testing Setup

@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