← Назад к вопросам

Что лучше тестируется: монолит или микросервисная архитектура

2.3 Middle🔥 221 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Что лучше тестируется: монолит или микросервисная архитектура

Оба подхода имеют свои преимущества и недостатки в контексте тестирования. Нет однозначного ответа — это зависит от масштаба, типа приложения и тестовой стратегии.

Тестирование монолита

Преимущества монолита в тестировании

1. Unit-тесты очень быстрые

// В монолите все находится в одном процессе
@SpringBootTest
public class OrderServiceTest {
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Test
    public void testOrderCreation() {
        Order order = orderService.createOrder(product, quantity);
        
        // Все сервисы работают в памяти — очень быстро
        assertTrue(order.getId() > 0);
    }
}

2. Нет сетевых вызовов в тестах

// Тестирование взаимодействия сервисов без HTTP
@SpringBootTest
public class OrderIntegrationTest {
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Test
    public void testOrderWithPayment() {
        // Оба сервиса находятся в том же приложении
        // Нет HTTP вызовов, все синхронно и быстро
        Order order = orderService.createAndPay(100.0);
        assertEquals("PAID", order.getStatus());
    }
}

3. Легко тестировать транзакции

@SpringBootTest
@Transactional
public class TransactionTest {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private PaymentRepository paymentRepository;
    
    @Test
    public void testAtomicOperation() {
        // Все в одной транзакции
        Order order = new Order();
        orderRepository.save(order);
        
        Payment payment = new Payment();
        paymentRepository.save(payment);
        
        // Откат всего в случае ошибки
    }
}

4. Полное покрытие через unit-тесты

// Легко достичь 90%+ покрытия
@ExtendWith(MockitoExtension.class)
public class OrderCalculationTest {
    @Mock
    private TaxService taxService;
    
    @InjectMocks
    private OrderCalculation calculation;
    
    @Test
    public void testDiscountCalculation() {
        when(taxService.getTaxRate("US")).thenReturn(0.1);
        double total = calculation.calculateTotal(100, "US");
        assertEquals(110, total);
    }
}

Недостатки монолита в тестировании

1. Зависимости между модулями

// Тестирование одного модуля требует установки других
@SpringBootTest
public class ProductServiceTest {
    @Autowired
    private ProductService productService;
    
    @Autowired
    private InventoryService inventoryService;  // Нужен для работы
    
    @Autowired
    private RecommendationService recommendationService;  // И этот
    
    @Autowired
    private AnalyticsService analyticsService;  // И этот тоже!
    
    @Test
    public void testGetProduct() {
        // Загружается весь контекст Spring
        // Может быть медленно и сложно
        Product product = productService.getProduct(1);
    }
}

2. Интеграционные тесты медленные

// Весь монолит запускается в памяти
@SpringBootTest
public class FullApplicationTest {
    // Может занять 10-30 секунд на загрузку контекста
    // В проекте с 100+ тестами это становится bottleneck
}

3. Сложная подготовка данных

// Нужно подготовить состояние для всех модулей
@Test
public void testComplexScenario() {
    // Создаем продукт
    Product product = productRepository.save(new Product("Item", 100));
    
    // Создаем инвентарь
    Inventory inv = inventoryRepository.save(new Inventory(product, 10));
    
    // Создаем промо код
    PromoCode promo = promoRepository.save(new PromoCode("SAVE10", 0.1));
    
    // ... много кода для подготовки ...
    
    Order order = orderService.createOrder(product, promo);
}

Тестирование микросервисной архитектуры

Преимущества микросервисов в тестировании

1. Изолированные unit-тесты

// Каждый микросервис имеет свой контекст
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OrderServiceTest {
    @Autowired
    private OrderController controller;
    
    @MockBean  // Внешние сервисы мокируются
    private InventoryClient inventoryClient;
    
    @MockBean
    private PaymentClient paymentClient;
    
    @Test
    public void testCreateOrder() {
        when(inventoryClient.checkStock(1L)).thenReturn(true);
        when(paymentClient.processPayment(100)).thenReturn(true);
        
        // Тестируем только OrderService, остальное замокировано
        ResponseEntity<Order> response = controller.createOrder(request);
        assertEquals(HttpStatus.CREATED, response.getStatusCode());
    }
}

2. Параллельное тестирование

# Можно тестировать все сервисы параллельно
# Сервис 1: ./gradlew test  (5 сек)
# Сервис 2: ./gradlew test  (5 сек)
# Сервис 3: ./gradlew test  (5 сек)
# Время: 5 сек (параллельно)
# Вместо 15 сек (последовательно)

3. Слабая связанность

// OrderService не знает деталей других сервисов
@Service
public class OrderService {
    @Autowired
    private InventoryClient inventoryClient;
    
