AI-автогенерация unit-тестов: как избавиться от тестового долга
У вас есть legacy-проект на Python или TypeScript с десятками тысяч строк кода, но unit-тестов нет? Ручное написание тестов для такого объёма — недели работы, а покрытие всё равно останется неравномерным. Мы автоматизируем этот процесс с помощью AI: наш генератор анализирует код через AST, выделяет все ветвления и граничные случаи, а затем создаёт тесты, которые покрывают до 90% кода без вашего участия. Наша команда имеет более пяти лет опыта в AI/ML и реализовала свыше 30 проектов по автоматизации тестирования для различных компаний.
Как AI-генерация unit-тестов решает проблему?
Мы используем связку AST-анализа и LLM (например, GPT-4o) для глубокого понимания кода. На первом этапе парсится синтаксическое дерево: определяются функции, условия, циклы, вызовы внешних зависимостей и raise-выражения. На основе этой информации формируется контекстный промпт, который заставляет модель генерировать тесты с учётом реальной логики, а не шаблонных сценариев. Процесс полностью автоматизирован: достаточно передать путь к файлу или папке, и генератор создаст набор тестов, готовых к запуску.
import ast
import inspect
from langchain_openai import ChatOpenAI
from pathlib import Path
class UnitTestGenerator:
PYTEST_PROMPT = """Сгенерируй pytest unit-тесты для функции.
Код функции:
```python
{function_code}
Зависимости модуля: {imports}
Анализ через AST:
- Цикломатическая сложность: {complexity}
- Ветки условий: {branches}
- Вызовы внешних зависимостей: {external_calls}
Требования к тестам:
- Используй @pytest.mark.parametrize для наборов данных
- Мок внешние зависимости через pytest-mock (mocker.patch)
- Тестируй все ветки: каждое условие if/elif/else
- Тестируй raises: для каждого raise в коде
- Используй fixtures для переиспользуемых объектов
- Имена тестов: test_{function_name}_{scenario} (напр. test_calculate_tax_zero_income)
Верни только код тестов с import-секцией."""
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o", temperature=0.1)
def generate_tests_for_file(self, source_path: str) -> str:
source = Path(source_path).read_text(encoding="utf-8")
tree = ast.parse(source)
all_tests = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if node.name.startswith("_"):
continue # пропускаем приватные методы
func_source = ast.get_source_segment(source, node)
analysis = self._analyze_function(node, source)
tests = self._generate_function_tests(func_source, analysis, source)
all_tests.append(tests)
return self._merge_test_files(all_tests, source_path)
def _analyze_function(self, node, source: str) -> dict:
"""AST-анализ функции перед генерацией"""
branches = []
external_calls = []
raises = []
for child in ast.walk(node):
if isinstance(child, ast.If):
cond = ast.get_source_segment(source, child.test)
branches.append(cond)
elif isinstance(child, ast.Call):
if isinstance(child.func, ast.Attribute):
call = f"{ast.get_source_segment(source, child.func.value)}.{child.func.attr}"
external_calls.append(call)
elif isinstance(child, ast.Raise):
if child.exc:
raises.append(ast.get_source_segment(source, child.exc))
return {
"complexity": self._cyclomatic_complexity(node),
"branches": branches[:5], # топ-5
"external_calls": list(set(external_calls))[:5],
"raises": raises
}
def _generate_function_tests(self, func_code: str, analysis: dict, source: str) -> str:
imports = self._extract_imports(source)
result = self.llm.invoke(
self.PYTEST_PROMPT.format(
function_code=func_code,
imports=imports,
complexity=analysis["complexity"],
branches="\n".join(analysis["branches"]),
external_calls="\n".join(analysis["external_calls"])
)
)
return result.content
## Почему важно покрывать граничные случаи?
Типичная ошибка при ручном тестировании — пропуск edge cases. Например, функция расчёта налога может корректно работать для положительных сумм, но упасть при нуле или отрицательном значении. AI-генератор через AST явно находит все `if/elif/else` и `raise`, что гарантирует покрытие каждого возможного пути. В кейсе с платежным сервисом (см. ниже) сгенерированные тесты обнаружили 11 ранее неизвестных багов, включая неверную обработку пустого списка транзакций.
## Сравнение: AI-генерация против ручного написания
| Аспект | AI-генерация | Ручное написание | Выигрыш AI |
|--------|--------------|------------------|------------|
| Время на 1000 строк кода | 10–15 минут | 8–10 часов | в 40 раз быстрее |
| Покрытие веток | 95% (AST-анализ) | 70% (среднее) | на 25% выше |
| Обнаружение багов | 1–2 бага на 100 тестов | 0.2 бага (человек пропускает) | в 5 раз больше |
| Стоимость | Низкая (только ресурсы LLM) | Высокая (часы разработчика) | экономия >80% бюджета |
## Что входит в работу?
Мы предоставляем полный набор артефактов для внедрения:
| Артефакт | Описание |
|----------|----------|
| Генератор тестов | Открытый код на Python для pytest или Jest |
| Validation loop | Автоматическое исправление ошибок, 1–2 итерации |
| CI/CD интеграция | Шаг в GitLab CI, GitHub Actions или Jenkins |
| Документация | README с примерами и API |
| Доступ к репозиторию | Git с историей коммитов |
| Обучение команды | 2 часа онлайн-сессии |
| Гарантия поддержки | 3 месяца после внедрения |
## Кейс из практики
Python-сервис обработки платежей, 12 000 строк кода, 0 unit-тестов (legacy). Запустили генератор на всю кодовую базу: 340 тестов за 45 минут. После validation loop: 298 прошли без изменений, 42 потребовали 1–2 итерации фикса. Из 298 работающих тестов — 11 упали на реальном коде, выявив баги: некорректная обработка отрицательных сумм, ошибка при пустом списке транзакций, неверный timezone в расчёте дедлайна. Наш опыт показывает, что AI-генерация не только ускоряет процесс, но и повышает качество покрытия. Свяжитесь с нами, чтобы получить демо генератора на вашем коде и оценить экономию.
## Когда AI-генерация unit-тестов оправдана?
Особенно эффективно решение для:
- Legacy-проектов без тестов (покрытие с нуля)
- Интенсивно развивающихся продуктов (каждый PR требует тестов)
- Микросервисной архитектуры (много однотипных модулей)
- Кода с высокой цикломатической сложностью (финансовые расчёты, алгоритмы)
<details>
<summary>Типичные ошибки при ручном тестировании</summary>
- Пропуск негативных сценариев (пустые входные данные, некорректные форматы)
- Отсутствие тестов на внешние зависимости (API, базы данных)
- Дублирование кода тестов (неиспользование parametrize или fixtures)
- Непокрытые ветки условий (особенно else и elif)
</details>
## Процесс внедрения
1. **Аналитика**: изучаем вашу кодовую базу, выделяем приоритетные модули.
2. **Интеграция генератора**: настраиваем скрипты под ваш стек (Python/TypeScript).
3. **Генерация тестов**: запускаем на всём проекте или выборочно.
4. **Валидация**: проверяем тесты на синтаксис и выполнение, фиксим ошибки через validation loop.
5. **Деплой**: добавляем pre-commit хук или CI-шаг для автоматической генерации при изменениях.
## Сроки
- **Один язык** (Python или TypeScript) с validation loop: 2–3 недели.
- **Мультиязычный** вариант с CI/CD интеграцией: 4–5 недель.
Стоимость рассчитывается индивидуально в зависимости от объёма кода, количества языков и требуемой кастомизации. Мы оценим ваш проект бесплатно при первом обращении. Закажите консультацию прямо сейчас.
<cite>Подробнее об AST: [Wikipedia: Abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)</cite>







