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

Нужно ли править код, если реализация не проходит тест при корректном поведении?

2.2 Middle🔥 91 комментариев
#Тестирование

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

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

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

Когда тест не проходит, но код работает корректно

Это классический конфликт, с которым я сталкивался много раз. Ответ: это зависит от ситуации, и нужно понимать корневую причину.

Ситуация 1: Тест написан неправильно

Часто проблема именно в тесте:

// Код: метод возвращает пользователя по ID
public User getUserById(String id) {
    return userRepository.findById(id).orElse(null);
}

// Неправильный тест
@Test
public void testGetUser() {
    User user = userService.getUserById("123");
    
    // ❌ Проблема: тест ищет пользователя в памяти
    // но база данных не инициализирована!
    assertEquals("John", user.getName());
}

// Правильный тест
@Test
@DirtiesContext  // Очистить контекст Spring после теста
public void testGetUser() {
    // 1. Setup: добавить тестовые данные
    User testUser = new User("123", "John", "john@example.com");
    userRepository.save(testUser);
    
    // 2. Execute
    User result = userService.getUserById("123");
    
    // 3. Assert
    assertNotNull(result);
    assertEquals("John", result.getName());
}

Решение: Исправить тест. Код правильный, тест нужно менять.

Ситуация 2: Граничные случаи

Тест может упустить важный граничный случай:

// Код
public int divide(int a, int b) {
    if (b == 0) {
        throw new IllegalArgumentException("Деление на 0");
    }
    return a / b;
}

// Неправильный тест (не проверяет граничный случай)
@Test
public void testDivide() {
    assertEquals(5, divide(10, 2));
    assertEquals(2, divide(10, 5));
}

// Правильный тест
@Test
public void testDivide() {
    assertEquals(5, divide(10, 2));
    assertEquals(2, divide(10, 5));
}

@Test
public void testDivideByZero() {
    // ✅ Проверяем граничный случай
    IllegalArgumentException exception = 
        assertThrows(IllegalArgumentException.class, 
            () -> divide(10, 0));
    
    assertEquals("Деление на 0", exception.getMessage());
}

Решение: Добавить больше тестов для граничных случаев.

Ситуация 3: Race Condition в многопоточности

Тест не репродуцирует условия разработки:

// Код (потокобезопасный)
public class Counter {
    private int value = 0;
    
    public synchronized void increment() {
        value++;
    }
    
    public synchronized int getValue() {
        return value;
    }
}

// Простой тест (может пройти случайно)
@Test
public void testIncrement() {
    Counter counter = new Counter();
    counter.increment();
    counter.increment();
    
    assertEquals(2, counter.getValue());
}

// Правильный тест (с многопоточностью)
@Test
public void testConcurrentIncrement() throws InterruptedException {
    Counter counter = new Counter();
    int threadCount = 100;
    int incrementsPerThread = 1000;
    
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    
    for (int i = 0; i < threadCount; i++) {
        executor.submit(() -> {
            for (int j = 0; j < incrementsPerThread; j++) {
                counter.increment();
            }
        });
    }
    
    executor.shutdown();
    executor.awaitTermination(10, TimeUnit.SECONDS);
    
    // Проверяем, что все инкременты произошли
    assertEquals(threadCount * incrementsPerThread, counter.getValue());
}

Решение: Переписать тест чтобы проверить многопоточный сценарий.

Ситуация 4: Порядок зависимостей (Flaky Test)

Тест может быть нестабильным:

// Нестабильный тест
@Test
public void testAsyncOperation() throws InterruptedException {
    CompletableFuture<String> future = asyncService.processAsync();
    
    // ❌ Даём только 100ms, но операция может занять больше
    Thread.sleep(100);
    
    assertEquals("done", future.get());
}

// Правильный тест
@Test
public void testAsyncOperation() throws InterruptedException, ExecutionException {
    CompletableFuture<String> future = asyncService.processAsync();
    
    // ✅ Ждём результат (с timeout)
    String result = future.get(5, TimeUnit.SECONDS);
    
    assertEquals("done", result);
}

