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-генерація unit-тестів у 40 разів швидша за ручне написання та забезпечує на 25% вище покриття гілок.
| Аспект | 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-генерація не тільки прискорює процес, але й підвищує якість покриття. Економія для цього кейсу склала близько $30,000 на рік на команді з трьох розробників. Зв'яжіться з нами, щоб отримати демо генератора на вашому коді та оцінити економію.
## Коли 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>
<cite>Джерело: внутрішня статистика компанії</cite>







