Стал бы разбивать тестирование метода на части
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разбиение тестирования метода на части: практический подход
Когда нужно разбивать тесты
Да, я бы разбил тестирование сложного метода на несколько отдельных тестов. Это один из ключевых принципов 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 очень дорог и не может быть оптимизирован (редко).