Отработка навыков работы с 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. - Всегда начинайте с написания тестов!
- Фиксируйте ошибки и улучшайте код постепенно.