12 промтов для написания unit-тестов и интеграционных тестов

Зачем промты для тестов?

Написание тестов — критически важная часть разработки, но часто она отнимает много времени. Особенно когда нужно покрыть legacy-код или написать однотипные проверки для десятков функций. Промты для AI позволяют автоматизировать рутину: вы описываете, что должна делать функция, — и получаете готовый тест. Это не значит, что тесты не нужно ревьюить, но стартовый шаблон экономит часы. В этой подборке — 12 проверенных промтов, которые я использую в работе с pytest, Jest и unittest. Каждый промт — это готовая инструкция для AI, с примером и разбором.

Как использовать промты

Я предполагаю, что вы работаете с ChatGPT, Claude или аналогичной моделью. Скопируйте промт, вставьте в чат и адаптируйте под свой код. Важно: всегда указывайте язык программирования, фреймворк и вставляйте реальный код, который нужно тестировать. Чем больше контекста, тем точнее результат. Не поленитесь проверить сгенерированные тесты — AI может ошибаться в логике.

1. Базовый unit-тест для функции на pytest

Проблема: нужно быстро покрыть простую функцию, например, расчёт суммы заказа со скидкой.

Промт:

Напиши unit-тесты на pytest для функции calculate_discount(price: float, discount_percent: float) -> float, которая возвращает цену после скидки. Учитывай краевые случаи: discount_percent = 0, 100, больше 100 (должно выбрасывать ValueError), отрицательные цены. Используй параметризацию. Добавь doctring в тесты.

Пример использования:

Допустим, функция выглядит так:

def calculate_discount(price: float, discount_percent: float) -> float:
    if discount_percent < 0 or discount_percent > 100:
        raise ValueError("Discount must be between 0 and 100")
    if price < 0:
        raise ValueError("Price cannot be negative")
    return price * (1 - discount_percent / 100)

AI сгенерирует:

import pytest
from discount import calculate_discount

@pytest.mark.parametrize("price, discount, expected", [
    (100, 0, 100),
    (100, 10, 90),
    (100, 100, 0),
])
def test_calculate_discount_normal(price, discount, expected):
    assert calculate_discount(price, discount) == expected

def test_calculate_discount_invalid_discount():
    with pytest.raises(ValueError):
        calculate_discount(100, 101)

def test_calculate_discount_negative_price():
    with pytest.raises(ValueError):
        calculate_discount(-10, 50)

Вывод: получаем покрытие базовых случаев и проверку ошибок. Осталось только адаптировать импорт — и тест готов.

2. Тесты с фикстурами для класса (pytest)

Проблема: нужно протестировать класс, который требует подключения к временной базе данных или другим внешним зависимостям.

Промт:

Сгенерируй pytest-тесты для класса UserManager, который работает с базой данных через SQLAlchemy. Используй фикстуру db_session, которая создаёт in-memory SQLite базу. Протестируй методы create_user, get_user_by_email, delete_user. Проверь, что при создании возвращается объект User с id, а при удалении запись исчезает из БД.

Пример:

# models.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String, unique=True)
    name = Column(String)

# user_manager.py
class UserManager:
    def __init__(self, session):
        self.session = session

    def create_user(self, email: str, name: str) -> User:
        user = User(email=email, name=name)
        self.session.add(user)
        self.session.commit()
        return user

    def get_user_by_email(self, email: str) -> User | None:
        return self.session.query(User).filter_by(email=email).first()

    def delete_user(self, email: str) -> None:
        user = self.get_user_by_email(email)
        if user:
            self.session.delete(user)
            self.session.commit()

AI создаст conftest.py с фикстурой и тесты:

# conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base

@pytest.fixture
async def db_session():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    try:
        yield session
    finally:
        session.close()
        Base.metadata.drop_all(engine)
# test_user_manager.py
from user_manager import UserManager
from models import User

def test_create_user(db_session):
    manager = UserManager(db_session)
    user = manager.create_user("test@example.com", "Test")
    assert user.id is not None
    assert db_session.query(User).count() == 1

def test_get_user_by_email_found(db_session):
    manager = UserManager(db_session)
    manager.create_user("test@example.com", "Test")
    user = manager.get_user_by_email("test@example.com")
    assert user is not None
    assert user.name == "Test"

