5 антипаттернов AI-тестов: Cursor, Copilot, Claude — примеры на Java | AiManual
AiManual Logo Ai / Manual.
28 Май 2026 Гайд

Почему AI-тесты не ловят баги: 5 антипаттернов Cursor, Copilot и Claude с примерами

Разбор типичных ошибок при генерации тестов нейросетями. Почему AI-тесты дают ложное чувство безопасности и как это исправить. Примеры на Java.

Зеленый CI, красный продакшен

Вы запускаете тесты — все зеленое. Покрытие — 85%. Copilot или Cursor написали сотни строк тестового кода за минуту. Чувствуете прилив уверенности? Зря. Я тоже так делал. И именно эти тесты пропустили баг, который положил прод в прошлую пятницу.

Проблема не в AI как таковом — модели 2026 года (Claude 4 Sonnet, GPT-5, Gemini 3) пишут чертовски хороший код. Беда в том, что мы слепо доверяем сгенерированным тестам, не понимая их слабых мест. Как показывает недавнее исследование VAKRA (ссылка ниже), AI-агенты отлично справляются с типовыми сценариями, но проваливаются на граничных условиях — ровно там, где живут настоящие баги.

Предупреждение: если вы используете Cursor, Copilot или Claude для написания unit-тестов без ручного ревью, вы, скорее всего, создаете фальшивое покрытие. Тесты проходят, баги остаются.

Зачем я это пишу? Чтобы вы перестали тешить себя иллюзией. Ниже — 5 антипаттернов, которые я нашел в сотнях AI-сгенерированных тестов. Каждый с конкретным кодом на Java и способом лечения. Если после прочтения вы не пойдете фиксить свои тесты — я зря потратил время.

1. Тест-пустышка: «зеленый» без единой проверки

Симптом: метод сложный, а тест — копипаста с моком и парой assertTrue(true).

AI часто генерирует тесты, которые никогда не упадут. Почему? Потому что модель учится на репозиториях, где куча мусорных тестов. В 2025 году Stack Overflow провели опрос: 34% разработчиков признались, что их тесты бесполезны. С AI этот процент растет.

1Как выглядит

@Test
void testCalculateDiscount() {
    Order order = mock(Order.class);
    when(order.getTotal()).thenReturn(100.0);
    DiscountCalculator calculator = new DiscountCalculator();
    double result = calculator.calculateDiscount(order);
    assertTrue(true); // <-- Бесполезно!
}

Почему это плохо: тест не проверяет результат. assertTrue(true) всегда зеленый. Если калькулятор вернет -1 или null — тест пройдет. В реальном проекте такой тест только засоряет отчет о покрытии.

2Как исправить

@Test
void calculateDiscount_regularOrder_returns10Percent() {
    Order order = mock(Order.class);
    when(order.getTotal()).thenReturn(100.0);
    DiscountCalculator calculator = new DiscountCalculator();
    double result = calculator.calculateDiscount(order);
    assertEquals(10.0, result, 0.001);
}

Проверяйте конкретное значение, границы, ошибки. Если ваш тест можно заменить на @Test void test(){} — выметайте в мусорку.

2. Моковый ад: тестируем мок, а не логику

AI обожает моки. Особенно Copilot — он их плодит как кроликов. В итоге вы тестируете не взаимодействие компонентов, а поведение мок-объектов. Классика: вы замокали репозиторий, проверили, что сервис вызвал save() — и вуаля, 80% покрытия. А то, что в реальности репозиторий кидает SQL-исключение, тест не проверяет.

В нашем сравнении DeepAgents CLI с Claude Code мы заметили: Claude Code генерирует до 70% тестов, которые проверяют только вызовы методов, а не результаты. Это ложное чувство безопасности.

// AI-сгенерированный тест
@Test
void testCreateUser() {
    UserRepository repo = mock(UserRepository.class);
    EmailService email = mock(EmailService.class);
    UserService service = new UserService(repo, email);

    service.createUser("test@example.com");

    verify(repo).save(any(User.class)); // проверяем вызов
    // но не проверяем, что пользователь сохранен с правильными полями!
}

Решение: используйте реальные объекты где возможно (in-memory базы, Fake), или хотя бы захватывайте аргументы и проверяйте их содержимое.