    public Order createOrder(OrderRequest request) {
        // Просто вызываем REST API
        if (inventoryClient.hasStock(request.getProductId())) {
            return orderRepository.save(new Order());
        }
        throw new OutOfStockException();
    }
}

// Тест замокирует HTTP клиент
@Test
public void testCreateOrder() {
    when(inventoryClient.hasStock(1L)).thenReturn(true);
    Order order = orderService.createOrder(request);
    assertEquals("CREATED", order.getStatus());
}

4. E2E тесты через API контракты

// Consumer-Driven Contract Testing (CDC)
public class OrderServiceConsumerTest {
    @Test
    public void testOrderServiceExpectsInventoryAPI() {
        // Используем Contract для проверки совместимости
        PactDslWithProvider builder = new PactDslWithProvider();
        
        builder
            .given("product with id 1 exists")
            .uponReceiving("a request for stock check")
            .path("/inventory/1/stock")
            .willRespondWith()
            .status(200)
            .body(json.object().numberValue("quantity", 100))
            .toPact();
        
        // Гарантирует, что OrderService работает с реальным API
    }
}

Недостатки микросервисов в тестировании

1. Сложность интеграционных тестов

// Нужно поднимать несколько контейнеров
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class OrderIntegrationTest {
    @Autowired
    private TestRestTemplate restTemplate;
    
    @BeforeEach
    public void setup() {
        // Запускаем все микросервисы (Docker Compose)
        // Может занять минуты!
    }
    
    @Test
    public void testFullOrderFlow() {
        // Очень медленный тест
        // Может упасть из-за сетевых проблем
    }
}

2. Проблемы с сетью

// Тест может упасть из-за timeout
@Test
@Timeout(30)  // 30 секунд
public void testOrderProcessing() {
    // Если Inventory Service медленный — тест упадет
    Order order = orderService.createOrder(request);
}

3. Сложность распределенных транзакций

// Нет простой ACID гарантии
public class OrderService {
    public void createOrder(OrderRequest request) {
        // Шаг 1: создаем заказ
        Order order = orderRepository.save(new Order());
        
        // Шаг 2: вызываем внешний сервис
        try {
            paymentService.charge(order.getId(), request.getAmount());
        } catch (Exception e) {
            // Как откатить создание заказа если платеж не прошел?
            // SAGA pattern? Compensating transaction?
            orderRepository.delete(order);  // Может не удалиться!
        }
    }
}

4. Сложность контрактного тестирования

// Нужно тестировать contracts между сервисами
// Требует Pact, Spring Cloud Contract или подобных инструментов
// Усложняет CI/CD pipeline

Сравнительная таблица

АспектМонолитМикросервисы
Unit-тестыОчень быстрыеБыстрые (при правильной изоляции)
Integration-тестыМедленные (весь контекст)Сложные (много сервисов)
E2E тестыПроще (один процесс)Сложнее (распределенная система)
ИзоляцияСложная (много зависимостей)Легкая (слабая связанность)
ПараллелизмВ одном процессеМежду сервисами
ТранзакцииACID по умолчаниюНужна SAGA
DebuggingПросто (один стек)Сложно (распределено)
Скорость тестированияЗависит от размераЗависит от кол-во сервисов

Лучшие практики для каждого подхода

Для монолита

  1. Unit-тесты для бизнес-логики (90% покрытия)
  2. Интеграционные тесты для критичных путей (10% времени)
  3. E2E тесты в production (smoke tests)
  4. Используй @MockBean для отключения ненужных компонентов
@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    classes = OrderServiceApplication.class
)
public class OrderServiceTest {
    @MockBean  // Отключаем неиспользуемые сервисы
    private AnalyticsService analyticsService;
    
    @Autowired
    private OrderService orderService;  // Используем реальный
}

Для микросервисов

  1. Unit-тесты для каждого сервиса (80% покрытие)
  2. Contract-тесты между сервисами (Spring Cloud Contract)
  3. Integration-тесты через docker-compose (10% времени)
  4. E2E тесты через API Gateway
// Используй Pact для контрактных тестов
@Provider("InventoryService")
@PactFolder("pacts")
public class InventoryServiceContractTest {
    @TestTemplate
    void testContract(PactVerificationContext context) {
        context.verifyInteraction();
    }
}

Вывод

  1. Монолит лучше тестируется на уровне unit и integration тестов благодаря простоте
  2. Микросервисы требуют более сложной стратегии тестирования (contracts, E2E)
  3. Выбор зависит от:
    • Размера команды
    • Количества разработчиков
    • Требований к масштабируемости
    • Инструментов и опыта
  4. Оптимально: начать с монолита, тестировать unit-тесты (быстро), затем мигрировать на микросервисы при необходимости
  5. Главное: независимо от архитектуры — иметь хорошее покрытие тестами и стратегию тестирования