Зачем тестировать Python-код
Тесты — это автоматизированная проверка того, что код работает правильно. В Python стандарт де-факто — pytest, который проще и мощнее unittest.
▸Преимущества pytest
Основы pytest
▸Простой тест
1# test_calculator.py2def add(a, b):3 return a + b45def test_add_positive():6 assert add(2, 3) == 578def test_add_negative():9 assert add(-1, -1) == -21011def test_add_zero():12 assert add(0, 0) == 0
▸Тесты с классами
1class TestCalculator:2 def test_add(self):3 assert add(2, 3) == 545 def test_multiply(self):6 assert multiply(2, 3) == 678 def test_divide(self):9 assert divide(6, 3) == 21011 def test_divide_by_zero(self):12 with pytest.raises(ZeroDivisionError):13 divide(1, 0)
▸Запуск тестов
1# Все тесты2pytest34# Конкретный файл5pytest test_calculator.py67# Конкретный тест8pytest test_calculator.py::test_add910# С отладкой11pytest -s1213# С подробным выводом14pytest -v1516# Только упавшие17pytest --tb=short
Fixtures
▸Базовые fixtures
1import pytest23@pytest.fixture4def sample_data():5 return {6 "users": [7 {"id": 1, "name": "Alice", "email": "alice@test.com"},8 {"id": 2, "name": "Bob", "email": "bob@test.com"}9 ]10 }1112def test_get_user(sample_data):13 user = sample_data["users"][0]14 assert user["name"] == "Alice"
▸Fixtures с параметрами
1@pytest.fixture(params=["sqlite", "postgresql"])2def database(request):3 if request.param == "sqlite":4 db = create_sqlite_db()5 else:6 db = create_postgresql_db()78 yield db910 db.cleanup()1112def test_query(database):13 result = database.query("SELECT * FROM users")14 assert len(result) > 0
▸Autouse fixtures
1@pytest.fixture(autouse=True)2def setup_logging():3 logging.basicConfig(level=logging.DEBUG)4 yield5 logging.shutdown()67# Этот fixture будет применяться ко ВСЕМ тестам в модуле
▸Scope fixtures
1@pytest.fixture(scope="session")2def browser():3 # Создаётся один раз на всю сессию тестов4 driver = webdriver.Chrome()5 yield driver6 driver.quit()78@pytest.fixture(scope="module")9def db_connection():10 # Создаётся один раз на модуль11 conn = create_connection()12 yield conn13 conn.close()1415@pytest.fixture(scope="function")16def clean_db(db_connection):17 # Создаётся для каждой функции18 db_connection.execute("DELETE FROM users")19 yield20 db_connection.execute("DELETE FROM users")
Параметризованные тесты
1@pytest.mark.parametrize("input,expected", [2 (1, 2),3 (2, 4),4 (3, 6),5 (0, 0),6 (-1, -2),7])8def test_double(input, expected):9 assert double(input) == expected1011# Параметризация с именами12@pytest.mark.parametrize("user", [13 pytest.param({"name": "Alice", "role": "admin"}, id="admin"),14 pytest.param({"name": "Bob", "role": "user"}, id="regular_user"),15])16def test_user_role(user):17 assert user["role"] in ["admin", "user", "moderator"]
Mocking
▸unittest.mock
1from unittest.mock import patch, MagicMock, AsyncMock23# Мокирование функции4@patch('myapp.services.external_api.call')5def test_with_mock(mock_call):6 mock_call.return_value = {"status": "ok"}7 result = my_function()8 assert result == "success"9 mock_call.assert_called_once()1011# Мокирование класса12def test_with_class_mock():13 with patch('myapp.db.Database') as MockDB:14 mock_db = MockDB.return_value15 mock_db.query.return_value = [{"id": 1}]16 result = get_users()17 assert len(result) == 1
▸pytest-mock
1def test_with_mocker(mocker):2 # mocker — встроенный fixture из pytest-mock3 mock_func = mocker.patch('myapp.utils.external_call')4 mock_func.return_value = "mocked"56 result = my_function()7 assert result == "mocked"8 mock_func.assert_called_once()
▸AsyncMock
1import pytest2from unittest.mock import AsyncMock34@pytest.mark.asyncio5async def test_async_function():6 mock_db = AsyncMock()7 mock_db.fetch_user.return_value = {"id": 1, "name": "Alice"}89 result = await get_user_async(1)1011 assert result["name"] == "Alice"12 mock_db.fetch_user.assert_called_once_with(1)
Тестирование исключений
1def test_division_by_zero():2 with pytest.raises(ZeroDivisionError, match="division by zero"):3 divide(1, 0)45def test_custom_exception():6 with pytest.raises(ValueError) as exc_info:7 validate_email("invalid-email")89 assert "Invalid email" in str(exc_info.value)
Плагины pytest
▸pytest-cov (покрытие кода)
1pytest --cov=myapp --cov-report=html
▸pytest-xdist (параллельные тесты)
1pytest -n auto # Использовать все ядра CPU
▸pytest-django
1@pytest.mark.django_db2def test_user_creation():3 user = User.objects.create(name="Test", email="test@test.com")4 assert user.pk is not None
Best Practices
▸Структура тестов
1tests/2├── unit/3│ ├── test_calculator.py4│ └── test_validator.py5├── integration/6│ ├── test_database.py7│ └── test_api.py8├── conftest.py # Общие fixtures9└── fixtures/10 └── data.json
▸Именование тестов
1# Плохо2def test_function(): pass34# Хорошо5def test_add_returns_sum_of_two_numbers(): pass6def test_divide_raises_error_on_zero_divisor(): pass7def test_user_creation_with_valid_data_succeeds(): pass
Заключение
pytest — стандарт тестирования в Python. Понимание fixtures, параметризации и mocking критически важно для написания качественных тестов. На собеседовании спрашивают про стратегию тестирования, разницу между unit и интеграционными тестами, и как тестировать сложные зависимости.