TL;DR: AI-генерация тестов пропускает целые категории багов из-за структурных ограничений LLM. В 62.5% случаев тесты не покрывают реальный класс ошибки, при этом пропуски систематичны и предсказуемы.
Введение: эпоха AI-assisted testing
Современные LLM типа Claude/GPT стали неотъемлемой частью workflow senior-разработчиков. Особенно при:
- Рефакторинге legacy-кода
- Покрытии тестами
- Фиксации регрессий
Но наш анализ 16 реальных багов из SWE-bench показал: AI-тесты имеют предсказуемые blind spots. Они не случайны — это прямое следствие архитектурных ограничений языковых моделей.
Основные паттерны пропусков
1. Cascade-blindness
Типичный кейс: AI меняет метод A, пишет тест для метода A, но не анализирует impact на методы B, C, D.
Пример из sympy:
# Было
def nthroot_mod(a, n, p, all_roots=False):
if not isprime(p):
raise NotImplementedError("Not implemented for composite p")
# Стало
def nthroot_mod(a, n, p, all_roots=False):
if not isprime(p):
return _nthroot_mod_composite(a, n, p)
AI-тест:
def test_nthroot_mod_prime():
assert nthroot_mod(2, 3, 5) == ... # Тестирует только prime case
Пропущенный тест:
def test_nthroot_mod_composite():
assert nthroot_mod(1, 3, 15) is not None # Проверяет новый composite case
2. Circular mocking
LLM создают моки, которые повторяют их же предположения:
// Production bug:
interface APIResponse {
userId: string; // Реальный API
}
// AI-generated code:
function processUser(data: { user: { id: string } }) {
return data.user.id;
}
// AI-generated test:
jest.mock('api', () => ({
fetchUser: () => ({ user: { id: '123' } }) // Тот же некорректный контракт
}));
// Тест проходит, но production падает
3. Boundary blindness
AI редко тестирует edge cases:
// Пропущенные кейсы:
- Пустые массивы
- null/undefined
- Пограничные значения
- Максимальные числа
Практические решения
1. Blast radius analysis
Автоматизируем поиск зависимостей:
# AST-анализ для Python
import ast
def find_dependents(code, target_func):
tree = ast.parse(code)
dependents = []
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if hasattr(node.func, 'id') and node.func.id == target_func:
dependents.append(node)
return dependents
2. Mutation testing
Добавляем мутации для проверки тестов:
// Jest + Stryker
import { mutate } from 'stryker';
test('should handle null input', () => {
const original = (x) => x?.toUpperCase();
const mutated = mutate(original, { nullInput: true });
expect(mutated(null)).toBeNull();
});
3. Контрактное тестирование
Фиксируем API-контракты:
// Pact-тест
import { Pact } from '@pact-foundation/pact';
describe('API Contract', () => {
beforeAll(() => {
new Pact()
.addInteraction({
state: 'user exists',
uponReceiving: 'GET /user',
willRespondWith: {
status: 200,
body: {
userId: Matchers.string() // Явное требование
}
}
})
.start();
});
});
Заключение
AI-тестирование — мощный инструмент, но требует:
- Дополнительной верификации через mutation testing
- Анализа графа зависимостей
- Явного контрактного тестирования
Инструменты вроде Optinum автоматизируют поиск blind spots, но senior-разработчик должен понимать системные ограничения AI.
Key takeaways:
- AI пропускает целые категории тестов систематически
- Требуется доп. анализ blast radius изменений
- Контрактное тестирование обязательно для API
- Mutation testing выявляет ложноположительные тесты
Для глубокого погружения рекомендую:
- Изучить SWE-bench датасет
- Поэкспериментировать с Optinum
- Внедрить mutation testing в CI/CD
Источник: https://dev.to/anhnguyensynctree/ai-writes-your-tests-heres-what-it-systematically-misses-3a38