AI-система автоматичної генерації unit-тестів по коду

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
AI-система автоматичної генерації unit-тестів по коду
Простий
від 1 дня до 3 днів
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

AI-автогенерація unit-тестів

Unit-тести пишуть усі, але мало хто робить це систематично — особливо для успадкованого коду, де немає тестів і незрозуміло з чого починати. AI-генератор створює pytest/Jest/JUnit тести безпосередньо з вихідного коду, аналізуючи логіку через AST та виявляючи сценарії, які людина пропустила б.

Python: генерація pytest через AST + LLM

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}

Вимоги до тестів:

  1. Використовуй @pytest.mark.parametrize для наборів даних
  2. Mock зовнішні залежності через pytest-mock (mocker.patch)
  3. Тестуй всі гілки: кожну умову if/elif/else
  4. Тестуй raises: для кожного raise в коді
  5. Використовуй fixtures для переважених об'єктів
  6. Імена тестів: 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

### TypeScript/Jest генерація

```python
    JEST_PROMPT = """Генеруй Jest unit-тести для TypeScript функції.

```typescript
{function_code}

Вимоги:

  • Використовуй describe/it блоки
  • jest.fn() для моків
  • beforeEach для setup
  • expect().toBe() / toEqual() / toThrow()
  • Тест має імпортувати тільки потрібне
  • Не використовуй any — тільки строга типізація в тестах

Покриття: успішні сценарії, граничні значення, помилки. Повернення тільки TypeScript код."""

async def generate_jest_tests(self, ts_function: str) -> str:
    result = await self.llm.ainvoke(
        self.JEST_PROMPT.format(function_code=ts_function)
    )
    return result.content

### Автоматичне виявлення проблем у сгенерованих тестах

Сгенеровані тести іноді мають синтаксичні помилки або неправильні assertions. Додаємо validation loop:

```python
import subprocess

class TestValidator:
    def validate_and_fix(self, test_code: str, source_file: str) -> str:
        """Запускає тести та фіксить помилки в циклі"""
        temp_test_file = "/tmp/test_generated.py"
        Path(temp_test_file).write_text(test_code)

        for attempt in range(3):
            result = subprocess.run(
                ["pytest", temp_test_file, "-x", "--tb=short",
                 f"--rootdir={Path(source_file).parent}"],
                capture_output=True, text=True, timeout=60
            )

            if result.returncode == 0:
                break

            # Фіксимо помилки через LLM
            fix_prompt = f"""Виправ pytest тести, які не проходять.

Тести:
{test_code}

Помилка:
{result.stdout[-2000:]}

Повернення виправлених тестів (тільки код)."""

            test_code = self.llm.invoke(fix_prompt).content
            Path(temp_test_file).write_text(test_code)

        return test_code

Інтеграція через pre-commit hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: ai-test-generator
        name: AI Unit Test Generator
        entry: python scripts/generate_tests.py
        language: python
        pass_filenames: true
        types: [python]
        stages: [push]  # тільки при push, не при кожному commit

Кейс: Python-сервіс обробки платежів, 12 000 рядків коду, 0 unit-тестів (legacy). Запустили генератор на всю кодову базу: 340 тестів за 45 хвилин. Після validation loop: 298 пройшли без змін, 42 потребували 1–2 ітерації фіксу. З 298 робочих тестів — 11 впали на реальному коді, виявивши баги: некоректна обробка від'ємних сум, помилка при порожному списку транзакцій, неправильна timezone в розрахунку дедлайну.

Строки: генератор для однієї мови (Python/TypeScript) з validation loop: 2–3 тижні; мультимовний з CI/CD інтеграцією: 4–5 тижнів.