Тестирование: как проверять что код работает
Тесты — это автоматические проверки что код делает то что должен. Без них: каждое изменение может сломать что-то другое, и ты не узнаешь об этом пока пользователь не пожалуется.
Зачем тесты вайб-кодеру
Без тестов: добавил новую функцию → сломал старую → узнал от пользователя → стыдно.
С тестами: добавил новую функцию → pytest → видишь что сломалось → правишь сразу.
ИИ пишет тесты хорошо. Попросил — получил. Запускать умеет pytest одной командой.
Минимальный пример
Есть функция:
# utils.py
def calculate_discount(price: float, percent: int) -> float:
if percent < 0 or percent > 100:
raise ValueError("Скидка должна быть от 0 до 100")
return price * (1 - percent / 100)Тест к ней:
# test_utils.py
import pytest
from utils import calculate_discount
def test_normal_discount():
assert calculate_discount(1000, 10) == 900.0
def test_zero_discount():
assert calculate_discount(1000, 0) == 1000.0
def test_full_discount():
assert calculate_discount(1000, 100) == 0.0
def test_invalid_discount_raises_error():
with pytest.raises(ValueError):
calculate_discount(1000, -10)
with pytest.raises(ValueError):
calculate_discount(1000, 150)Запуск:
pip install pytest
pytest test_utils.py -vВывод:
test_utils.py::test_normal_discount PASSED
test_utils.py::test_zero_discount PASSED
test_utils.py::test_full_discount PASSED
test_utils.py::test_invalid_discount_raises_error PASSEDКак попросить ИИ написать тесты
Самый простой промпт:
Напиши тесты pytest для этих функций:
[вставь код функций]
Покрой:
- нормальные случаи (правильные входные данные)
- граничные значения (ноль, максимум, минимум)
- случаи ошибок (неверные входные данные)Для более сложного кода:
Напиши тесты для класса [название].
Используй pytest.
Замокай внешние зависимости (базу данных, HTTP-запросы) через unittest.mock.
Покрой минимум 80% кода.Мокирование: тестируй без реальных API
Когда функция обращается к внешнему сервису — тест не должен делать реальные запросы (медленно, платно, зависит от интернета).
from unittest.mock import patch, MagicMock
def get_weather(city: str) -> dict:
import requests
response = requests.get(f"https://api.weather.com/v1/{city}")
return response.json()
# Тест: мокируем HTTP-запрос
def test_get_weather():
mock_response = MagicMock()
mock_response.json.return_value = {"temp": 20, "city": "Moscow"}
with patch("requests.get", return_value=mock_response):
result = get_weather("Moscow")
assert result["temp"] == 20
assert result["city"] == "Moscow"Функция вызывается с реальным кодом, но requests.get заменён на мок — никаких реальных запросов.
Структура тестов в проекте
my-project/
├── bot.py
├── utils.py
├── db.py
└── tests/
├── __init__.py
├── test_utils.py
├── test_db.py
└── conftest.py # общие фикстурыВ conftest.py — переиспользуемые объекты для тестов:
# tests/conftest.py
import pytest
import sqlite3
@pytest.fixture
def test_db():
"""Создаёт тестовую БД в памяти"""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
yield conn
conn.close()Используешь в тестах:
def test_add_user(test_db):
test_db.execute("INSERT INTO users (name) VALUES (?)", ("Андрей",))
cursor = test_db.execute("SELECT * FROM users")
assert cursor.fetchone()[1] == "Андрей"Запуск тестов
# Запустить все тесты
pytest
# С подробным выводом
pytest -v
# Конкретный файл
pytest tests/test_utils.py
# Конкретный тест
pytest tests/test_utils.py::test_normal_discount
# Показать покрытие кода
pip install pytest-cov
pytest --cov=. --cov-report=term-missingКогда тесты особенно важны
- Публичное API — сломаешь endpoint → все клиенты упадут
- Обработка платежей — ошибка в расчёте скидки → финансовые потери
- Авторизация — баг в проверке прав → доступ к чужим данным
- Бот с важной логикой — ошибка в FSM → пользователи зависнут
Для простых скриптов и ботов для личного использования — тесты опционально. Для продакшна с пользователями — обязательны.
TDD: пиши тесты до кода
Test-Driven Development (TDD) — сначала тест, потом код:
- Пишешь тест который ПРОВАЛИТСЯ (код ещё не написан)
- Пишешь минимальный код чтобы тест ПРОШЁЛ
- Рефакторишь код, тест проверяет что ничего не сломалось
С ИИ это работает так:
Мне нужна функция validate_email(email: str) -> bool.
Требования:
- Возвращает True если email валидный
- Возвращает False для: пустой строки, без @, без точки в домене
Сначала напиши тесты (они должны провалиться).
Потом напиши реализацию функции чтобы тесты прошли.Автоматический запуск при каждом push
Подключи GitHub Actions чтобы тесты запускались автоматически:
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- run: pip install -r requirements.txt pytest
- run: pytest -vТеперь при каждом push на GitHub — тесты запускаются. Если что-то упало — видишь сразу (GitHub покажет красный крестик).
Что дальше?
Тесты — это страховочная сетка. Добавь CI/CD через GitHub Actions чтобы тесты запускались при каждом push автоматически.