middle
Как организовать тестирование в современном Java-проекте?
Стратегия тестирования в 2026 году строится вокруг пирамиды: unit-тесты (много, быстрые), slice-тесты и интеграционные (средне), E2E и contract-тесты (мало), плюс автоматические архитектурные проверки через ArchUnit.
Пирамида тестирования
Пример
/ E2E \ <- Мало (Playwright, API tests)
/ Интегр. \ <- Средне (Testcontainers, @SpringBootTest)
/ Slice-тесты \ <- Средне (@WebMvcTest, @DataJpaTest)
/ Unit-тесты \ <- Много (JUnit 5 + Mockito)
/ Архитектурные \ <- Автоматически (ArchUnit)
Unit-тест бизнес-логики
OrderServiceTest
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository orderRepository;
@Mock PaymentGateway paymentGateway;
@Mock OrderEventPublisher eventPublisher;
@InjectMocks OrderService orderService;
@Test
void shouldCreateOrderWithCorrectTotal() {
CreateOrderCommand command = new CreateOrderCommand(
UUID.randomUUID(),
List.of(
new OrderItemCommand(UUID.randomUUID(), 2, new Money("29.99", "USD")),
new OrderItemCommand(UUID.randomUUID(), 1, new Money("49.99", "USD"))
));
when(orderRepository.save(any(Order.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
Order result = orderService.createOrder(command);
assertThat(result.getStatus()).isEqualTo(OrderStatus.CREATED);
assertThat(result.getTotalAmount()).isEqualByComparingTo(new BigDecimal("109.97"));
verify(eventPublisher).publishOrderCreated(result);
verify(paymentGateway, never()).charge(any(), any());
}
@ParameterizedTest
@CsvSource({
"CREATED, CONFIRMED, true",
"CONFIRMED, SHIPPED, true",
"DELIVERED, CANCELLED, false"
})
void shouldValidateStatusTransition(OrderStatus from, OrderStatus to, boolean allowed) {
assertThat(from.canTransitionTo(to)).isEqualTo(allowed);
}
}
Testcontainers для интеграционных тестов
Testcontainers запускает реальные зависимости (PostgreSQL, Kafka, Redis) в Docker-контейнерах:
IntegrationTestBase
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
abstract class IntegrationTestBase {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.7.0"));
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
registry.add("spring.data.redis.host", redis::getHost);
registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
}
}
Slice-тесты
Пример
// Тест контроллера (без запуска всего контекста)
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mockMvc;
@MockBean CreateOrderUseCase createOrderUseCase;
@Test
void shouldReturn201WhenOrderCreated() throws Exception {
when(createOrderUseCase.create(any())).thenReturn(order);
mockMvc.perform(post("/api/v1/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{...}"))
.andExpect(status().isCreated())
.andExpect(header().exists("Location"));
}
}
ArchUnit для архитектурных правил
Пример
@AnalyzeClasses(packages = "com.example.order")
class ArchitectureTest {
@ArchTest
static final ArchRule domainShouldNotDependOnAdapters =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("..adapter..");
@ArchTest
static final ArchRule domainShouldNotDependOnSpring =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("org.springframework..");
}
Частые ошибки
- Тестирование на H2 вместо реальной БД: различия в SQL-диалектах приводят к пропущенным багам
- Отсутствие тестов на ошибочные сценарии: тестируется только happy path
- Mock всего подряд: unit-тест, который мокает всё, не тестирует ничего
- Запуск @SpringBootTest для каждого теста — используйте @WebMvcTest/@DataJpaTest где возможно
На собеседовании: покажите знание пирамиды тестирования и объясните, когда какой тип используется. Ключевая фраза: “Testcontainers заменил H2 — тестируем на реальном PostgreSQL”. Упоминание ArchUnit для автоматической проверки архитектурных правил показывает зрелость. Частый вопрос: “Чем @WebMvcTest отличается от @SpringBootTest?” — первый загружает только web-слой, второй поднимает весь контекст.