Мы разработали агента, который умеет эффективно находить баги в крупных программных проектах. Для этого агент выводит общие свойства кода, которые должны выполняться, а затем применяет тестирование на основе свойств (property-based testing) — технику, похожую на фаззинг. Так нам удалось обнаружить баги в популярных Python-пакетах, включая NumPy, SciPy и Pandas. После тщательной ручной проверки мы начали сообщать об этих багах разработчикам; несколько из них уже исправлены.
Подробнее можно прочитать в полной версии статьи, посмотреть GitHub-репозиторий или изучить найденные нами баги на нашем сайте.
Сделать программу полностью свободной от багов — одна из самых сложных задач в разработке ПО. Ошибки часто остаются в коде, несмотря на все усилия разработчиков. Самый распространённый подход к тестированию — тесты на конкретных примерах: разработчик задаёт один конкретный сценарий использования и проверяет, что фактический результат совпадает с ожидаемым.
Например, можно проверить, что функция сортировки, вызванная для списка [2, 10, 5, 4], вернёт [2, 4, 5, 10]. Но полностью покрыть программу такими тестами трудно: баги часто прячутся в краевых случаях, которые разработчик не проверил. В конце концов, если разработчик не додумался протестировать какой-то краевой случай, велика вероятность, что он не учёл его и при реализации.
Тестирование на основе свойств устроено иначе. Это подход к тестированию, при котором мы проверяем, выполняется ли некоторое общее свойство кода для всех или почти всех входных данных. Разработчик задаёт свойство или инвариант программы — например, что десериализация JSON обратна сериализации, — а также описывает, какие входные данные это свойство принимает, например любые JSON-сериализуемые объекты.
Затем фреймворк для тестирования на основе свойств автоматически ищет контрпример к этому свойству, генерируя валидные входы как тестовые случаи с помощью техник, похожих на фаззинг. Поскольку разработчик описывает общее пространство входных данных, а не отдельные тест-кейсы, тестирование на основе свойств избавляет его от необходимости вручную придумывать все краевые случаи и позволяет рассуждать о программе на более высоком уровне абстракции.
В нашей статье, которую мы представили на воркшопе NeurIPS Deep Learning for Code 2025 и которая выросла из проекта MATS, мы описали ИИ-агента, самостоятельно пишущего тесты на основе свойств для существующего кода. Мы настроили агента так, чтобы он находил свойства программы, читая аннотации типов, строки документации, имена функций и комментарии. Затем агент писал соответствующие тесты на основе свойств с помощью Hypothesis.
В этой работе мы сосредоточились на поиске багов в целом, а не только уязвимостей безопасности. Многие классы логических ошибок, из которых вырастают уязвимости, хорошо ложатся на тестирование на основе свойств. Например, в недавней статье мы занимались поиском багов в смарт-контрактах; почти все найденные там уязвимости были следствием логических ошибок. В будущем, как нам кажется, похожие техники можно будет применять проактивно — чтобы находить баги ещё до деплоя.
Мы запустили нашего агента и нашли сотни потенциальных багов в популярных открытых Python-репозиториях, включая NumPy, SciPy и Pandas. Чтобы раскрывать эти баги ответственно и не создавать лишнюю нагрузку для мейнтейнеров, мы тщательно проверяли каждый из них.
Процесс проверки получился более трудоёмким, чем тот, который мы обычно использовали бы для ревью собственного кода, но для нас было важнее сократить число ложных срабатываний. Мы действовали так: сначала отбирали для проверки только баги с самым высоким приоритетом. Затем отправляли эти потенциальные баги на ревью трём экспертам — в среднем на каждый баг уходил час проверки. После этого мы отбрасывали любой баг, в корректности которого сомневался хотя бы один из трёх ручных ревьюеров. Наконец, мы, авторы этого поста, вручную проверяли каждый оставшийся баг-кандидат. Только если мы сами были уверены, что это действительно баг, мы вручную заводили issue у мейнтейнера соответствующего репозитория. Несколько таких баг-репортов мы уже отправили, и сейчас готовим к отправке ещё много других.
Мы открыли все наши данные: туда входят и баги, которые ещё не прошли валидацию, и даже те, которые мы сами признали невалидными — для полноты картины и чтобы мейнтейнеры могли посмотреть их на нашем сайте. В ближайшие недели мы планируем завести issue по многим из оставшихся багов — после дополнительной проверки — а также расширить проект на другие PyPI-пакеты.
# тест на конкретных примерах def test_sort(): assert my_sort([1,3,2]) == [1,2,3] assert my_sort([1,0,-5]) == [-5,0,1] # тест на основе свойств в Hypothesis from hypothesis import given, strategies as st @given(st.lists(st.integers())) def test_sort(lst): result = my_sort(lst) for i in range(len(result)-1): assert result[i] <= result[i+1]
Выше два подхода к тестированию кода. Модульный тест на конкретных примерах проверяет вручную заданные входные данные. Тест на основе свойств, наоборот, задаёт общее свойство — например, определение отсортированного списка — и полагается на фреймворк, который автоматически строит входные данные, способные это свойство нарушить.
Наш агент для тестирования на основе свойств
Наш агент для тестирования на основе свойств реализован как пользовательская команда Claude Code. Агент принимает один аргумент, который указывает на конкретный объект анализа: отдельный Python-файл, например normalizers.py; модуль, например numpy или scipy.signal; либо функцию, например requests.get или json.loads. После этого агент ищет потенциальные баги по следующему процессу:
Прочитать и понять объект анализа: изучить код, подтянуть релевантную документацию и разобраться, как выбранный фрагмент связан с остальной кодовой базой.
Предложить свойства, основанные на этих наблюдениях.
Написать соответствующие тесты на основе свойств на Hypothesis.
Запустить тесты и проанализировать результат: если тест упал, действительно ли он нашёл баг или сам тест нужно поправить? Если тест прошёл, проверяет ли он что-то содержательное или это просто тривиальная проверка?
Если агент уверен, что нашёл настоящий баг, он оформляет структурированный баг-репорт.

