Какие типы входных данных следует использовать при тестировании кода?

«Какие типы входных данных следует использовать при тестировании кода?» — вопрос из категории Тестирование, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Эффективное тестирование кода требует использования разнообразных входных данных для обеспечения надежности и корректности работы. Это помогает выявить ошибки, которые могут проявиться только в специфических условиях.

Важно проверять следующие категории входных данных:

  1. Типичные/Ожидаемые данные: Проверка базовой функциональности с данными, которые соответствуют обычным сценариям использования. Это подтверждает, что код работает как задумано в большинстве случаев.
  2. Граничные условия (Boundary Conditions): Тестирование на пределах допустимых значений. Это включает:
    • Пустые строки, списки, словари.
    • Нулевые значения.
    • Минимальные и максимальные допустимые значения (например, 0, sys.maxsize, float('inf')).
    • Один элемент в коллекции. Это помогает выявить ошибки "на один" (off-by-one errors) и проблемы с обработкой крайних случаев, которые часто являются источником багов.
  3. Некорректные/Невалидные данные: Проверка реакции кода на данные, которые не соответствуют ожидаемому формату или типу. Это включает:
    • Неверные типы данных (например, строка вместо числа).
    • Отрицательные числа, когда ожидаются положительные.
    • None, null значения.
    • Слишком длинные или короткие строки. Цель — убедиться, что код корректно обрабатывает ошибки, выбрасывает ожидаемые исключения или возвращает предсказуемые ошибочные состояния, предотвращая сбои.
  4. Случайные данные (Fuzzing): Генерация большого количества случайных или полуслучайных входных данных для выявления скрытых ошибок, уязвимостей или неожиданного поведения. Инструменты для фаззинга могут помочь обнаружить краши или зависания, которые трудно найти вручную.

Пример для функции деления на Python:

def divide(a: float, b: float) -> float:
    """Делит число a на b, выбрасывает ValueError при делении на ноль."""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# Тесты с различными типами входных данных с использованием pytest:
import pytest

def test_typical_cases():
    assert divide(4, 2) == 2.0
    assert divide(10, 5) == 2.0

def test_boundary_conditions():
    assert divide(0, 5) == 0.0 # Деление нуля
    assert divide(5, 1) == 5.0 # Деление на единицу
    assert divide(1000000, 2) == 500000.0 # Большие числа

def test_invalid_data():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(1, 0) # Деление на ноль должно вызвать ошибку

    # Пример с некорректным типом (хотя Python это обработает как TypeError до функции)
    # Если бы функция принимала str, но ожидала число, тест выглядел бы так:
    # with pytest.raises(TypeError):
    #     divide("abc", 2)

Для автоматизации и более глубокого тестирования рекомендуется использовать специализированные библиотеки, такие как pytest (с плагинами для параметризации тестов, например, @pytest.mark.parametrize) и hypothesis (для property-based testing и фаззинга).