def test_delete_user(db_session):
    manager = UserManager(db_session)
    manager.create_user("test@example.com", "Test")
    manager.delete_user("test@example.com")
    assert manager.get_user_by_email("test@example.com") is None

Вывод: фикстуры изолируют тесты друг от друга, а промт помогает сразу получить рабочий шаблон с in-memory БД.

3. Интеграционный тест для REST API (pytest + httpx)

Проблема: нужно проверить взаимодействие нескольких компонентов (например, эндпоинт + бизнес-логика + БД) через реальный HTTP-запрос.

Промт:

Напиши интеграционные тесты для FastAPI приложения с эндпоинтом POST /users и GET /users/{id}. Используй httpx AsyncClient и фикстуру для временной БД. Проверь: создание пользователя возвращает 201, получение существующего — 200, получение несуществующего — 404. После каждого теста очищай БД.

Пример:

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class UserIn(BaseModel):
    email: str
    name: str

@app.post("/users", status_code=201)
async def create_user(user: UserIn):
    ...

@app.get("/users/{id}")
async def get_user(id: int):
    ...

AI сгенерирует тесты с использованием pytest-asyncio и httpx:

import pytest
import httpx
from main import app
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base

@pytest.fixture
async def client():
    async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
        yield client

@pytest.mark.asyncio
async def test_create_user(client):
    response = await client.post("/users", json={"email": "a@b.com", "name": "Alice"})
    assert response.status_code == 201
    assert response.json()["id"] == 1

@pytest.mark.asyncio
async def test_get_existing_user(client):
    await client.post("/users", json={"email": "a@b.com", "name": "Alice"})
    response = await client.get("/users/1")
    assert response.status_code == 200
    assert response.json()["name"] == "Alice"

@pytest.mark.asyncio
async def test_get_missing_user(client):
    response = await client.get("/users/999")
    assert response.status_code == 404

Вывод: таким образом тестируется весь стек — от маршрутизации до базы данных. Главное, чтобы в приложении была корректная настройка зависимостей для тестов.

4. Тесты с моками для внешних сервисов (unittest.mock)

Проблема: функция отправляет email через сторонний API, и в тестах не должно быть реальных вызовов.

Промт:

Сгенерируй unittest-тесты для функции send_welcome_email(user_email), которая вызывает внешний сервис email_service.send(). Используй unittest.mock.patch, чтобы подменить email_service.send. Проверь, что функция возвращает True при успешной отправке и False при исключении. Также проверь, что send вызывается с правильными аргументами.

Пример:

# email_sender.py
import email_service

def send_welcome_email(user_email: str) -> bool:
    try:
        email_service.send(user_email, "Welcome!")
        return True
    except Exception:
        return False

AI сгенерирует:

import unittest
from unittest.mock import patch
from email_sender import send_welcome_email

class TestSendWelcomeEmail(unittest.TestCase):
    @patch("email_sender.email_service.send")
    def test_success(self, mock_send):
        result = send_welcome_email("user@example.com")
        self.assertTrue(result)
        mock_send.assert_called_once_with("user@example.com", "Welcome!")

    @patch("email_sender.email_service.send")
    def test_failure(self, mock_send):
        mock_send.side_effect = Exception("SMTP error")
        result = send_welcome_email("user@example.com")
        self.assertFalse(result)

Вывод: моки изолируют тест от внешнего мира, делают его быстрым и детерминированным. Официальная документация по unittest.mock — https://docs.python.org/3/library/unittest.mock.html.

5. Параметризованные тесты для pytest

Проблема: одну и ту же логику нужно проверить на множестве входных данных, чтобы избежать дублирования тестов.

Промт:

Создай параметризованные pytest-тесты для функции is_valid_date(date_str: str) -> bool, которая проверяет дату в формате YYYY-MM-DD. Используй @pytest.mark.parametrize для списка из 10+ случаев: корректные даты, високосный год, неверные форматы, 31 февраля, несуществующие месяцы. Для каждого случая укажи ожидаемое значение.

Пример:

import pytest
from date_utils import is_valid_date