Чтобы агенту было проще справляться с многошаговыми рассуждениями на длинной дистанции, мы просим его вести список задач и отслеживать в нём свой прогресс.
Одним из главных приоритетов при проектировании агента было снижение числа ложных срабатываний. По нашему опыту, полезные инструменты для разработчиков стараются минимизировать количество ошибочных отчётов, которые попадают к разработчику. Цикл самопроверки помогает снизить число ложных срабатываний, как и привязка предлагаемых свойств к явному использованию объекта анализа и его документации. Например, в одном из запусков агент написал свойство, которое сначала успешно проходило проверку. Однако после самопроверки агент понял, что обернул весь тест в блок try/except. Когда он убрал этот блок, тест упал, и агент нашёл баг. Мы также заметили, что самопроверка заметно улучшилась в Opus 4.1 и Sonnet 4.5 по сравнению с Sonnet 4.
Пример запуска
Чтобы показать, как агент проходит по объекту анализа во время тестирования, мы приводим пересказанный лог работы, в котором Claude находит баг в реализации numpy.random.wald. Агент начинает с изучения функции: её сигнатуры, строки документации и даже существующих тестов.
лог работы можно посмотреть в оригинале
Обратите внимание: предложенное исправление некорректно. Когда мы исправляли этот баг, мы отследили источник ошибки до численно неустойчивого вычисления; принятое исправление можно посмотреть здесь.
Поиск багов в реальных PyPI-пакетах
Чтобы проверить возможности агента в реальных условиях, мы собрали разнообразный набор из более чем 100 популярных Python-пакетов. Эти библиотеки охватывают самые разные области: от численных вычислений и парсинга до баз данных. Мы запускали агента на каждом из этих пакетов и собирали все сгенерированные баг-репорты.
Оценка агента
На первом этапе оценки, который описан в нашей статье, мы запускали агента с Claude Opus 4.1 на каждом пакете и собирали все сгенерированные баг-репорты. Для оценки этих отчётов мы остановились на двух критериях: во-первых, «это реальный баг?»; во-вторых, «это одновременно реальный баг и то, о чём мы действительно стали бы писать мейнтейнерам библиотеки?». Второй критерий строже первого: например, баг может быть настоящим, но слишком незначительным, чтобы заводить по нему issue.
Из 984 баг-репортов мы вручную выбрали 50 для ревью. Оказалось, что 56% из них описывали реальные баги, а 32% — реальные баги, о которых мы также стали бы сообщать мейнтейнерам.
На основе этого ручного ревью мы разработали рубрику, которая оценивает баги по 15-балльной шкале. Её цель — ранжировать выше баги, которые с большей вероятностью окажутся реальными и достойными исправления. Затем мы использовали Opus 4.1, чтобы оценить по этой рубрике все баг-репорты. Этот этап ранжирования оказался довольно эффективным: среди баг-репортов с максимальными оценками 86% были реальными, а 81% — одновременно реальными и достойными отправки мейнтейнерам.
На втором этапе оценки мы запустили агента с Sonnet 4.5 на подмножестве из 10 важных пакетов, причём каждый пакет прогоняли несколько раз. Также мы разработали агента-оценщика на Sonnet 4.5: он читал код и баг-репорт, чтобы проверить корректность и серьёзность бага. Этот подход был сложнее рубрики с первого этапа. Наконец, мы оплатили работу трёх экспертов, которые проверяли корректность багов высокой серьёзности.
Все баг-репорты, которые нашёл наш агент, можно посмотреть здесь: https://mmaaz-git.github.io/agentic-pbt-site/.
Валидация со стороны мейнтейнеров
Оценивать эффективность любого инструмента, который ищет баги в коде, сложно. Мы стараемся как можно лучше проверять корректность баг-репортов, но последнее слово всё равно остаётся за мейнтейнерами пакетов. Чтобы убедиться, что агент действительно находит баги, которые мейнтейнеры считают реальными и достойными исправления, мы выбрали пять особенно интересных случаев и вручную отправили их в соответствующие GitHub-репозитории вместе с предложенным патчем. В ближайшие недели мы планируем продолжать репортить новые баги по мере их проверки.
numpy
numpy.random.wald иногда возвращает отрицательные числа. Это баг, потому что значения из распределения Вальда должны быть только положительными. Именно этот баг мы показывали выше в примере запуска. Claude корректно определил это свойство распределения Вальда и написал простой тест на основе свойств, который проверял, что все сгенерированные значения положительны.
Мы отследили ошибку до катастрофической потери точности (catastrophic cancellation) в коде и, когда отправляли pull request, предложили более численно устойчивую формулу. Как показали мейнтейнеры NumPy в pull request, наша новая формулировка даёт относительную ошибку почти на десять порядков ниже, чем прежний алгоритм.
Смерженный патч: https://github.com/numpy/numpy/pull/29609
aws-lambda-powertools
slice_dictionary() снова и снова возвращает первый фрагмент, потому что не инкрементирует итератор. Агент поймал это, определив, что если нарезать словарь на фрагменты, а потом собрать обратно, должен получиться исходный словарь.
Смерженный патч: https://github.com/aws-powertools/powertools-lambda-python/pull/7246
cloudformation-cli-java-plugin
item_hash() выдаёт одно и то же значение hash(None) для всех списков, потому что использует in-place метод .sort(), который возвращает None. Агент поймал это, проверяя, что у разных входных данных должны быть разные хеши.
Смерженный патч: https://github.com/aws-cloudformation/cloudformation-cli/pull/1106
tokenizers
В EncodingVisualizer.calculate_label_colors() не хватает закрывающей скобки, из-за чего функция возвращает невалидный HSL-код CSS. Агент обнаружил это, проверяя, что результат должен совпадать с регулярным выражением для HSL-кода цвета.
Смерженный патч: https://github.com/huggingface/tokenizers/pull/1853
python-dateutil
easter() для некоторых лет возвращает дату, которая не приходится на воскресенье, при использовании юлианского календаря. Мейнтейнеры определили, что это ожидаемое поведение из-за различий между календарными системами, и признали, что семантика здесь тонкая.
Issue признан невалидным: https://github.com/dateutil/dateutil/issues/1437
Репорт в python-dateutil показывает важное ограничение агента: выводить свойства из кода с тонкой или сложной семантикой всё ещё трудно. Если код опирается на неявное допущение, только мейнтейнеры библиотеки могут решить, какое свойство действительно нужно проверять.
Подведем итоги
По мере того как языковые модели становятся лучше, мы считаем, что агентное тестирование на основе свойств может стать всё более ценным дополнением к тестам, написанным людьми. Высокоуровневые семантические гарантии тестирования на основе свойств хорошо дополняют процесс разработки. По нашим наблюдениям, языковые модели (LLM) особенно хорошо умеют по контексту — имени функции, строке документации, тому, как функцию вызывают другие части системы, и так далее — определять свойства, которые должны выполняться для конкретного блока кода. Благодаря этому LLM могут эффективно писать качественные тесты на основе свойств.
В дальнейшем мы считаем применение LLM к тестированию и поиску багов важным направлением исследований. Это особенно важно на фоне того, что LLM становятся всё сильнее в эксплуатации уязвимостей: нужно опережать атакующих, которые используют модели для эксплуатации.
Хотя в этой работе мы не фокусировались на автоматической генерации патчей, это очевидное направление для дальнейших исследований. Если удастся почти полностью специфицировать свойства корректности для фрагмента кода, исправлять баги станет заметно проще. Мы считаем, что в ближайшем будущем LLM смогут эффективно предлагать качественные патчи, которые мейнтейнерам действительно будет иметь смысл рассмотреть.
Читайте больше про работу с агентами:
Почему AI-агенты ломаются на длинных задачах — и как обвязка помогает им дописывать приложения
Вышла Qwen3-Coder-Next: модель с открытыми весами для кодинг-агентов

Продолжить тему применения ИИ в тестировании можно на двух бесплатных уроках OTUS. На занятиях разберут, где ИИ действительно помогает находить и исправлять проблемы в тестах, а где создаёт новые риски. Заодно можно познакомиться с преподавателями, посмотреть на формат обучения и задать вопросы по своим задачам.
16 июня, 20:00. «ИИ в автотестах: помощник или угроза?». Записаться
18 июня, 20:00. «Тесты, которые чинят себя сами: практика ИИ в UI-тестировании». Записаться
Полный список бесплатных уроков июня смотрите в дайджесте.
