Зачем тестировать
Тесты — это страховка вашего кода. Они:
JUnit 5: основы
▸Структура теста
1import org.junit.jupiter.api.*;2import static org.junit.jupiter.api.Assertions.*;34class UserServiceTest {56 private UserService userService;78 @BeforeEach9 void setUp() {10 userService = new UserService();11 }1213 @Test14 void shouldCreateUser() {15 // Arrange16 CreateUserRequest request = new CreateUserRequest("John", "john@test.com");1718 // Act19 User result = userService.create(request);2021 // Assert22 assertNotNull(result);23 assertEquals("John", result.getName());24 assertEquals("john@test.com", result.getEmail());25 }2627 @Test28 void shouldThrowExceptionWhenEmailExists() {29 // Arrange30 CreateUserRequest request = new CreateUserRequest("John", "existing@test.com");3132 // Act & Assert33 assertThrows(DuplicateEmailException.class,34 () -> userService.create(request));35 }36}
▸Аннотации JUnit 5
@Test — помечает метод как тест@BeforeEach — выполняется перед каждым тестом@AfterEach — выполняется после каждого теста@BeforeAll — выполняется один раз перед всеми тестами@AfterAll — выполняется один раз после всех тестов@Disabled — отключает тест@DisplayName — задаёт читаемое имя теста▸Параметризованные тесты
1@ParameterizedTest2@CsvSource({3 "1, 1, 2",4 "2, 3, 5",5 "0, 0, 0"6})7void shouldAddNumbers(int a, int b, int expected) {8 assertEquals(expected, calculator.add(a, b));9}1011@ParameterizedTest12@ValueSource(strings = {"racecar", "radar", "level"})13void shouldDetectPalindromes(String word) {14 assertTrue(calculator.isPalindrome(word));15}
Mockito: моки и стабы
▸Создание мока
1@ExtendWith(MockitoExtension.class)2class OrderServiceTest {34 @Mock5 private OrderRepository orderRepository;67 @Mock8 private PaymentService paymentService;910 @InjectMocks11 private OrderService orderService;1213 @Test14 void shouldCreateOrder() {15 // Arrange16 CreateOrderRequest request = new CreateOrderRequest(1L, List.of(item1, item2));17 Order expectedOrder = new Order(1L, request.getItems(), OrderStatus.CREATED);1819 when(orderRepository.save(any(Order.class))).thenReturn(expectedOrder);2021 // Act22 Order result = orderService.createOrder(request);2324 // Assert25 assertNotNull(result);26 assertEquals(OrderStatus.CREATED, result.getStatus());27 verify(orderRepository).save(any(Order.class));28 }29}
▸Основные методы Mockito
when(...).thenReturn(...) — возвращает значениеwhen(...).thenThrow(...) — выбрасывает исключениеdoReturn(...).when(...) — альтернативный синтаксисverify(...) — проверяет вызов методаverifyNoMoreInteractions(...) — проверяет что больше ничего не вызывалосьtimes(n) — проверяет количество вызовов▸Capturing аргументов
1ArgumentCaptor<Order> orderCaptor = ArgumentCaptor.forClass(Order.class);23orderService.createOrder(request);45verify(orderRepository).save(orderCaptor.capture());6Order savedOrder = orderCaptor.getValue();7assertEquals(OrderStatus.CREATED, savedOrder.getStatus());
Интеграционные тесты
▸Spring Boot Test
1@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)2@AutoConfigureTestDatabase3class UserRepositoryIntegrationTest {45 @Autowired6 private TestEntityManager entityManager;78 @Autowired9 private UserRepository userRepository;1011 @Test12 void shouldFindUserByEmail() {13 // Arrange14 User user = new User("John", "john@test.com");15 entityManager.persistAndFlush(user);1617 // Act18 Optional<User> found = userRepository.findByEmail("john@test.com");1920 // Assert21 assertTrue(found.isPresent());22 assertEquals("John", found.get().getName());23 }24}
▸Testcontainers
1@SpringBootTest2@Testcontainers3class UserRepositoryTest {45 @Container6 static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")7 .withDatabaseName("testdb")8 .withUsername("test")9 .withPassword("test");1011 @DynamicPropertySource12 static void configureProperties(DynamicPropertyRegistry registry) {13 registry.add("spring.datasource.url", postgres::getJdbcUrl);14 registry.add("spring.datasource.username", postgres::getUsername);15 registry.add("spring.datasource.password", postgres::getPassword);16 }17}
Best Practices
▸Тестовая пирамида
▸AAA Pattern
▸Именование тестов
1@DisplayName("UserService")2class UserServiceTest {34 @Test5 @DisplayName("should return user when valid ID provided")6 void shouldReturnUserWhenValidIdProvided() { }78 @Test9 @DisplayName("should throw exception when user not found")10 void shouldThrowExceptionWhenUserNotFound() { }11}
Заключение
Тестирование — обязательная часть профессиональной разработки. JUnit 5 и Mockito — стандарт де-факто в Java-экосистеме. На собеседовании важно уметь объяснять стратегию тестирования, различия между unit и интеграционными тестами, и как писать поддерживаемые тесты.