Введение: Зачем программисту тесты и почему именно Pytest?
Если вы пишете код дольше пары недель, вам наверняка знакомо это чувство. Ваш проект, который вчера работал идеально, сегодня после пары «незначительных» правок ведет себя непредсказуемо. Вы чините одно — ломается другое. Каждое изменение превращается в ручную проверку всего приложения, а рефакторинг вызывает тихий ужас. Код становится похож на хрупкий карточный домик, где тронешь одну карту — и всё рухнет.
Это не ваша вина. Это естественный этап роста любого проекта. И чтобы перейти от «кода, который страшно трогать» к «надежной и стабильной системе», профессиональные разработчики используют один мощный инструмент — автоматизированное тестирование.
Тесты — это не скучная формальность, а ваша личная страховка. Это набор небольших программ, которые автоматически проверяют, что каждая часть вашего кода работает именно так, как вы от неё ожидаете. Они дают вам:
Уверенность. Вы можете смело вносить изменения и проводить рефакторинг, зная, что если что-то пойдет не так, тесты вас предупредят.
Скорость. Вместо того чтобы часами вручную кликать по интерфейсу или проверять вывод в консоли, вы запускаете одну команду и за секунды получаете полный отчет о здоровье вашего проекта.
Качество. Написание тестов заставляет лучше продумывать архитектуру и писать более чистый и предсказуемый код.
Хорошо, тесты — это важно. Но с чего начать?
В мире Python существует несколько инструментов для тестирования, но стандартом де-факто для большинства разработчиков стал Pytest. Встроенный в Python модуль unittest тоже справляется со своей задачей, но Pytest завоевал любовь сообщества по нескольким веским причинам, особенно важным для начинающих:
Простота и лаконичность. Для проверки вам не нужно запоминать специальные методы вроде
self.assertEqual()илиself.assertTrue(). Вы просто используете стандартное ключевое слово Python —assert. Ваш тестassert add(2, 2) == 4выглядит как простое и понятное утверждение.Минимум шаблоного кода. Pytest не требует от вас создавать классы для каждого набора тестов. Простая функция — это уже готовый тест. Это снижает порог входа и позволяет сосредоточиться на логике проверки, а не на структуре.
Мощные возможности «из коробки». Такие вещи, как подготовка данных для тестов (фикстуры) и запуск одного теста с разными наборами данных (параметризация), в Pytest реализованы невероятно элегантно и интуитивно.
2. Подготовка к работе: Установка и первый запуск
Теория — это хорошо, но давайте сразу перейдем к практике. Лучший способ понять Pytest — это написать и запустить свой первый тест. Мы сделаем это буквально за пять шагов.
Шаг 1: Установка
Первым делом установим Pytest. Как и большинство библиотек Python, он устанавливается одной командой в терминале. Откройте ваш терминал или командную строку и введите:
pip install pytest
Готово! Pytest установлен и готов к работе.
Шаг 2: Создание структуры проекта
Теперь создадим рабочее пространство. Pytest очень умен и может сам находить тесты, если придерживаться простых правил именования.
Создайте папку для нашего проекта, назовем ее pytest_project. Внутри этой папки создайте два файла:
functions.py— здесь будет жить код, который мы собираемся тестировать.test_functions.py— а здесь мы напишем тесты для этого кода.
Важное правило: Pytest автоматически ищет файлы, имена которых начинаются с test_ или заканчиваются на _test.py. Внутри этих файлов он ищет функции, имена которых начинаются с test_. Поэтому такое именование — не просто прихоть, а соглашение, которое позволяет инструменту работать.
Ваша структура должна выглядеть так:
pytest_project/
├── functions.py
└── test_functions.py
Шаг 3: Наша первая функция
Откройте файл functions.py и напишем в нем что-нибудь предельно простое, чтобы сосредоточиться на самом тесте. Например, функцию сложения двух чисел:
# functions.py
def add(x, y):
"""Эта функция складывает два числа."""
return x + y
Шаг 4: Наш первый тест
Теперь самое интересное. Откройте файл test_functions.py. Нам нужно сделать две вещи: импортировать нашу функцию add и написать тестовую функцию, которая проверит её работу.
# test_functions.py
# Импортируем функцию, которую хотим протестировать
from functions import add
# Объявляем тестовую функцию
def test_add():
"""
Проверяем, что функция add() правильно складывает два числа.
"""
# Используем assert, чтобы проверить результат
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(10, -5) == 5
Посмотрите, насколько читаемым получился тест. Он буквально говорит: «Результат вызова add(2, 3) должен быть равен 5». Никаких сложных конструкций, только assert и простое условие.
Шаг 5: Запуск!
Теперь вернитесь в терминал. Убедитесь, что вы находитесь внутри папки pytest_project. Если вы создавали ее на рабочем столе, команда может выглядеть так: cd Desktop/pytest_project.
А теперь просто выполните команду:
pytest
Если всё сделано правильно, вы увидите примерно такой вывод:
============================= test session starts ==============================
platform linux -- Python 3.10.6, pytest-7.1.2, pluggy-1.0.0
rootdir: /path/to/your/pytest_project
collected 1 item
test_functions.py . [100%]
============================== 1 passed in 0.01s ===============================
Зеленая надпись 1 passed — это тот самый момент, ради которого все затевалось. Она означает, что Pytest нашел наш тестовый файл, запустил функцию test_add, выполнил все три проверки assert, и все они оказались истинными. Наша функция add работает корректно.
Поздравляю, вы только что написали и запустили свой первый автоматизированный тест! В следующей главе мы разберем, что происходит, когда тест проваливается, и как читать сообщения об ошибках.
3. Как читать ошибки: Что делать, если тест упал?
Зеленая надпись PASSED — это приятно, но настоящую пользу тесты приносят, когда они падают. Проваленный тест — это не проблема, это ваш верный сторож, который только что поймал баг до того, как его заметили пользователи. Ваша главная задача — научиться понимать, что он вам сообщает.
Давайте намеренно сломаем наш тест, чтобы посмотреть, что произойдет.
Шаг 1: Создаем ошибку
Откройте файл test_functions.py и измените одну из проверок на заведомо неверную. Например, давайте утверждать, что 2 + 2 = 5.
# test_functions.py
from functions import add
def test_add():
"""
Проверяем, что функция add() правильно складывает два числа.
"""
assert add(2, 3) == 5
assert add(2, 2) == 5 # <-- Вот здесь мы намеренно ошиблись
assert add(10, -5) == 5
Сохраните файл и снова запустите Pytest в терминале:
pytest
На этот раз результат будет другим. Вместо зеленого цвета вы увидите много красного текста. Не пугайтесь, это ваш новый лучший друг — отчет об ошибке.
============================= test session starts ==============================
...
collected 1 item
test_functions.py F [100%]
=================================== FAILURES ===================================
___________________________________ test_add ___________________________________
def test_add():
"""
Проверяем, что функция add() правильно складывает два числа.
"""
assert add(2, 3) == 5
> assert add(2, 2) == 5
E assert 4 == 5
E + where 4 = add(2, 2)
test_functions.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_functions.py::test_add - assert 4 == 5
============================== 1 failed in 0.03s ===============================
Шаг 2: Разбираем отчет об ошибке
Этот текст может выглядеть пугающе, но на самом деле он дает вам всю необходимую информацию, как навигатор, ведущий прямо к месту аварии. Давайте разберем его по частям.
=================================== FAILURES ===================================
Это главный заголовок, который говорит: «Внимание, что-то пошло не так».___________________________________ test_add ___________________________________
Pytest сообщает, в какой именно тестовой функции произошла ошибка. В нашем случае этоtest_add. Если у вас будет 100 тестов, это поможет сразу сузить область поиска.> assert add(2, 2) == 5
Это самая важная часть. Pytest показывает точную строку кода, которая провалила проверку. Символ>указывает именно на нее.E assert 4 == 5
Здесь происходит магия Pytest. Он не просто говорит, чтоassertне сработал. Он показывает, почему он не сработал. Он вычислил левую часть выражения (add(2, 2)превратилось в4) и показал, что4не равно5. БукваEв начале означает "Error" (Ошибка).-
test_functions.py:9: AssertionError
Это техническая сводка:test_functions.py: файл, где произошла ошибка.9: номер строки в этом файле.AssertionError: тип исключения, которое было вызвано.
============================== 1 failed ... ==============================
Итоговая сводка. Один тест провалился.
Что делать дальше?
Теперь, когда вы "прочитали" ошибку, у вас есть четкий план действий:
Посмотрите на проваленный
assert:assert 4 == 5.Оцените ситуацию: Функция вернула
4, а мы ожидали5.-
Задайте себе вопрос: Где ошибка — в моем коде или в моем тесте?
Возможно, ошибка в тесте: Мы знаем, что 2 + 2 = 4. Значит, наш тест ожидал неверный результат. Мы должны исправить тест.
Возможно, ошибка в коде: Если бы функция
addвнезапно начала возвращать4на ввод(2, 3), то проблема была бы в самой функции, и чинить нужно было бы ее.
В нашем случае ошибка в тесте. Давайте исправим ее, вернув assert add(2, 2) == 4, и снова запустим pytest. Вы опять увидите заветную зеленую надпись 1 passed.
Вы только что прошли полный цикл: написали тест -> увидели, как он падает -> прочитали ошибку -> исправили -> увидели, как он проходит. Это и есть основа тестирования. Теперь вы готовы к более мощным возможностям Pytest.
4. Фикстуры: Готовим данные для тестов
По мере того как ваши тесты становятся сложнее, вы начнете замечать повторения. Например, для проверки нескольких функций вам может понадобиться один и тот же объект: словарь с данными пользователя, экземпляр класса или подключение к тестовой базе данных. Создавать эти данные заново в каждой тестовой функции — плохая практика. Код становится громоздким, а если данные нужно изменить, придется править их в десятке мест.
Здесь на помощь приходят фикстуры.
Представьте, что вы шеф-повар, и вам нужно приготовить три разных блюда, для каждого из которых нужны нарезанные овощи. Вы же не будете для каждого блюда заново мыть и нарезать одни и те же овощи? Вы сделаете это один раз, сложите в миску и будете брать оттуда по мере необходимости.
Фикстура в Pytest — это и есть такая «миска с подготовленными ингредиентами». Это функция, которая подготавливает данные или состояние до того, как тест будет выполнен, и передает их в тест.
Пример: от повторения к элегантности
Давайте добавим в наш файл functions.py новую функцию, которая работает со словарем, представляющим пользователя.
# functions.py
def add(x, y):
"""Эта функция складывает два числа."""
return x + y
def get_user_full_name(user_data):
"""Возвращает полное имя пользователя из словаря."""
first_name = user_data.get("first_name", "")
last_name = user_data.get("last_name", "")
return f"{first_name} {last_name}".strip()
Теперь напишем для нее пару тестов.
Плохой путь (с повторением):
Без фикстур нам бы пришлось создавать словарь user в каждой тестовой функции.
# test_functions.py
# ... предыдущий тест для add ...
def test_get_user_full_name():
# Создаем данные для теста
user = {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com"
}
assert get_user_full_name(user) == "John Doe"
def test_user_has_email():
# Снова создаем те же самые данные!
user = {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com"
}
assert "email" in user
Видите проблему? Словарь user дублируется. Если мы захотим добавить в него поле, придется менять его в двух местах. А если таких тестов будет двадцать?
Хороший путь (с фикстурой):
Давайте перепишем это с использованием фикстуры. Для этого нам понадобится импортировать pytest.
# test_functions.py
import pytest
from functions import add, get_user_full_name
# ... тест для add ...
# ШАГ 1: Создаем фикстуру
@pytest.fixture
def sample_user_data():
"""Фикстура, которая возвращает словарь с данными пользователя."""
return {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com"
}
# ШАГ 2: Используем фикстуру в тестах
def test_get_user_full_name_with_fixture(sample_user_data):
# Pytest автоматически вызовет фикстуру и передаст ее результат
# в аргумент sample_user_data
assert get_user_full_name(sample_user_data) == "Jane Doe"
def test_user_has_email_with_fixture(sample_user_data):
assert "email" in sample_user_data
Давайте разберем, что произошло:
Мы создали функцию
sample_user_data()и украсили ее декоратором@pytest.fixture. Это превратило ее в фикстуру.Эта функция создает и возвращает наш словарь с данными пользователя.
Теперь наши тестовые функции (
test_get_user_full_name_with_fixtureиtest_user_has_email_with_fixture) принимают аргумент с именем, в точности совпадающим с именем нашей фикстуры —sample_user_data.
Когда Pytest видит это, он выполняет магию:
Находит фикстуру с таким же именем.
Запускает ее код (создает и возвращает словарь).
Передает возвращенное значение в тестовую функцию в качестве аргумента.
Теперь наши данные централизованы. Если нам понадобится изменить структуру пользователя, мы сделаем это только в одном месте — в фикстуре. Тесты стали чище, короче и гораздо проще в поддержке.
Фикстуры — это фундаментальный инструмент в Pytest. Они позволяют отделить подготовку данных от логики самого теста, что делает ваш тестовый код профессиональным и масштабируемым.
5. Один тест для разных сценариев (Параметризация)
Представьте, что вы написали функцию, логика которой зависит от входных данных. Например, функция для определения категории возраста (ребенок, подросток, взрослый). Чтобы быть уверенным в ее работе, нужно проверить несколько сценариев:
Возраст 5 лет (должен быть "ребенок").
Возраст 17 лет (должен быть "подросток").
Возраст 25 лет (должен быть "взрослый").
И особенно важно проверить пограничные значения: 12, 13, 17, 18.
Ваша первая мысль может быть: "Нужно написать по одной тестовой функции на каждый случай". Но тогда ваш тестовый файл разрастется от однотипного, повторяющегося кода. Это долго, неудобно и сложно поддерживать.
К счастью, у Pytest есть на этот случай суперспособность — параметризация. Она позволяет запустить одну и ту же тестовую функцию несколько раз с разными наборами данных.
Как это работает?
Давайте вернемся к нашей простой функции add и протестируем ее с разными типами чисел, используя параметризацию. Для этого нам понадобится декоратор @pytest.mark.parametrize.
Откройте test_functions.py и добавьте новый тест:
# test_functions.py
import pytest
from functions import add, get_user_full_name
# ... предыдущие тесты и фикстура ...
# Создаем список тестовых сценариев
# Каждый кортеж - это один набор данных: (аргумент_1, аргумент_2, ожидаемый_результат)
test_cases = [
(1, 2, 3), # Обычное сложение
(-1, -1, -2), # Сложение отрицательных чисел
(5, 0, 5), # Сложение с нулем
(-1, 1, 0), # Противоположные числа
(3.5, 2.5, 6.0) # Сложение чисел с плавающей точкой
]
@pytest.mark.parametrize("a, b, expected", test_cases)
def test_add_parametrized(a, b, expected):
"""Проверяем функцию add() с разными наборами данных."""
assert add(a, b) == expected
Давайте разберем эту конструкцию:
-
@pytest.mark.parametrize(argnames, argvalues): Это сам декоратор. Он принимает два главных аргумента.argnames(имена аргументов):"a, b, expected"— это строка, в которой через запятую перечислены имена переменных, которые мы будем использовать. Эти имена станут аргументами нашей тестовой функции.argvalues(значения аргументов):test_cases— это список, где каждый элемент представляет один тестовый запуск. В нашем случае это список кортежей. Значения внутри каждого кортежа ((1, 2, 3)) по порядку присваиваются именам из строкиargnames(a=1,b=2,expected=3).
def test_add_parametrized(a, b, expected):: Обратите внимание, что наша тестовая функция теперь принимает в качестве аргументов те самые имена, которые мы указали в декораторе.assert add(a, b) == expected: Логика теста осталась прежней, но теперь она работает с переменными, которые Pytest подставляет для каждого запуска.
Что происходит при запуске?
Когда вы запустите pytest в терминале, он увидит декоратор parametrize и поймет, что test_add_parametrized — это не один тест, а целых пять! Он последовательно выполнит эту функцию для каждой строки из списка test_cases.
Чтобы увидеть это в деталях, запустите pytest с флагом -v (verbose, подробный режим):
pytest -v
Вывод будет выглядеть примерно так:
============================= test session starts ==============================
...
collected 8 items
test_functions.py::test_add PASSED [ 12%]
test_functions.py::test_get_user_full_name_with_fixture PASSED [ 25%]
test_functions.py::test_user_has_email_with_fixture PASSED [ 37%]
test_functions.py::test_add_parametrized[1-2-3] PASSED [ 50%]
test_functions.py::test_add_parametrized[-1--1--2] PASSED [ 62%]
test_functions.py::test_add_parametrized[5-0-5] PASSED [ 75%]
test_functions.py::test_add_parametrized[-1-1-0] PASSED [ 87%]
test_functions.py::test_add_parametrized[3.5-2.5-6.0] PASSED [100%]
============================== 8 passed in 0.04s ===============================
Посмотрите, Pytest сам сгенерировал понятные имена для каждого запуска, например, test_add_parametrized[1-2-3]. Если один из этих сценариев упадет, вы точно будете знать, на каких именно данных произошла ошибка.
6. Проверяем ошибки: Тестирование исключений
Иногда правильное поведение функции — это выдать ошибку. Например, если мы пытаемся разделить число на ноль, наша программа должна сгенерировать исключение ZeroDivisionError. Если она этого не сделает или вернет странный результат, это баг. Тесты должны проверять и такое поведение.
Давайте добавим в functions.py функцию для деления:
# functions.py
# ... (код предыдущих функций) ...
def divide(a, b):
"""Делит число a на b."""
if b == 0:
raise ValueError("Деление на ноль невозможно")
return a / b
Примечание: мы могли бы позволить Python самому вызвать ZeroDivisionError, но явное возбуждение ValueError — хороший пример для теста.
Теперь, как нам проверить, что при b = 0 функция действительно вызывает ValueError? Для этого существует контекстный менеджер pytest.raises.
Добавьте этот тест в test_functions.py:
# test_functions.py
# ...
from functions import divide
def test_divide_by_zero_raises_error():
"""Проверяем, что деление на ноль вызывает ValueError."""
with pytest.raises(ValueError):
divide(10, 0)
Как это работает:
Блок with pytest.raises(ValueError): — это своего рода "ловушка" для исключений.
Если код внутри этого блока (
divide(10, 0)) вызывает указанное исключение (ValueError), ловушка срабатывает, и тест считается пройденным.Если код не вызывает никакого исключения или вызывает другое исключение, тест провалится.
Этот прием гарантирует, что ваш код правильно обрабатывает ошибочные ситуации.
7. Пропуск тестов и ожидаемые падения
Иногда тесты временно не работают или неактуальны. Pytest позволяет их элегантно обработать, а не удалять или комментировать.
@pytest.mark.skip — Пропустить тест
Используйте этот маркер, если функция еще не реализована или тест зависит от внешних условий, которые сейчас не выполняются.
@pytest.mark.skip(reason="Эта функция еще в разработке")
def test_new_feature():
# Код теста для новой, еще не готовой функции
assert False
При запуске pytest этот тест не будет выполнен, а в отчете вы увидите SKIPPED. Это гораздо информативнее, чем просто закомментированный код.
@pytest.mark.xfail — Ожидать падения
Это очень полезно при подходе TDD (Test-Driven Development). Вы нашли баг, написали тест, который его воспроизводит (и который, очевидно, падает), но исправление бага откладывается. Чтобы этот падающий тест не "красил" весь ваш отчет в красный цвет, вы помечаете его как xfail (expected fail).
@pytest.e.mark.xfail(reason="Известный баг с точностью float, тикет #123")
def test_float_precision_bug():
assert (0.1 + 0.2) == 0.3 # Этот тест упадет из-за особенностей float
Pytest запустит этот тест, и если он упадет, как и ожидалось, в отчете будет пометка XFAIL. А вот если однажды (после исправления бага) тест внезапно пройдет, Pytest сообщит вам об этом (XPASS), и это будет сигналом, что маркер xfail можно убирать.
8. Группировка тестов с помощью маркеров
Когда тестов становится много, хочется запускать не все сразу, а только определенные группы. Например, "быстрые" тесты для частых проверок и "медленные" (работающие с сетью или БД), которые запускаются реже.
Pytest позволяет создавать свои собственные маркеры-теги.
# test_functions.py
# ...
@pytest.mark.math
def test_add():
# ...
@pytest.mark.math
def test_add_parametrized(a, b, expected):
# ...
@pytest.mark.user_profile
def test_get_user_full_name_with_fixture(sample_user_data):
# ...
@pytest.mark.user_profile
def test_user_has_email_with_fixture(sample_user_data):
# ...
Теперь вы можете запускать тесты выборочно из командной строки:
# Запустить только тесты, связанные с математикой
pytest -m math
# Запустить только тесты, связанные с профилем пользователя
pytest -m user_profile
# Запустить все, КРОМЕ математических
pytest -m "not math"
Хорошая практика: чтобы Pytest не выдавал предупреждений о неизвестных маркерах, зарегистрируйте их. Создайте в корневой папке проекта (pytest_project) файл pytest.ini со следующим содержимым:
[pytest]
markers =
math: тесты для математических операций
user_profile: тесты для функционала профиля пользователя
Эти инструменты помогут вам поддерживать порядок даже в большом наборе тестов и сделают вашу работу с ними гибкой и эффективной.
Параметризация — это невероятно мощный инструмент. Он делает ваш тестовый код:
DRY (Don't Repeat Yourself): Вы не повторяете одну и ту же логику теста.
Читаемым: Все тестовые сценарии собраны в одном месте в виде таблицы данных.
Расширяемым: Добавить новый тест-кейс — значит просто добавить новый кортеж в список.
7. Домашнее задание
Теория усваивается лучше всего, когда подкреплена практикой. Ниже вы найдете пять задач разной сложности. Они помогут вам закрепить все, что мы прошли: от простого assert до фикстур и параметризации.
Как это работает: Каждая задача скрыта под спойлером. Нажмите на название, чтобы раскрыть условие и код для тестирования. Постарайтесь решить задачу самостоятельно, прежде чем двигаться к следующей. Удачи!
Задача 1: Простая валидация пароля
Код для тестирования:
Скопируйте эту функцию в ваш файл functions.py.
def is_valid_password(password: str) -> bool:
"""
Проверяет, соответствует ли пароль минимальным требованиям по длине.
Требования: длина пароля должна быть не менее 8 символов.
"""
return len(password) >= 8
Задание:
Напишите тесты для функции is_valid_password. Вам нужно проверить как минимум три сценария:
Пароль корректной длины (например, 10 символов).
Пароль слишком короткий (например, 5 символов).
Пароль на граничной длине (ровно 8 символов).
Используйте простые assert для проверки True или False.
Задача 2: Определение возрастной группы (с параметризацией)
Код для тестирования:
Добавьте эту функцию в functions.py.
def get_age_group(age: int) -> str:
"""
Возвращает возрастную группу по возрасту.
- До 13 лет: "ребенок"
- От 13 до 18 лет: "подросток"
- Старше 18 лет: "взрослый"
"""
if age < 13:
return "ребенок"
elif 13 <= age < 18:
return "подросток"
else:
return "взрослый"
Задание:
Напишите один параметризованный тест, который проверяет работу функции get_age_group.
Ключевая цель: Обязательно протестируйте пограничные значения — возраст, на котором меняется категория.
Пример сценариев для проверки: (12, "ребенок"), (13, "подросток"), (17, "подросток"), (18, "взрослый").
Задача 3: Расчет стоимости корзины (с фикстурой)
Код для тестирования:
Добавьте эту функцию в functions.py.
def calculate_cart_total(cart_items: list[dict]) -> float:
"""
Рассчитывает общую стоимость товаров в корзине.
Каждый товар - это словарь с ключами "name", "price", "quantity".
"""
total_cost = 0.0
for item in cart_items:
total_cost += item["price"] * item["quantity"]
return total_cost
Задание:
Создайте фикстуру, которая будет возвращать тестовый набор данных — список словарей, имитирующий корзину с 2-3 товарами.
-
Напишите как минимум два теста, которые используют эту фикстуру:
Первый тест проверяет, что функция
calculate_cart_totalправильно считает итоговую сумму.Второй тест проверяет что-то еще, используя те же данные (например, что в корзине есть определенный товар).
Задача 4: Доступ к элементу словаря (тестирование исключений)
Код для тестирования:
Добавьте эту функцию в functions.py.
def get_value_from_dict(data_dict: dict, key: str):
"""
Возвращает значение из словаря по ключу.
Если ключа нет, Python по умолчанию вызовет исключение KeyError.
"""
return data_dict[key]
Задание:
Напишите два теста для функции get_value_from_dict:
Успешный сценарий: Проверьте, что функция возвращает правильное значение, если ключ существует в словаре.
Сценарий с ошибкой: Проверьте, что функция вызывает исключение
KeyError, если попытаться получить доступ к несуществующему ключу. Используйте для этогоpytest.raises.
Задача 5: Управление кошельком (объединяем все вместе)
Код для тестирования:
Это будет не просто функция, а целый класс. Добавьте его в functions.py.
class InsufficientFundsError(Exception):
"""Исключение, вызываемое при нехватке средств."""
pass
class Wallet:
def __init__(self, initial_balance: float = 0.0):
if initial_balance < 0:
raise ValueError("Начальный баланс не может быть отрицательным.")
self.balance = initial_balance
def deposit(self, amount: float):
if amount <= 0:
raise ValueError("Сумма пополнения должна быть положительной.")
self.balance += amount
def withdraw(self, amount: float):
if amount <= 0:
raise ValueError("Сумма снятия должна быть положительной.")
if amount > self.balance:
raise InsufficientFundsError("Недостаточно средств на счете.")
self.balance -= amount
Задание:
Протестируйте класс Wallet, применив полученные знания.
Создайте фикстуру
wallet, которая будет возвращать экземпляр классаWalletс начальным балансом (например, 20).Напишите тест для успешного пополнения счета (
deposit).Напишите тест для успешного снятия средств (
withdraw).Напишите тест, который проверяет, что при попытке снять больше денег, чем есть на счете, вызывается исключение
InsufficientFundsError.(По желанию) Напишите параметризованный тест, который проверяет, что
__init__,depositиwithdrawвызываютValueErrorпри передаче отрицательных или нулевых значений.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Уверен, у вас все получится. Вперед, к практике!