@Test
void createUser_savesUserWithEmail() {
    // in-memory implementation
    FakeUserRepository repo = new FakeUserRepository();
    EmailService email = mock(EmailService.class);
    UserService service = new UserService(repo, email);

    service.createUser("test@example.com");

    assertEquals(1, repo.count());
    assertEquals("test@example.com", repo.findById(1).getEmail());
}

Звучит как больше кода? Да. Но он ловит баги.

3. Границы — для слабаков: AI игнорирует edge cases

AI модели, особенно в режиме чата, склонны генерировать тесты для «счастливого пути». Пустые списки, null, отрицательные числа, переполнение — все это остается за бортом.

Вспомните статью о VAKRA: AI-агенты путаются при работе с тремя API — то же самое происходит с тестами. Модель не видит граничных условий, если они не описаны в промпте.

// Метод, который нужно протестировать
public double getProgressPercentage(int current, int total) {
    return (double) current / total * 100;
}

// AI-сгенерированный тест
@Test
void testProgress() {
    assertEquals(50.0, getProgressPercentage(50, 100));
    // нет проверки total = 0, current > total, current < 0
}

Как заставить AI писать граничные тесты: явно указывайте в промпте «добавь тесты для null, пустых коллекций, нулевых делителей». Или используйте property-based testing (jqwik, quickcheck) — AI может сгенерировать генераторы случайных данных.

@Test
void progress_withTotalZero_throwsException() {
    assertThrows(ArithmeticException.class, () -> getProgressPercentage(10, 0));
}

Не надейтесь, что AI сам додумается — у него нет контекста вашей предметной области.

4. Покрытие кода, а не логики: тестируем строки, а не поведение

Метрики вроде JaCoCo — зло. Разработчики гонятся за 100% покрытием, а AI помогает: генерирует тесты для геттеров, сеттеров, конструкторов. Покрытие растет, баги множатся.

Cursor (версия 2026.1) в своем режиме Agent часто предлагает «improve coverage» — и вставляет тесты для пустых методов. А помните историю с .cursorrules и капибарами? Люди прячут в системные промпты целые мантры, чтобы программировать поведение AI. Но почему-то для тестов таких правил не пишут.

// AI-сгенерированный тест для getter'а
@Test
void getName_returnsName() {
    User user = new User("Alice");
    assertEquals("Alice", user.getName());
}

// Это бесполезно. Тест никогда не упадет, если getName не сломают специально.
// Но если в setName есть валидация длинного имени — AI не проверит.

Правило: тестируйте поведение, а не поля. Пишите тесты для инвариантов: «после setName имя не может быть длиннее 50 символов». Только такие тесты имеют ценность.

5. Регрессия — враг AI: тесты не помнят прошлые баги

AI генерирует тесты на основе текущего кода. Он не знает, какие баги были исправлены неделю назад. Поэтому регрессионные тесты — зона ответственности человека.

Если вы используете Claude Code для автоматизации — как в нашем гайде по Claude Code — у вас, вероятно, есть пайплайн. Но AI-агент не подключен к вашей баг-трекинговой системе. Он не сможет сам написать тест для кейса, который был зафиксирован в Jira.

Пример из жизни: клиентский заказ с суммой 0.00 проходил валидацию, хотя должен был падать. Баг исправили вручную. На следующий день Copilot сгенерировал новый тест для этого сервиса — и снова пропустил этот сценарий.

Мораль: заведите привычку после каждого исправления бага писать тест, который воспроизводит дефект. AI может помочь, только если вы дадите ему точное описание.

Как использовать AI для тестов без иллюзий

Не надо отказываться от нейросетей. Просто помните: AI — это джуниор, который пишет много кода, но не понимает бизнес-логику. Ваша задача — ревьювить и дописывать.

  • Никогда не принимайте AI-тесты без assert-проверок результатов.
  • Всегда требуйте от AI граничные случаи через промпт.
  • Добавляйте в .cursorrules или системный промпт правила для тестов: «использовать только AssertJ с мягкими проверками», «мокать только внешние сервисы».
  • Проверяйте регрессионные тесты вручную.

Хотите глубже? Почитайте наш промпт-гайд для Cursor и Copilot — там есть раздел про тестирование. Или загляните в пентест нейросетей — там показано, как AI fallback-сценарии провоцируют баги.

Лично я теперь к каждому AI-сгенерированному тесту добавляю один ручной — на самый неочевидный кейс. И покрытие упало с 90% до 70%. Зато баги в проде перестали появляться.

Подписаться на канал