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

Для чего нужны тесты?

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

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

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

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

# Для чего нужны тесты?

Тесты — это один из самых важных компонентов качественной разработки программного обеспечения. Они обеспечивают надёжность, стабильность и уверенность в корректности кода. За 10+ лет разработки я убедился, что хорошие тесты экономят время и деньги.

1. Обнаружение ошибок (Bug Detection)

Тесты ловят баги до того, как они попадут в production.

Пример без тестов (ПЛОХО):

public class Calculator {
    public int divide(int a, int b) {
        return a / b;  // Что если b = 0? Возникнет ArithmeticException!
    }
}

// В production:
Calculator calc = new Calculator();
int result = calc.divide(10, 0);  // CRASH! Никто не проверил!

С тестами (ХОРОШО):

@Test
public void testDivideByZero() {
    Calculator calc = new Calculator();
    assertThrows(IllegalArgumentException.class, () -> calc.divide(10, 0));
}

@Test
public void testDivideNormal() {
    Calculator calc = new Calculator();
    assertEquals(5, calc.divide(10, 2));
}

Результат: Баг найден до deployment!

2. Регрессионное тестирование (Regression Prevention)

Тесты гарантируют, что новые изменения не сломали старый функционал.

Сценарий:

// v1.0 - работает
public class UserService {
    public boolean isValidEmail(String email) {
        return email.contains("@");
    }
}

// v2.0 - разработчик изменил логику
public class UserService {
    public boolean isValidEmail(String email) {
        // Забыл про точку после @
        return email.contains("@") && email.contains(".");
    }
}
// Но старые тесты ловят регрессию!

3. Документация кода (Living Documentation)

Тесты показывают, как использовать класс.

@Test
public void demonstrateUserCreation() {
    // Тест ПОКАЗЫВАЕТ как использовать UserService
    UserService service = new UserService(mockRepository);
    
    User user = service.createUser("john@example.com", "John");
    
    assertNotNull(user);
    assertEquals("john@example.com", user.getEmail());
    assertEquals("John", user.getName());
}

Без документации разработчик видит: как работает API из теста!

4. Уверенность при рефакторинге (Refactoring Confidence)

Тесты позволяют безопасно улучшать код без страха что-то сломать.

Сценарий рефакторинга:

// OLD (медленный код)
public class OrderService {
    public boolean isValidOrder(Order order) {
        if (order == null) return false;
        if (order.getItems() == null) return false;
        if (order.getItems().isEmpty()) return false;
        if (order.getTotal() == null) return false;
        if (order.getTotal().compareTo(BigDecimal.ZERO) <= 0) return false;
        return true;
    }
}

// NEW (чистый код)
public class OrderService {
    public boolean isValidOrder(Order order) {
        return order != null
            && !order.getItems().isEmpty()
            && order.getTotal() != null
            && order.getTotal().compareTo(BigDecimal.ZERO) > 0;
    }
}

// Тесты проходят! Рефакторинг сделан безопасно.

5. Проектирование через тесты (Test-Driven Design)

Написание тестов первыми помогает спроектировать лучший API.

// Сначала пишем тест (что хотим)
@Test
public void userShouldBeCreatedSuccessfully() {
    UserService service = new UserService(mockRepository);
    
    User user = service.createUser(CreateUserRequest.builder()
        .email("test@example.com")
        .name("Test User")
        .build());
    
    assertNotNull(user);
    assertEquals("test@example.com", user.getEmail());
}

// Потом реализуем (как это сделать)
public class UserService {
    public User createUser(CreateUserRequest request) {
        // Реализация...
    }
}

Результат: API проектируется на основе требований!

6. Снижение техдолга (Technical Debt Reduction)

Хорошие тесты предотвращают накопление проблем.

Без тестов:
Время →
├─ День 1: быстро сделал, работает
├─ День 10: нашёл баг, исправил
├─ День 20: баг вернулся при новых изменениях
├─ День 30: боюсь трогать код
└─ День 60: переписываем всё

С тестами:
Время →
├─ День 1: написал тесты, сделал
├─ День 10: вижу регрессию сразу
├─ День 20: уверенно рефакторю
├─ День 30: добавляю новые фичи без страха
└─ День 60: код стал лучше

7. Качество кода и производительность команды