@pytest.mark.parametrize(
    "date_str, expected",
    [
        ("2023-01-01", True),
        ("2024-02-29", True),  # високосный
        ("2023-02-29", False),
        ("2023-13-01", False),
        ("2023-00-01", False),
        ("2023/01/01", False),
        ("2023-01-32", False),
        ("not-a-date", False),
    ]
)
def test_is_valid_date(date_str, expected):
    assert is_valid_date(date_str) == expected

Вывод: параметризация уменьшает количество кода и делает тесты нагляднее. Полезно комбинировать с pytest -k для выборочного запуска.

6. Тесты для асинхронного кода (pytest-asyncio)

Проблема: есть асинхронная функция, которая гоняет запросы к API, нужно проверить её логику.

Промт:

Напиши async-тесты с pytest-asyncio для функции fetch_user_data(user_id: int) -> dict, которая делает GET-запрос на https://api.example.com/users/{id} и возвращает JSON. Подмени httpx.Client.get, чтобы не делать реальный запрос. Проверь, что функция возвращает корректный словарь при статусе 200 и выбрасывает исключение при 404.

Пример:

# client.py
import httpx

async def fetch_user_data(user_id: int) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(f"https://api.example.com/users/{user_id}")
        response.raise_for_status()
        return response.json()

AI сгенерирует тест с моком для httpx.AsyncClient:

import pytest
import httpx
from client import fetch_user_data

@pytest.mark.asyncio
async def test_fetch_user_data_success(monkeypatch):
    class FakeResponse:
        def raise_for_status(self):
            pass
        def json(self):
            return {"id": 1, "name": "Alice"}

    async def fake_get(self, url):
        return FakeResponse()

    monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
    data = await fetch_user_data(1)
    assert data == {"id": 1, "name": "Alice"}

@pytest.mark.asyncio
async def test_fetch_user_data_404(monkeypatch):
    class FakeResponse:
        def raise_for_status(self):
            raise httpx.HTTPStatusError("Not Found", request=None, response=None)
        def json(self):
            return {}

    async def fake_get(self, url):
        return FakeResponse()

    monkeypatch.setattr(httpx.AsyncClient, "get", fake_get)
    with pytest.raises(httpx.HTTPStatusError):
        await fetch_user_data(999)

Вывод: тестирование асинхронного кода требует асинхронных фикстур и моков, но промт избавляет от рутины. Подробнее — в официальной документации pytest-asyncio.

7. Генерация тестов для React-компонента (Jest + React Testing Library)

Проблема: нужно проверить, что компонент корректно рендерится и обрабатывает события.

Промт:

Сгенерируй тесты на Jest и React Testing Library для компонента Button.tsx, который принимает пропсы label, onClick, disabled. Проверь: рендерится текст, клик вызывает callback, при disabled клик не срабатывает, и кнопка имеет атрибут disabled.

Пример:

// Button.tsx
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

export const Button = ({ label, onClick, disabled }: ButtonProps) => (
  <button onClick={onClick} disabled={disabled}>{label}</button>
);

AI сгенерирует:

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

test('renders label', () => {
  render(<Button label="Click me" onClick={() => {}} />);
  expect(screen.getByText('Click me')).toBeInTheDocument();
});

test('calls onClick when clicked', () => {
  const onClick = jest.fn();
  render(<Button label="Click me" onClick={onClick} />);
  fireEvent.click(screen.getByText('Click me'));
  expect(onClick).toHaveBeenCalledTimes(1);
});

test('does not call onClick when disabled', () => {
  const onClick = jest.fn();
  render(<Button label="Click me" onClick={onClick} disabled />);
  fireEvent.click(screen.getByText('Click me'));
  expect(onClick).not.toHaveBeenCalled();
  expect(screen.getByText('Click me')).toBeDisabled();
});

Вывод: этот промт экономит время на настройку тестового рендера. Обратите внимание, что AI часто забывает про jest.fn() — проверяйте.

8. Интеграционные тесты для Django (pytest-django)

Проблема: нужно протестировать взаимодействие модели и представления (view) в Django.

Промт:

Напиши pytest-django тесты для модели Product и view product_list. Используй фикстуру client и db. Проверь, что view возвращает 200, содержит список продуктов, и что создание продукта через ORM работает.

