Skip to content

Тестирование в Python без использования библиотек

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

В Python существует множество библиотек для тестирования, таких как unittest, pytest и другие, которые упрощают написание тестов. Однако иногда полезно понять, как тестирование работает "под капотом", и попробовать написать тесты без использования сторонних библиотек.

Основная идея тестирования

Тестирование основывается на проверке того, что функция или метод возвращает ожидаемые результаты при определённых входных данных. Основной принцип прост:

  1. Задаём входные данные для функции.
  2. Определяем ожидаемый результат.
  3. Сравниваем результат выполнения функции с ожидаемым.
  4. Если результат совпадает с ожиданиями — тест пройден, иначе — тест провален.

Юнит Тестирование

Юнит тестирование — это метод тестирования программного обеспечения, где отдельные компоненты (юниты) исходного кода, такие как функции или методы, тестируются независимо, чтобы убедиться, что они работают должным образом. Цель юнит тестирования — изолировать каждый компонент и проверить, соответствует ли его поведение ожидаемому.

Простая проверка через assert

Python предоставляет встроенную конструкцию для проверки условий — assert (с анг. перевод "утверждение"). Она используется для проверки истинности выражения. Если выражение ложно, выполнение программы завершается с ошибкой AssertionError, сигнализирующей о провале теста.

Пример тестирования с использованием assert

Для начала создадим простую функцию, которую будем тестировать:

python
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

Теперь напишем тесты для этой функции:

python
def test_add():
    # Тест 1: Проверяем сложение положительных чисел
    assert add(2, 3) == 5, "Ошибка: 2 + 3 должно быть равно 5"
    
    # Тест 2: Проверяем сложение отрицательных чисел
    assert add(-1, -1) == -2, "Ошибка: -1 + -1 должно быть равно -2"
    
    # Тест 3: Проверяем сложение положительного и отрицательного числа
    assert add(5, -3) == 2, "Ошибка: 5 + (-3) должно быть равно 2"
    
    # Тест 4: Проверяем сложение с нулём
    assert add(0, 7) == 7, "Ошибка: 0 + 7 должно быть равно 7"

# Запускаем тест
test_add()
print("Все тесты пройдены успешно!")

Если все утверждения в тесте истинны, программа завершится без ошибок. Если хотя бы одно утверждение ложно, выполнение остановится с сообщением об ошибке.

Автоматизация нескольких тестов

Если тестов становится много, можно организовать их в виде списка или словаря, чтобы избежать дублирования кода. Например:

python
def test_add_with_cases():
    # Список тест-кейсов в формате (a, b, expected_result)
    test_cases = [
        (2, 3, 5),
        (-1, -1, -2),
        (5, -3, 2),
        (0, 7, 7),
        (10, 0, 10),
    ]
    
    for a, b, expected in test_cases:
        result = add(a, b)
        assert result == expected, f"Ошибка: {a} + {b} должно быть равно {expected}, а получилось {result}"
    
    print("Все тесты для add() пройдены успешно!")

# Запускаем тест
test_add_with_cases()

Теперь мы можем легко добавлять новые тест-кейсы, просто добавляя их в список.

Тестирование исключений

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

python
def divide(a, b):
    if b == 0:
        raise ValueError("Деление на ноль запрещено")
    return a / b

def test_divide():
    # Тест 1: Проверяем обычное деление
    assert divide(10, 2) == 5, "Ошибка: 10 / 2 должно быть равно 5"
    
    # Тест 2: Проверяем деление на 0
    try:
        divide(10, 0)
    except ValueError as e:
        assert str(e) == "Деление на ноль запрещено", f"Неверное сообщение об ошибке: {e}"
    else:
        assert False, "Ошибка: Ожидалось исключение ValueError при делении на 0"

    print("Все тесты для divide() пройдены успешно!")
# Запускаем тест
test_divide()

Здесь мы используем конструкцию try-except-else для проверки выбрасываемого исключения.

Организация тестов

Если в вашем проекте несколько функций, и вы хотите протестировать их все, можно организовать тесты в виде отдельных функций и запускать их последовательно. Например:

python
def run_tests():
    test_add()       # Тестируем функцию add
    test_divide()    # Тестируем функцию divide
    print("Все тесты пройдены успешно!")

# Запускаем все тесты
run_tests()

Улучшение вывода результатов

Для удобства можно добавить более подробный вывод результатов тестирования. Например:

