Зачем промты для тестов?
Написание тестов — критически важная часть разработки, но часто она отнимает много времени. Особенно когда нужно покрыть 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и viewproduct_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
Комментарии