Пример:

# models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

# views.py
from django.views.generic import ListView

class ProductListView(ListView):
    model = Product
    template_name = "products/list.html"

AI сгенерирует:

import pytest
from products.models import Product

@pytest.mark.django_db
def test_product_list_view(client):
    Product.objects.create(name="Phone", price="999.99")
    response = client.get("/products/")
    assert response.status_code == 200
    assert "Phone" in str(response.content)

@pytest.mark.django_db
def test_create_product():
    product = Product.objects.create(name="Laptop", price="1999.99")
    assert product.id is not None
    assert Product.objects.count() == 1

Вывод: pytest-django предоставляет удобные фикстуры для доступа к тестовой БД. Официальная документация: https://pytest-django.readthedocs.io/

9. Тесты для FastAPI с зависимостями (override)

Проблема: эндпоинт использует зависимость, которая ходит в базу, и её нужно подменить в тестах.

Промт:

Сгенерируй тесты для FastAPI-приложения, где есть зависимость get_db, возвращающая сессию SQLAlchemy. Используй app.dependency_overrides[get_db], чтобы подменить на тестовую сессию (SQLite in-memory). Напиши тест для создания и получения данных.

Пример:

# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db
from models import User

app = FastAPI()

@app.post("/users")
def create_user(name: str, db: Session = Depends(get_db)):
    user = User(name=name)
    db.add(user)
    db.commit()
    return user

AI сгенерирует с override:

from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from main import app, get_db
from models import Base

engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
TestingSession = sessionmaker(bind=engine)

def override_get_db():
    try:
        db = TestingSession()
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)

def test_create_user():
    response = client.post("/users?name=Alice")
    assert response.status_code == 200
    assert response.json()["name"] == "Alice"

Вывод: dependency_overrides — стандартный способ изоляции зависимостей в FastAPI, описано в официальной документации: https://fastapi.tiangolo.com/advanced/testing-dependencies/

10. Генерация тестов для legacy-кода (unittest)

Проблема: большой старый модуль без тестов, нужно быстро добавить smoke-проверки.

Промт:

Сгенерируй unittest-тесты для legacy-функции process_data(data: list) -> dict, которая не имеет внешних зависимостей, но плохо документирована. Напиши три теста: на пустой список, на список с числами, на список со строками. Проверь, что функция всегда возвращает словарь с ключами 'count' и 'sum'. Не углубляйся в реализацию.

Пример:

# legacy.py
def process_data(data):
    # старый код, который не меняли 5 лет
    if not data:
        return {'count': 0, 'sum': 0}
    return {'count': len(data), 'sum': sum(data)}

AI сгенерирует:

import unittest
from legacy import process_data

class TestProcessData(unittest.TestCase):
    def test_empty_list(self):
        result = process_data([])
        self.assertEqual(result, {'count': 0, 'sum': 0})

    def test_ints(self):
        result = process_data([1, 2, 3])
        self.assertEqual(result, {'count': 3, 'sum': 6})

    def test_strings(self):
        result = process_data(['a', 'b'])
        self.assertEqual(result, {'count': 2, 'sum': 'ab'})  # это показывает странное поведение

Вывод: такие тесты фиксируют текущее поведение, а не ожидаемое. Это важно для рефакторинга legacy-кода.

11. Генерация тестов для pytest с coverage

Проблема: нужно проверить полноту покрытия кода тестами.

Промт:

Сгенерируй pytest-тесты для модуля calculator.py, который содержит функции add, subtract, multiply, divide. Требуется достичь покрытия не менее 90%. Используй pytest-cov для проверки. Определи, какие строки пропущены, и добавь недостающие тесты.

Пример:

# calculator.py
def add(a, b): return a + b
def subtract(a, b): return a - b
def multiply(a, b): return a * b
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

AI сгенерирует тесты, покрывающие все ветки:

import pytest
from calculator import add, subtract, multiply, divide

def test_add(): assert add(2, 2) == 4
def test_subtract(): assert subtract(5, 3) == 2
def test_multiply(): assert multiply(2, 3) == 6
def test_divide(): assert divide(10, 2) == 5