python
def run_test(name, func):
    try:
        func()
        print(f"[PASS] {name}")
    except AssertionError as e:
        print(f"[FAIL] {name}: {e}")

def run_all_tests():
    run_test("test_add", test_add)
    run_test("test_divide", test_divide)

run_all_tests()

Теперь для каждого теста будет выводиться, прошёл он или провалился.

Более продвинутая организация

Для более сложных проектов можно использовать более структурированный подход. Например, можно создать класс для группировки тестов:

python


class TestCalculator:
    def test_add_with_cases(self):
        # Список тест-кейсов в формате (a, b, expected_result)
        test_cases = [
            (2, 3, 5),
            (-1, -1, -2),
            (5, -3, 2),
            (0, 7, 7),
            (10, 0, 10),
        ]
        
        for a, b, expected in test_cases:
            result = add(a, b)
            assert result == expected, f"Ошибка: {a} + {b} должно быть равно {expected}, а получилось {result}"
        
        print("Все тесты для add() пройдены успешно!")

    def test_divide(self):
        # Тест 1: Проверяем обычное деление
        assert divide(10, 2) == 5, "Ошибка: 10 / 2 должно быть равно 5"
        
        # Тест 2: Проверяем деление на 0
        try:
            divide(10, 0)
        except ValueError as e:
            assert str(e) == "Деление на ноль запрещено", f"Неверное сообщение об ошибке: {e}"
        else:
            assert False, "Ошибка: Ожидалось исключение ValueError при делении на 0"
        
        print("Все тесты для divide() пройдены успешно!")

# Запуск тестов (более структурированный способ)
test_suite = TestCalculator()
for attr in dir(test_suite):
    if attr.startswith('test_'):
        try:
            getattr(test_suite, attr)()
        except AssertionError as e:
            print(f"Тест '{attr}' провален: {e}")
            break # Можно продолжить тестирование или прервать
else:
    print("Все тесты пройдены успешно!")

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

Ограничения подхода без библиотек

Хотя тестирование без библиотек позволяет понять базовые принципы, у такого подхода есть ограничения:

  1. Отсутствие удобных инструментов: Библиотеки, такие как unittest или pytest, предоставляют множество функций для проверки различных условий, упрощают организацию тестов и выводят подробные отчёты.
  2. Нет поддержки фикстур: С тестовыми библиотеками можно легко создавать подготовительные данные (фикстуры) для каждого теста.
  3. Нет параллелизма: Современные библиотеки позволяют запускать тесты одновременно, что экономит время.
  4. Нет интеграции с инструментами разработки: Библиотеки чаще всего поддерживают интеграцию с IDE и CI/CD системами.

Заключение

Тестирование — неотъемлемая часть разработки качественного программного обеспечения. Тестирование без использования библиотек — это полезное упражнение, которое помогает понять основы тестирования и принципы работы с проверками. Если у вас небольшой проект или вы хотите разобраться в базовых принципах, тестирование с использованием assert и простых функций — отличный старт.

Упражнения

  1. Test_Absolute_Value: Напишите функцию calculate_absolute_value(number: int) -> int, которая вычисляет абсолютное значение числа. Создайте ручные тесты для положительных, отрицательных и нулевых входных данных.

  2. Test_Maximum_of_Two: Напишите функцию find_maximum(a: int, b: int) -> int, которая возвращает максимальное из двух чисел. Создайте ручные тесты для случаев, когда a > b, a < b и a == b.

  3. Test_String_Reversal: Напишите функцию reverse_string(input_string: str) -> str, которая переворачивает строку. Создайте ручные тесты для пустых строк, строк с одним символом и строк с несколькими символами.

  4. Test_List_Sum: Напишите функцию calculate_list_sum(numbers: list[int]) -> int, которая вычисляет сумму элементов списка целых чисел. Создайте ручные тесты для пустых списков, списков с положительными числами, списков с отрицательными числами и списков с и положительными, и отрицательными числами.

  5. Test_Leap_Year: Напишите функцию is_leap_year(year: int) -> bool, которая определяет, является ли год високосным. Создайте ручные тесты для обычных годов и високосных годов (например, 2000, 2004, 1900, 2003, 2100).

  6. Test_Factorial: Напишите функцию calculate_factorial(n: int) -> int, которая вычисляет факториал числа. Создайте ручные тесты для n = 0, n = 1, n = 5, и n = 10. Обратите внимание на граничные условия и обработку ошибок.

Contacts: teffal@mail.ru