Каков алгоритм написания тестов для функции в Python?

Ответ

Процесс написания тестов для функции строится вокруг стандартного паттерна Arrange-Act-Assert (AAA) или Given-When-Then.

Общий алгоритм:

  1. Определение сценариев: Проанализировать функцию и определить, что нужно протестировать.

    • Позитивные сценарии: Работа с корректными, ожидаемыми данными.
    • Негативные сценарии: Реакция на некорректные данные (неверный тип, формат).
    • Граничные случаи: Проверка на крайних значениях (пустые строки, 0, None, максимальные значения).
  2. Написание теста для каждого сценария по структуре AAA:

    • Arrange (Подготовка): Создать все необходимые условия и входные данные для теста. Инициализировать объекты, подготовить моки (mocks).
    • Act (Действие): Вызвать тестируемую функцию с подготовленными данными.
    • Assert (Проверка): Сравнить полученный результат с ожидаемым. Убедиться, что состояние системы изменилось так, как предполагалось, или что было вызвано нужное исключение.

Пример с pytest для функции add:

# Функция, которую мы тестируем
def add(a, b):
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("Оба аргумента должны быть числами")
    return a + b

# --- Тесты ---
import pytest

def test_add_positive_numbers():
    # Arrange (Подготовка)
    num1 = 5
    num2 = 10
    expected_result = 15

    # Act (Действие)
    actual_result = add(num1, num2)

    # Assert (Проверка)
    assert actual_result == expected_result

def test_add_negative_numbers():
    # AAA в одну строку для простых случаев
    assert add(-2, -3) == -5

def test_add_with_zero():
    assert add(10, 0) == 10

def test_add_raises_type_error_for_string():
    # Для проверки исключений используется контекстный менеджер
    with pytest.raises(TypeError):
        # Act & Assert происходят здесь
        add("2", 3)

Ответ 18+ 🔞

Давай разберём эту тему, но без занудства, а как есть. Смотри, есть у тебя функция, и ты хочешь убедиться, что она не накосячит в продакшене. Как к этому подступиться? А подступаться надо с умом, по чёткому плану, а не как мартышлюшка с гранатой.

Вот смотри, алгоритм такой, что хоть на стенке пиши:

1. Прикинь, где она может наебнуться. Это самый важный этап, ёпта. Не надо сразу писать код. Сядь, посмотри на функцию и подумай: "А что, если ей передать не число, а строку? А что, если пустой список? А если передать None?" Вот эти сценарии и надо выписывать. Позитивные (когда всё ок), негативные (когда всё хуёво) и граничные (когда на самом краю, типа пустая строка или ноль).

2. Для каждого такого сценария пишешь отдельный тест по шаблону "Подготовил-Сделал-Проверил". Это и есть тот самый Arrange-Act-Assert (AAA).

  • Arrange (Подготовка): Ты тут как режиссёр перед съёмкой. Расставляешь декорации — создаёшь нужные переменные, подсовываешь моки (это такие заглушки, которые притворяются другими частями системы, чтобы не дергать базу данных или API просто так).
  • Act (Действие): Кричишь "Мотор!" и запускаешь свою функцию с этими подготовленными данными.
  • Assert (Проверка): Смотришь, что получилось в кадре, и сравниваешь с тем, что должно было быть. Совпало? Отлично. Нет? Пиздец, надо переснимать (то есть чинить код).

Вот смотри на живом примере. Есть у нас функция add, которая складывает два числа. Проще некуда, но и тут можно накосячить.

# Функция, которую мы тестируем
def add(a, b):
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("Оба аргумента должны быть числами")
    return a + b

А теперь пишем на неё тесты. Смотри, как это выглядит в pytest:

import pytest

# Тест 1: Проверяем, что с нормальными числами всё ок
def test_add_positive_numbers():
    # Arrange (Подготовка)
    num1 = 5
    num2 = 10
    expected_result = 15

    # Act (Действие)
    actual_result = add(num1, num2)

    # Assert (Проверка)
    assert actual_result == expected_result  # Если тут не 15, тест упадёт с криком

# Тест 2: Можно и покороче, если сценарий простой
def test_add_negative_numbers():
    # AAA в одну строку для простых случаев
    assert add(-2, -3) == -5

# Тест 3: Граничный случай — ноль
def test_add_with_zero():
    assert add(10, 0) == 10

# Тест 4: А вот это важно! Проверяем, что на хуйню функция реагирует правильно
def test_add_raises_type_error_for_string():
    # Для проверки исключений используется контекстный менеджер
    with pytest.raises(TypeError):
        # Act & Assert происходят здесь: если исключение НЕ вылетело — тест провален
        add("2", 3)  # Пытаемся сложить строку и число

Вот и весь сказ, Колян. Главное — не лениться покрывать эти самые сценарии. Потому что если ты этого не сделаешь, это сделает пользователь, и потом тебе придётся объяснять, почему всё накрылось медным тазом. А доверия к тебе после такого будет, блядь, ноль.