Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как пишешь тесты на Python
Тестирование — критическая часть разработки. Расскажу о лучших практиках с использованием pytest.
1. unittest vs pytest
# unittest (встроенный, объектно-ориентированный)
import unittest
class TestMath(unittest.TestCase):
def test_addition(self):
self.assertEqual(2 + 2, 4)
if __name__ == '__main__':
unittest.main()
# pytest (функциональный, более гибкий)
def test_addition():
assert 2 + 2 == 4
pytest лучше: проще синтаксис, лучше отчёты, встроенные fixtures, параметризованное тестирование.
2. Структура проекта
my_project/
├── src/
│ ├── calculator.py
│ └── database.py
├── tests/
│ ├── conftest.py
│ ├── test_calculator.py
│ └── test_database.py
└── pytest.ini
3. Простой пример
# src/calculator.py
def add(a: int, b: int) -> int:
return a + b
def divide(a: int, b: int) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# tests/test_calculator.py
import pytest
from src.calculator import add, divide
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
4. Fixtures — переиспользуемые данные
# conftest.py
import pytest
@pytest.fixture
def sample_user():
"""Пользователь для тестов."""
return {'id': 1, 'name': 'John', 'email': 'john@example.com'}
# tests/test_users.py
def test_get_user(sample_user):
assert sample_user['name'] == 'John'
assert sample_user['email'] == 'john@example.com'
5. Параметризованное тестирование
@pytest.mark.parametrize("input,expected", [
(2, "even"),
(3, "odd"),
(0, "even"),
])
def test_check_even_odd(input, expected):
result = check_even_odd(input)
assert result == expected
6. Mock и Patch
from unittest.mock import patch, MagicMock
@patch('src.users.requests.get')
def test_get_user_from_api(mock_get):
mock_response = MagicMock()
mock_response.json.return_value = {'id': 1, 'name': 'John'}
mock_get.return_value = mock_response
result = get_user_from_api(1)
assert result['name'] == 'John'
mock_get.assert_called_once()
7. Тестирование исключений
def test_division_error():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_invalid_input():
with pytest.raises(TypeError):
add("not a number", 5)
8. Fixtures с областями
@pytest.fixture(scope="session")
def database_connection():
"""Одна БД для всех тестов."""
conn = connect_db()
yield conn
conn.close()
@pytest.fixture(scope="function")
def fresh_user():
"""Новый пользователь для каждого теста."""
user = User(name="Test")
yield user
user.delete()
9. Тестирование API
from fastapi.testclient import TestClient
from src.api import app
client = TestClient(app)
def test_create_user():
response = client.post(
"/users",
json={"id": 1, "name": "John", "email": "john@example.com"}
)
assert response.status_code == 200
assert response.json()["name"] == "John"
def test_get_nonexistent_user():
response = client.get("/users/999")
assert response.status_code == 404
10. Лучшие практики
# Один тест = один случай
def test_login_with_valid_credentials():
result = login("user", "password")
assert result.success is True
def test_login_with_invalid_credentials():
with pytest.raises(AuthError):
login("user", "wrongpassword")
# Дескриптивные имена
def test_user_cannot_login_with_expired_token():
pass
# Given-When-Then паттерн
def test_user_deletion():
# Given
user = create_user(name="John")
# When
delete_user(user.id)
# Then
assert not user_exists(user.id)
11. Запуск тестов
pytest # Все тесты
pytest tests/test_calculator.py # Конкретный файл
pytest tests/test_calculator.py::test_add # Конкретный тест
pytest -v # Verbose
pytest -x # Стоп при первой ошибке
pytest --cov=src # Покрытие кода
12. Test Coverage
pip install pytest-cov
pytest --cov=src --cov-report=html
Тестирование в Python — инвестиция в стабильность. Используй pytest, пишешь тесты для критичных функций, стремись к coverage >= 80%.