Решение: Исправить timing в тесте.

Ситуация 5: Отличаются expectations

Тест проверяет не то, что нужно:

// Код: фильтр работает правильно
public List<String> filterByLength(List<String> items, int minLength) {
    return items.stream()
        .filter(s -> s.length() >= minLength)
        .collect(Collectors.toList());
}

// Неправильный тест (проверяет неправильное условие)
@Test
public void testFilter() {
    List<String> items = Arrays.asList("a", "hello", "world");
    List<String> result = filterByLength(items, 2);
    
    // ❌ Тест ждёт что фильтр вернёт "hello" и "world"
    // но на самом деле "a" имеет длину 1, поэтому исключается правильно
    assertEquals(Arrays.asList("hello", "world"), result);
}

// Правильный тест
@Test
public void testFilter() {
    List<String> items = Arrays.asList("a", "hello", "world", "hi");
    List<String> result = filterByLength(items, 2);
    
    // ✅ Проверяем что отфильтрованы элементы с длиной >= 2
    assertEquals(3, result.size());  // "hello", "world", "hi"
    assertEquals(Arrays.asList("hello", "world", "hi"), result);
}

Решение: Переписать expectations в тесте.

Мой подход: как я это решаю

Шаг 1: Убедиться, что код действительно работает

// Ручной тест или интеграционный тест
// в реальном окружении
public static void main(String[] args) {
    OrderService service = new OrderService();
    Order order = service.createOrder(new OrderRequest(...));
    
    System.out.println(order);  // Видим что работает
    System.out.println(order.getId());  // Есть ID
    System.out.println(order.getStatus());  // Статус правильный
}

Шаг 2: Проверить тест

// Что проверяет этот тест?
// Правильны ли expectations?
// Правильна ли setup?

@Test
public void testCreateOrder() {
    // ADD DEBUGGING
    System.out.println("Test starting...");
    
    Order order = service.createOrder(request);
    
    System.out.println("Order ID: " + order.getId());
    System.out.println("Order Status: " + order.getStatus());
    
    // Может выяснить где проблема
}

Шаг 3: Decide — менять код или тест

Если проблема в:

❌ Неправильном setup теста
   → Исправить тест

❌ Неправильных expectations
   → Исправить тест

❌ Недостаточном coverage граничных случаев
   → Добавить больше тестов

❌ Flaky/timing issues
   → Переписать тест с правильными waits

✅ Коде (если тест правильный)
   → Исправить код

Реальный пример из проекта

// Был баг в Payment Service
// Тест не проходил:
// Expected: 100.50
// Actual: 100.4999999999

@Test
public void testCalculateTax() {
    Payment payment = new Payment(BigDecimal.valueOf(100));
    
    // ❌ Неправильно: comparing doubles
    assertEquals(12.5, payment.calculateTax(0.125));
}

// Решение: использовать BigDecimal
@Test
public void testCalculateTax() {
    Payment payment = new Payment(BigDecimal.valueOf(100));
    
    // ✅ Правильно: comparing BigDecimals
    assertEquals(
        new BigDecimal("12.50"),
        payment.calculateTax(new BigDecimal("0.125"))
    );
}

Итоговый ответ

Нужно ли менять код если он работает но тест не проходит?

Ответ: Обычно нужно исправить тест, но может быть и код.

Проверьте в этом порядке:

  1. Тест корректен ли? (setup, expectations, assertions)
  2. Граничные случаи покрыты? (null, empty, edge values)
  3. Timing правильный? (для async операций)
  4. Expectations правильные? (что мы на самом деле проверяем)
  5. Окружение правильное? (mock'и, database state)

Если все вышеперечисленное правильно, тогда:

  • Исправьте код, если код действительно неправильный
  • Или обсудите с командой, что код может быть правильным, но тест требует ändern expectations

Best practice: Всегда пишите тесты ПЕРЕД кодом (TDD). Тогда вы избежите этого конфликта.

Нужно ли править код, если реализация не проходит тест при корректном поведении? | PrepBro