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

Стал бы разбивать тестирование метода на части

2.0 Middle🔥 121 комментариев
#Тестирование

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

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

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

Разбиение тестирования метода на части: практический подход

Когда нужно разбивать тесты

Да, я бы разбил тестирование сложного метода на несколько отдельных тестов. Это один из ключевых принципов Unit Testing — каждый тест должен проверять одно логическое поведение.

Принцип Single Responsibility в тестировании

Каждый тест должен:

  • Проверять один аспект функциональности
  • Иметь одно утверждение (или логически связанные утверждения)
  • Быть независимым от других тестов
  • Иметь понятное имя, описывающее что именно тестируется

Антипаттерн — множество проверок в одном тесте:

@Test
public void testCalculateOrderPrice() {
    Order order = new Order();
    order.addItem(new Item("Laptop", 1000.0, 1));
    order.addItem(new Item("Mouse", 50.0, 2));
    
    // Проверяем всё подряд — плохо!
    double price = order.calculateTotalPrice();
    assertEquals(1100.0, price);
    
    order.applyDiscount(0.1); // 10% скидка
    assertEquals(990.0, order.getTotalPrice());
    
    order.applyTax(0.2); // 20% налог
    assertEquals(1188.0, order.getTotalPrice());
    
    assertTrue(order.isValid());
    assertFalse(order.isEmpty());
}

Правильный подход — разбиение на части

// Тест 1: Базовый расчёт цены без скидок и налогов
@Test
public void calculateTotalPrice_WithMultipleItems_ReturnsCorrectSum() {
    Order order = new Order();
    order.addItem(new Item("Laptop", 1000.0, 1));
    order.addItem(new Item("Mouse", 50.0, 2));
    
    assertEquals(1100.0, order.calculateTotalPrice());
}

// Тест 2: Применение скидки
@Test
public void applyDiscount_WithValidPercentage_ReducesPrice() {
    Order order = new Order();
    order.addItem(new Item("Laptop", 1000.0, 1));
    order.applyDiscount(0.1);
    
    assertEquals(900.0, order.getTotalPrice());
}

// Тест 3: Применение налога
@Test
public void applyTax_WithValidPercentage_IncreasesPrice() {
    Order order = new Order();
    order.addItem(new Item("Laptop", 1000.0, 1));
    order.applyTax(0.2);
    
    assertEquals(1200.0, order.getTotalPrice());
}

// Тест 4: Валидация пустого заказа
@Test
public void isValid_WithEmptyOrder_ReturnsFalse() {
    Order order = new Order();
    assertFalse(order.isValid());
}

// Тест 5: Валидация непустого заказа
@Test
public void isValid_WithItems_ReturnsTrue() {
    Order order = new Order();
    order.addItem(new Item("Laptop", 1000.0, 1));
    assertTrue(order.isValid());
}

Преимущества разбиения тестов

1. Быстрая диагностика багов

  • Если тест упал, сразу ясно, что именно сломалось
  • Не нужно искать проблему среди 10 проверок

2. Лучший code coverage

Один монолитный тест может пройти до первой ошибки.
Разбитые тесты выявляют все проблемные пути.

3. Переиспользование и поддержка

  • Тесты становятся документацией кода
  • Проще добавлять новые сценарии
  • Меньше дублирования в Setup

4. Параллельное выполнение

  • JUnit запускает независимые тесты параллельно
  • Экономия времени выполнения suite

Когда объединять тесты

Есть редкие случаи, когда объединение оправдано:

1. Integration тесты с дорогим Setup

// Дорого создавать контекст, поэтому объединяем несколько проверок
@Test
public void integrationFlow_CompleteUserJourney() {
    // Setup database
    User user = createUser("john@test.com");
    assertTrue(userRepository.exists(user.getId()));
    
    // Update profile
    user.setName("John Doe");
    userRepository.save(user);
    assertEquals("John Doe", userRepository.findById(user.getId()).getName());
    
    // Delete user
    userRepository.delete(user.getId());
    assertFalse(userRepository.exists(user.getId()));
}

2. Тесты с общей, но дорогой фикстурой

public class DataProcessingTest {
    // Один раз парсим 1GB файл
    private static List<Record> largeDataset;
    
    @BeforeClass
    public static void setUpClass() {
        largeDataset = DataParser.parse("huge-file.csv");
    }
    
    @Test
    public void process_ReturnsCorrectCount() {
        assertEquals(1_000_000, largeDataset.size());
    }
    
    @Test
    public void filter_RemovesDuplicates() {
        List<Record> unique = largeDataset.stream()
            .distinct()
            .collect(toList());
        assertTrue(unique.size() <= largeDataset.size());
    }
}

Практический пример из боевого опыта

Тестировал сложный метод обработки платежей:

public class PaymentProcessingTest {
    
    private PaymentService paymentService;
    private PaymentGateway gateway;
    
    @Before
    public void setUp() {
        gateway = mock(PaymentGateway.class);
        paymentService = new PaymentService(gateway);
    }
    
    // Разбили на 6 отдельных тестов:
    
    @Test
    public void processPayment_ValidAmount_CallsGateway() {
        Payment payment = new Payment(100.0);
        paymentService.process(payment);
        verify(gateway).charge(100.0);
    }
    
    @Test
    public void processPayment_NegativeAmount_ThrowsException() {
        Payment payment = new Payment(-50.0);
        assertThrows(IllegalArgumentException.class, 
            () -> paymentService.process(payment));
    }
    
    @Test
    public void processPayment_GatewayFailure_ReturnsError() {
        doThrow(GatewayException.class).when(gateway).charge(anyDouble());
        Payment payment = new Payment(100.0);
        
        PaymentResult result = paymentService.process(payment);
        assertEquals(PaymentStatus.FAILED, result.getStatus());
    }
    
    @Test
    public void processPayment_RecordsTransaction() {
        Payment payment = new Payment(100.0);
        paymentService.process(payment);
        
        verify(gateway).logTransaction(any(TransactionLog.class));
    }
    
    @Test
    public void processPayment_NotifiesUser() {
        Payment payment = new Payment(100.0);
        paymentService.process(payment);
        
        verify(gateway).sendNotification(payment.getUserId());
    }
    
    @Test
    public void processPayment_UpdatesBalance() {
        User user = new User("john", 500.0);
        Payment payment = new Payment(100.0);
        
        paymentService.process(payment);
        assertEquals(400.0, user.getBalance());
    }
}

Итоговый вывод

Правило: разбивай тесты, если их можно разбить логически. Это улучшает читаемость, диагностику и поддерживаемость. Единственное исключение — когда Setup очень дорог и не может быть оптимизирован (редко).

Стал бы разбивать тестирование метода на части | PrepBro