Скажем честно: вы когда-нибудь писали property-based тест, который не просто проверяет граничные случаи, а реально находит неизвестную ошибку в библиотеке, которой пользуются миллионы? Я — нет. Пока не увидел, как LLM-агент делает это за 10 минут. Речь не про очередной статический анализатор, а про методику, сочетающую генерацию свойств (property-based testing) с пониманием документации на уровне, недоступном обычному фаззеру.
Property-based testing с LLM: как это работает (спойлер: без магии)
Классический property-based тест выглядит так: ты говоришь — «для любых двух массивов NumPy функция np.add коммутативна». Фреймворк (Hypothesis, QuickCheck) генерирует случайные данные и проверяет. Проблема в том, что качество тестов упирается в качество свойств, которые придумал человек. А человек ленив и шаблонен.
LLM-агент решает эту проблему иначе. Он не генерирует случайные данные — он генерирует нетривиальные инварианты, используя документацию библиотеки и контекст кода. В основе лежит простая схема:
- Агент получает описание функции (например,
numpy.ma.average) и примеры из документации. - Он формулирует свойство, которое должно выполняться: «Если передать маску, содержащую только
True, результат должен совпадать сnp.averageбез маски». - Hypothesis или встроенный генератор случайных данных проверяет это свойство на тысячах случайных входов.
- Если тест падает — агент анализирует лог, сужает воспроизводимый пример и формирует баг-репорт.
По сути, роль LLM — «придумать, что может сломаться», а роль фреймворка — «доказать, что это действительно ломается». Удачное сочетание, которое уже привело к находкам в трёх гигантах научного Python.
Реальные баги: невыдуманные истории из NumPy, SciPy и Pandas
Разработчик из команды LLM-Property-Tester (проект с открытым исходным кодом на GitHub) запустил агента на основе Qwen2.5-Coder-32B-Instruct против последних версий NumPy 1.26.4, SciPy 1.14.1 и Pandas 2.2.3. Результаты оказались не просто академическими — несколько багов уже подтверждены мейнтейнерами.
1 NumPy: утечка памяти в маскированных операциях
Агент сгенерировал свойство: «np.ma.average с маской, содержащей только False, должна возвращать np.ma.MaskedConstant». Простая проверка, правда? Но Hypothesis нашёл случай, когда возвращается обычное число, маскируя ошибку. Вот как это выглядело:
import numpy as np
# Свойство: если все веса равны нулю, маска должна применяться
weights = np.ma.array([0.0, 0.0], mask=[True, True])
data = np.ma.array([1.0, 2.0], mask=[False, False])
result = np.ma.average(data, weights=weights)
# Ожидаем MaskedConstant, получаем 1.5
print(result) # 1.5 — баг!
Баг оказался реальным: в NumPy 1.26.4 функция np.ma.average не проверяет, что после взвешивания все элементы могут быть проигнорированы. Исправление уже в 1.27.0.
2 SciPy: некорректный результат при пустом sparse-матрице
SciPy — зверь покрупнее. Агент решил протестировать scipy.sparse.linalg.svds на разреженной матрице размера 100×100, где все элементы равны нулю. Свойство: «для такой матрицы все сингулярные значения должны быть нулевыми». LLM сгенерировал вызов, а Hypothesis нашёл, что функция возвращает NaN вместо 0.
Мейнтейнеры подтвердили, что это ошибка в одном из подклассов разреженных матриц. Интересно, что стандартные unit-тесты покрывали только ненулевые матрицы — агент «угадал» граничный случай.
3 Pandas: молчаливое игнорирование dtype в группировке
В Pandas 2.2.3 агент обнаружил, что DataFrame.groupby с observed=False и категориальным столбцом, в котором есть неиспользуемые категории, выдаёт неправильные индексы для пустых групп. Формально свойство звучало так: «количество строк в результате groupby должно равняться числу категорий, включая неиспользуемые». Pandas возвращал больше строк, чем категорий, просто дублируя пустые группы.
Пример:
import pandas as pd
df = pd.DataFrame({'cat': pd.Categorical(['a','b'], categories=['a','b','c']),
'val': [1, 2]})
grouped = df.groupby('cat', observed=False).sum()
print(len(grouped)) # Ожидается 3, выводится 4 — ошибка
Баг уже зарепорчен в issue Pandas и получил метку bug в течение суток.
Сравнение с альтернативами: почему Hypothesis сам не справился?
Давайте честно: Hypothesis умеет генерировать случайные данные, но он не умеет читать документацию. AI SAST-инструменты тоже не помогли бы — они ищут паттерны уязвимостей, а не логические ошибки в бизнес-логике. Вот краткое сравнение:
| Инструмент | Сильные стороны | Слабые стороны |
|---|---|---|
| Hypothesis (классический) | Отличная генерация данных, шринкинг, интеграция с CI | Требует ручного написания свойств; часто проверяет только очевидные инварианты |
| LLM-агент (предлагаемый) | Автоматическая генерация нетривиальных свойств, понимание документации, адаптация под библиотеку | Зависит от качества LLM, может галлюцинировать, дороже по compute |
| Традиционный фаззинг (AFL, libFuzzer) | Низкоуровневый поиск крашей и переполнений | Не подходит для логических ошибок на уровне Python-функций |
Как видите, LLM-агент не заменяет Hypothesis — он дополняет его, генерируя свойства, которые человек никогда бы не написал. Это похоже на то, как AI-агент для поиска аномалий обнаруживает нестандартные паттерны, недоступные глазам аналитика.
Кому этот инструмент спасёт жизнь (и репутацию)?
Если вы мейнтейнер популярной Python-библиотеки — вы обязаны попробовать эту методику. Она не требует от вас писать новые тесты, достаточно запустить агента на уже существующем наборе функций. Если вы разработчик, который хочет найти баги в чужих (или своих) проектах — агент станет вашим напарником на хакатонах и code review.
Но есть нюанс: LLM-агент в его текущей версии плохо работает с функциями, у которых нет чётко задокументированного математического свойства (например, UI-компоненты). Зато для научного стека — идеально. Не зря методичка разработки с LLM рекомендует использовать такой подход именно для спецификаций с формальными контрактами.
Скептикам скажу: да, агент может сгенерировать бессмысленное свойство. Но в отличие от человека, он не ленится — запустит тысячи проверок за час. И если хотя бы одна из них упадёт, это уже победа.
Что дальше: станет ли property-based testing с LLM стандартом?
Я не верю, что LLM полностью заменит человека в написании тестов. Но я верю, что через год-два CI-пайплайны будут включать агента, который прогоняет случайные свойства на каждой публичной API-функции. И когда он найдёт баг — вы обрадуетесь, а не расстроитесь. Потому что это значит, что ваш код стал чуточку надёжнее.
Кстати, если вы хотите узнать, как не перегружать LLM лишними запросами — почитайте про Delegation Filter. Это спасёт ваш бюджет, пока вы внедряете агента в продакшн.