def test_divide_by_zero():
    with pytest.raises(ValueError):
        divide(10, 0)

Затем вы запускаете pytest --cov=calculator --cov-report=term-missing и видите, если какие-то строки не покрыты. Промт можно уточнить: «подсвети пропущенные строки».

Вывод: coverage — обязательный атрибут здорового проекта. Не гонитесь за 100%, но 80-90% — хорошая цель.

12. Генерация интеграционных тестов для микросервисной архитектуры (pytest + Docker)

Проблема: нужно проверить взаимодействие между сервисами, когда один вызывает другой по HTTP.

Промт:

Напиши интеграционные тесты для сервиса A, который при обращении к эндпоинту /status делает запрос к сервису B. Используй pytest и библиотеку requests. В тестах подними заглушку для сервиса B с помощью pytest-httpserver. Проверь, что сервис A возвращает 200, когда B доступен, и 503, когда B недоступен.

Пример:

# service_a.py
import requests
from flask import Flask

app = Flask(__name__)

@app.route("/status")
def status():
    try:
        response = requests.get("http://service-b:5000/health", timeout=2)
        return {"status": "ok", "b_status": response.json()}, 200
    except requests.exceptions.RequestException:
        return {"status": "degraded"}, 503

AI сгенерирует тест с pytest_httpserver:

import pytest
import requests
from pytest_httpserver import HTTPServer
from service_a import app

@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_status_ok(client, httpserver):
    httpserver.expect_request("/health").respond_with_json({"status": "up"})
    # Переопределяем URL в приложении (обычно через переменные окружения)
    import service_a
    service_a.B_URL = httpserver.url_for("/")
    response = client.get("/status")
    assert response.status_code == 200
    assert response.json["b_status"] == {"status": "up"}

def test_status_503(client, httpserver):
    httpserver.expect_request("/health").respond_with_error(500)
    import service_a
    service_a.B_URL = httpserver.url_for("/")
    response = client.get("/status")
    assert response.status_code == 503

Вывод: такой подход позволяет тестировать взаимодействие без разворачивания всей инфраструктуры. В CI можно использовать Testcontainers для поднятия реальных контейнеров.

Заключение

Промты — это не замена качественному тестированию, а способ ускорить создание тестовой базы. Из 12 примеров выше большинство генерирует рабочий код, но всегда проверяйте его на краевые случаи. Лучше всего работает связка: вы задаёте чёткий контекст, AI генерирует скелет, вы его дорабатываете под специфику проекта. Помните, что тесты — это код, который придётся поддерживать, поэтому пишите их так же качественно, как и основную логику.

Если вы хотите прокачаться в написании тестов — начните с этих промтов, адаптируйте под свои проекты и делитесь результатами. А если у вас есть свои проверенные промты — расскажите о них в комментариях!

Полезные ссылки:
- Документация pytest
- Официальный гайд по unittest
- Jest documentation
- FastAPI Testing

← Все статьи

Комментарии

Читайте также

Курс «Управление стрессом и устойчивость» 2026: практическое руководство по преодолению выгорания на современном рабочем месте

7 августа 2026

Asset tracking + ASI Biont: как подключить GPS-трекеры к ИИ-агенту через MQTT и REST API

7 августа 2026

Raspberry Pi Zero 2 W + ASI Biont: как превратить микрокомпьютер в умного IoT-помощника с AI-интеграцией

7 августа 2026

UART (any MCU) + ASI Biont: как подключить ESP32 к AI-агенту и автоматизировать IoT без ручной разработки

7 августа 2026

Управление GPU: почему простаивающие GPU — это новые приземлённые самолёты

7 августа 2026

DC motors (L298N, BTS7960) + ASI Biont: как AI превращает моторы в интеллектуальные системы

7 августа 2026

Cambridge IGCSE Economics (0455): Освойте экономику с помощью персонализированного обучения на основе ИИ

7 августа 2026

Микросервисная архитектура на ASI Biont: руководство backend-инженера по событийно-ориентированному проектированию и миграции монолита

7 августа 2026

Интеграция Banking APIs (any) с AI-агентом ASI Biont: автоматизируйте финансы без кода

7 августа 2026