Skip to content

Отработка навыков работы с pytest

Цель:
Научиться использовать pytest для написания тестов в соответствии с концепцией Test-Driven Development (TDD).
Задачи:
1. Понять основные принципы TDD.
2. Написать тесты до реализации кода.
3. Использовать фикстуры, параметризацию и другие возможности pytest.

Теоретическая часть

Что такое TDD?

Test-Driven Development (TDD) — это подход к разработке, при котором: 1. Сначала пишется тест для новой функциональности. 2. Затем пишется минимальный код, чтобы тест прошел. 3. Проводится рефакторинг кода для улучшения структуры.

Основные возможности pytest

  • Фикстуры (@pytest.fixture) для переиспользуемых ресурсов.
  • Параметризация тестов (@pytest.mark.parametrize).
  • Проверки через assert.

Фикстуры (@pytest.fixture) для переиспользуемых ресурсов

Что это такое? Фикстура — это специальная функция, которая помогает подготовить данные или ресурсы перед выполнением теста и/или очистить их после завершения теста. Например, если вам нужно создать объект базы данных или файл перед каждым тестом, вы можете использовать фикстуру.

Пример:

Представим, что у нас есть функция add(a, b), которую мы хотим протестировать. Для этого нам нужен объект калькулятора.

# calculator.py
class Calculator:
    def add(self, a, b):
        return a + b

# test_calculator.py
import pytest
from calculator import Calculator

@pytest.fixture
def calculator():
    """Фикстура, которая создает объект калькулятора"""
    return Calculator()

def test_add(calculator):
    result = calculator.add(2, 3)
    assert result == 5

Как это работает? - Функция calculator помечена как фикстура с помощью декоратора @pytest.fixture. - Когда мы указываем calculator в качестве параметра тестовой функции test_add, pytest автоматически вызывает эту фикстуру и передает её результат (объект Calculator) в тест. - После завершения теста фикстура может быть автоматически удалена (например, если бы она создавала временные файлы).

Параметризация тестов (@pytest.mark.parametrize)

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

Пример:

Тестируем функцию multiply(a, b) на разных наборах данных.

# math_operations.py
def multiply(a, b):
    return a * b

# test_math_operations.py
import pytest
from math_operations import multiply

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 6),   # Первый случай: 2 * 3 = 6
    (5, 4, 20),  # Второй случай: 5 * 4 = 20
    (0, 10, 0),  # Третий случай: 0 * 10 = 0
])
def test_multiply(a, b, expected):
    result = multiply(a, b)
    assert result == expected

Как это работает? - Декоратор @pytest.mark.parametrize принимает два аргумента: - Список имён параметров ("a, b, expected"). - Список кортежей с данными для этих параметров. - Pytest автоматически запустит тест test_multiply три раза, каждый раз подставляя новые значения из списка.

Проверки через assert

Что это такое? assert — это ключевое слово Python, которое используется для проверки условий. Если условие ложно, тест падает с ошибкой. В pytest оно используется для сравнения фактического результата с ожидаемым.

Пример:

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

# utils.py
def is_even(number):
    return number % 2 == 0

# test_utils.py
import pytest
from utils import is_even

def test_is_even_positive():
    assert is_even(4) == True  # Проверяем, что 4 — четное число

def test_is_even_negative():
    assert is_even(7) == False  # Проверяем, что 7 — нечетное число

Как это работает? - Если условие после assert истинно, тест проходит успешно. - Если условие ложно, тест завершается с ошибкой, и pytest показывает подробную информацию о том, почему проверка не прошла.

Пример объединяющий теоретические сведения

# calculator.py
class Calculator:
    def add(self, a, b):
        return a + b

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

# test_calculator.py
import pytest
from calculator import Calculator

@pytest.fixture
def calculator():
    """Фикстура для создания объекта калькулятора"""
    return Calculator()

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),  # 2 + 3 = 5
    (5, 4, 9),  # 5 + 4 = 9
    (0, 0, 0),  # 0 + 0 = 0
])
def test_add(calculator, a, b, expected):
    """Тестируем метод add()"""
    result = calculator.add(a, b)
    assert result == expected

@pytest.mark.parametrize("a, b, expected", [
    (5, 3, 2),  # 5 - 3 = 2
    (10, 5, 5), # 10 - 5 = 5
    (0, 0, 0),  # 0 - 0 = 0
])
def test_subtract(calculator, a, b, expected):
    """Тестируем метод subtract()"""
    result = calculator.subtract(a, b)
    assert result == expected

Итог - Фикстуры помогают избежать дублирования кода для подготовки данных. - Параметризация делает тесты более универсальными и покрывает больше случаев. - assert — простой и понятный способ проверять условия.

Пример: Калькулятор (TDD-подход)

Шаг 1: Написание теста

Создайте файл test_calculator.py:

def test_add():
    from calculator import add
    assert add(2, 3) == 5

Шаг 2: Запустите тест (он упадет)

pytest test_calculator.py

Ошибка: модуль calculator не существует.

Шаг 3: Напишите минимальную реализацию

Создайте calculator.py:

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

Шаг 4: Запустите тест снова

Тест должен пройти.

Практические задания

Задание 1: Проверка палиндрома (TDD)

Требования:
Функция is_palindrome(s: str) -> bool должна возвращать True, если строка является палиндромом.

Шаги:
1. Напишите тесты для случаев: - Палиндром без пробелов (например, "radar"). - Не палиндром ("hello"). - Палиндром с пробелами ("A man a plan a canal Panama"). 2. Запустите тесты (они упадут). 3. Реализуйте функцию. 4. Проверьте, что тесты проходят.

@pytest.mark.parametrize("s, expected", [
    ("radar", True),
    ("hello", False),
    ("A man a plan a canal Panama", True),
])
def test_is_palindrome(s, expected):
    assert is_palindrome(s) == expected

Задание 2: Подсчет слов в строке

Требования:
Функция count_words(s: str) -> int возвращает количество слов в строке. Слово — последовательность символов без пробелов.

Шаги:
1. Напишите тесты для: - Пустой строки. - Строки с одним словом. - Строки с несколькими словами и пробелами. 2. Реализуйте функцию после написания тестов.

Задание 3: Валидация email

Требования:
Функция is_valid_email(email: str) -> bool проверяет: - Наличие символа @. - Наличие домена (часть после @ содержит точку). - Верхний регистр переводить в нижний.

Примеры валидных email:
user@example.com, john.doe@domain.co

Примеры невалидных email:
user@, john.doe@domain

Шаги:
1. Создайте параметризованный тест. 2. Реализуйте функцию.


Самостоятельная практика

Задача 1: Сумма четных чисел

Напишите функцию sum_even(numbers: list) -> int, которая возвращает сумму всех четных чисел в списке.
Примеры:
- Для [1, 2, 3, 4] результат 6. - Для [] результат 0.

Задача 2: Проверка возраста

Функция is_adult(age: int) -> bool должна возвращать True, если возраст ≥ 18.
Тесты:
- Проверьте граничные значения (17, 18, 19).


Примечания

  • Для запуска тестов используйте pytest -v.
  • Всегда начинайте с написания тестов!
  • Фиксируйте ошибки и улучшайте код постепенно.