// Тесты раньше ловят ошибки, чем QA
// Раньше ловят -> дешевле исправлять
// Стоимость исправления бага:
// - На своём компьютере: 5 минут
// - При code review: 15 минут
// - В staging: 30 минут
// - В production: 500+ минут (срочный фикс, hot-fix, оценка урона)

Типы тестов в Java

Unit тесты (самые важные)

@Test
public void testCalculateDiscount() {
    // Тестируем один метод изолированно
    OrderCalculator calculator = new OrderCalculator();
    BigDecimal discount = calculator.calculateDiscount(BigDecimal.valueOf(1000));
    assertEquals(BigDecimal.valueOf(100), discount);
}

Integration тесты

@SpringBootTest
public class UserRepositoryTest {
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @Transactional
    public void testSaveAndFindUser() {
        User user = new User("test@example.com", "Test");
        userRepository.save(user);
        
        User found = userRepository.findByEmail("test@example.com").orElse(null);
        assertNotNull(found);
        assertEquals("test@example.com", found.getEmail());
    }
}

E2E тесты

@SpringBootTest
public class UserApiE2ETest {
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    public void testCreateUserEndToEnd() {
        CreateUserRequest request = new CreateUserRequest("new@example.com", "User");
        ResponseEntity<UserDTO> response = restTemplate.postForEntity(
            "/api/v1/users", request, UserDTO.class);
        
        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        assertEquals("new@example.com", response.getBody().getEmail());
    }
}

Пример: Полное покрытие тестами

@Service
public class PaymentService {
    private final PaymentRepository paymentRepository;
    private final NotificationService notificationService;
    
    public Payment processPayment(PaymentRequest request) throws InvalidPaymentException {
        // Валидация
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new InvalidPaymentException("Amount must be positive");
        }
        
        // Обработка
        Payment payment = new Payment(request);
        Payment saved = paymentRepository.save(payment);
        
        // Уведомление
        notificationService.notifyPaymentSuccess(saved);
        
        return saved;
    }
}

// ТЕСТЫ:
public class PaymentServiceTest {
    @Mock
    private PaymentRepository paymentRepository;
    
    @Mock
    private NotificationService notificationService;
    
    @InjectMocks
    private PaymentService paymentService;
    
    @Before
    public void setup() {
        MockitoAnnotations.openMocks(this);
    }
    
    // Test 1: Happy path
    @Test
    public void testPaymentProcessedSuccessfully() {
        PaymentRequest request = new PaymentRequest("123", BigDecimal.valueOf(100));
        Payment savedPayment = new Payment(request);
        
        when(paymentRepository.save(any())).thenReturn(savedPayment);
        
        Payment result = paymentService.processPayment(request);
        
        assertNotNull(result);
        verify(notificationService).notifyPaymentSuccess(savedPayment);
    }
    
    // Test 2: Negative amount
    @Test
    public void testPaymentWithNegativeAmount() {
        PaymentRequest request = new PaymentRequest("123", BigDecimal.valueOf(-50));
        
        assertThrows(InvalidPaymentException.class, () -> 
            paymentService.processPayment(request));
    }
    
    // Test 3: Zero amount
    @Test
    public void testPaymentWithZeroAmount() {
        PaymentRequest request = new PaymentRequest("123", BigDecimal.ZERO);
        
        assertThrows(InvalidPaymentException.class, () -> 
            paymentService.processPayment(request));
    }
}

Метрики качества через тесты

Test Coverage должна быть:
- < 50% - опасно
- 50-70% - приемлемо
- 70-90% - хорошо (обычная цель)
- 90%+ - отлично

Время выполнения тестов:
- Unit тесты: < 5 минут (быстрая обратная связь)
- Все тесты: < 15 минут

Когда НЕ писать тесты?

  1. Getter/Setter - обычно не тестируем
  2. Конфиги - редко тестируем
  3. UI компоненты - интеграционные тесты

Заключение

Тесты нужны для: ✓ Обнаружения ошибок ✓ Предотвращения регрессий ✓ Документации кода ✓ Уверенности при изменениях ✓ Лучшего дизайна (TDD) ✓ Снижения затрат на исправления ✓ Уверенности команды в коде

В профессиональной разработке тесты — это не опция, а обязательность.