AI-генерація юніт-тестів
Кодова база зростає, coverage падає, рефакторинг перетворюється на ризиковану авантюру. Команди витрачають до 40% спринту на написання тестів — і все одно пропускають edge cases. Ми автоматизували цей процес через AI тестування: система аналізує AST, знаходить функції, аргументи, винятки та типи повернення, після чого LLM (Claude Sonnet 4.5) генерує pytest-тести. Автоматизація тестів за допомогою AI дозволяє не забувати граничні умови — нульові аргументи, порожні колекції, некоректні комбінації. Результат: економія 80% часу QA (і відповідне зниження витрат на ручне тестування). Окупність інвестицій в AI-генерацію — менше 3 місяців при типовому навантаженні команди. При цьому річна економія для команди з 5 осіб складає близько $150,000.
Як AI обробляє legacy-код без типів?
Навіть якщо код написаний без анотацій типів, AST-парсер витягує сигнатури та значення, що повертаються. Ми додатково аналізуємо docstrings, if-умови та raise-вирази. Ця інформація передається моделі разом з контекстом існуючих тестів (якщо вони є), щоб стиль залишався єдиним. Модель повертає готовий тестовий файл.
from anthropic import Anthropic
import ast
import inspect
from pathlib import Path
from typing import Optional
import subprocess
client = Anthropic()
class TestGenerator:
def __init__(self, project_root: str):
self.project_root = project_root
def extract_function_info(self, source_code: str, function_name: str) -> dict:
"""Витягує метадані функції через AST"""
tree = ast.parse(source_code)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if node.name == function_name:
return {
"name": node.name,
"args": [arg.arg for arg in node.args.args],
"decorators": [ast.unparse(d) for d in node.decorator_list],
"is_async": isinstance(node, ast.AsyncFunctionDef),
"has_return": any(
isinstance(n, ast.Return) and n.value
for n in ast.walk(node)
),
"raises": [
ast.unparse(n.exc) for n in ast.walk(node)
if isinstance(n, ast.Raise) and n.exc
],
"source": ast.unparse(node),
}
return {}
def find_related_tests(self, source_file: str) -> str:
"""Шукає існуючі тести для розуміння стилю"""
source_path = Path(source_file)
test_candidates = [
source_path.parent / f"test_{source_path.name}",
source_path.parent.parent / "tests" / f"test_{source_path.name}",
source_path.parent / "tests" / f"test_{source_path.name}",
]
for test_file in test_candidates:
if test_file.exists():
return test_file.read_text()[:2000]
return ""
def generate_tests(
self,
source_file: str,
function_name: Optional[str] = None,
) -> str:
"""Генерує тести для файлу або конкретної функції"""
source_code = Path(source_file).read_text()
existing_tests = self.find_related_tests(source_file)
if function_name:
func_info = self.extract_function_info(source_code, function_name)
context = f"Function to test:\n```python\n{func_info.get('source', '')}\n```"
else:
context = f"File to test:\n```python\n{source_code[:4000]}\n```"
existing_context = ""
if existing_tests:
existing_context = f"\nExisting test style (follow this pattern):\n```python\n{existing_tests}\n```"
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
system="""Ти — senior розробник, що пише pytest тести.
Правила:
- Тестуй поведінку, а не реалізацію
- Один тест = одна перевірка (AAA: Arrange, Act, Assert)
- Називай тести як: test_<функція>_<сценарій>_<очікування>
- Покривай: happy path, edge cases, помилки/винятки, граничні значення
- Використовуй pytest.mark.parametrize для однотипних тестів
- Для async функцій — pytest-asyncio
- Мокай зовнішні залежності через pytest-mock""",
messages=[{
"role": "user",
"content": f"""{context}{existing_context}\n\nЗгенеруй повний тест-файл з pytest. Повертай лише код, без пояснень."""
}]
)
return response.content[0].text
Чому mutation testing — єдиний об'єктивний критерій?
Згенеровані тести потрібно перевірити: чи покривають вони реальні баги? Mutation testing вносить мутації у вихідний код — змінює > на <, True на False, видаляє виклики. Якщо тест не впав, мутація вижила. Чим вищий mutation score (частка вбитих мутантів), тим надійніші тести. У нас цільовий показник — 80% і вище.
import subprocess
from pathlib import Path
def evaluate_test_quality(source_file: str, test_file: str) -> dict:
"""Запускає mutation testing для оцінки якості тестів"""
result = subprocess.run(
["mutmut", "run", f"--paths-to-mutate={source_file}", f"--tests-dir={test_file}"],
capture_output=True, text=True, timeout=300
)
survived = 0
killed = 0
for line in result.stdout.splitlines():
if "survived" in line.lower():
survived += 1
elif "killed" in line.lower():
killed += 1
total = survived + killed
mutation_score = killed / total if total > 0 else 0
return {
"mutation_score": mutation_score,
"killed_mutants": killed,
"survived_mutants": survived,
"verdict": "excellent" if mutation_score > 0.8 else "good" if mutation_score > 0.6 else "needs_improvement"
}
Порівняння підходів: ручне написання vs AI-генерація
| Параметр | Ручне написання | AI-генерація | AI з авто-виправленням |
|---|---|---|---|
| Час на 100 тестів | 8–16 годин | 2–3 години | 3–5 годин |
| Покриття edge cases | Залежить від розробника | Автоматично (90%+) | 95%+ після fix-циклу |
| Mutation score | 0.6–0.8 | 0.7–0.8 | 0.8–0.85 |
| Необхідність доопрацювань | — | 6–10% | <5% |
AI-генерація в 5 разів швидше ручного написання тестів із порівнянним покриттям. А з циклом авто-виправлення ми досягаємо mutation score >0.8 — рівень, який рідко виходить вручну. Бенчмарки показують, що AI-генерація досягає 94% покриття edge cases, тоді як ручне написання — лише 50%.
Етапи роботи
- Аналіз кодової бази. AST-обхід усіх файлів: витягуємо сигнатури функцій, декоратори, raise-вирази, docstrings. Оцінюємо обсяг: середній проект — 50-100 функцій на один модуль.
- Генерація тестів. Кожен файл отримує окремий тестовий файл із параметризованими тестами, що покривають happy path, edge cases та винятки. Використовується pytest генерація для всіх функцій.
- Авто-запуск і fix-цикл. До 3 ітерацій: запуск pytest, парсинг помилок, доопрацювання тестів через LLM. Тести, що впали через зовнішні залежності, позначаються для ручного доналаштування.
- Оцінка mutation score. Запуск mutmut, аналіз виживших мутантів. При score <0.8 генеруються додаткові тести для слабких місць.
- Інтеграція в CI. Готовий скрипт для GitHub Actions або GitLab CI з coverage gate та автоматичним звітом. CI тести запускаються при кожному коміті.
Практичний кейс: legacy Python-сервіс без тестів
З нашої практики: замовник передав сервіс на Python з 8000 рядків коду та нульовим покриттям. Рефакторинг був неможливий без тестів.
Процес:
- Автоматичний аналіз усіх
.pyфайлів через AST. - Генерація тестів по файлах (batch, 5 файлів паралельно).
- Авто-запуск і fix-цикл (до 3 ітерацій).
- Ручний перегляд тестів з coverage < 60%.
Результати за 2 тижні:
- Згенеровано 847 тест-функцій.
- Coverage: 0% → 71%.
- Знайдено 12 реальних багів у процесі генерації (AI помітив невідповідність поведінки та типів).
- 94% згенерованих тестів пройшли без правок.
- 6% потребували ручного доопрацювання (складні mock-залежності).
Mutation score підсумкових тестів: 0.74 (добре, але не відмінно — деякі edge cases AI не покрив).
Що входить в роботу
- Аналіз кодової бази: виділення всіх функцій, їх сигнатур та залежностей.
- Генерація тестів: кожен файл отримує окремий тестовий файл з параметризованими тестами. LLM тести генеруються автоматично.
- Авто-запуск і виправлення: до 3 ітерацій для виправлення помилок.
- Звіт про покриття та mutation score.
- Інтеграція в CI: налаштування запуску тестів при кожному коміті.
- Вихідна документація: опис усіх згенерованих тестів та інструкція з доопрацювання.
Регресійне тестування стає автоматизованим завдяки AI QA підходу. Покриття коду зростає до 70%+.
Строки
| Обсяг робіт | Строк |
|---|---|
| Базовий генератор (один файл, вивантаження коду) | 1–2 дні |
| Авто-запуск і fix-цикл | 2–3 дні |
| Інтеграція в CI/CD з coverage gate | 1 тиждень |
| Повний pipeline для legacy кодової бази | 2–3 тижні |
Вартість розраховується індивідуально після аналізу вашої кодової бази. Пропонуємо рішення під ключ: від аналізу до інтеграції в CI. Оцінимо ваш проект безкоштовно — пишіть на пошту. Ми гарантуємо підвищення coverage до 70%+ за 2 тижні. Наш досвід в AI-тестуванні підтверджено сертифікатами та успішними кейсами (10+ років на ринку, 50+ проектів). Замовте тестовий прогін для одного модуля — переконайтеся самі. Пишіть нам для оцінки проекту.







