- Что такое модульное тестирование
- Тестируем основную функциональность
- Тестируем дополнительную функциональность
Изученной информации уже достаточно для тестирования в повседневной практике разработки. Перед тем, как погружаться в более сложные темы и возможности Pytest, пройдем полный путь тестирования библиотеки. Мы поговорим об организации тестов, хороших и плохих практиках. Это поможет сформировать правильное отношение к тестированию в целом.
Что такое модульное тестирование
В этом уроке мы разберем основы модульного тестирования. Это тестирование направлено на проверку модулей программы в изоляции от всех остальных частей. Эти тесты обычно тестируют базовые конструкции языка: функции, модули, классы. Такие тесты не дают никаких гарантий работы всего приложения в целом, но хорошо помогают тогда, когда какой-то модуль программы имеет сложную логику.
Попробуем протестировать стек. Напомним, что стек представляет собой список элементов, организованных по принципу LIFO (Last In First Out). По такому принципу данные кладутся в стек в одном порядке, а извлекаются в обратном.
Как правило, сам стек используется для реализации алгоритмов. Он часто используется в низкоуровневом коде, например, внутри языков программирования или в операционных системах:
stack = [] # В Python стек реализован через список
not stack # Список пуст
# True
stack.append(1) # [1]
stack.append(2) # [1, 2]
stack.append(3) # [1, 2, 3]
not stack # Список не пустой
# False
stack
# [1, 2, 3]
stack.pop() # В стеке [1, 2]
# 3
stack.pop() # В стеке [1]
# 2
stack.pop() # В стеке пусто
# 1
not stack
# True
Тестируем основную функциональность
Теперь напишем первый тест. Первый тест всегда должен проверять позитивный сценарий — тот, в котором задействована основная функциональность тестируемого компонента:
def test_stack():
stack = []
# Добавляем два элемента в стек и затем извлекаем их
# Почему два? Так надежнее, чем один, а три — уже избыточно
stack.append('one')
stack.append('two')
assert stack.pop() == 'two'
assert stack.pop() == 'one'
Этот тест проверяет, правильно ли работают два основных метода без учета пограничных случаев. Для этого внутри теста выполняются два утверждения, которые по очереди проверяют извлекаемые значения из стека.
В интернете можно встретить мнение, что несколько проверок в рамках одного теста — это неправильно. Считается, что тесты нужно детализировать максимально подробно и создавать новый тест на каждую проверку:
def test_stack1():
stack = []
stack.append('one')
stack.append('two')
assert stack.pop() == 'two'
def test_stack2():
stack = []
stack.append('one')
stack.append('two')
stack.pop()
assert stack.pop() == 'one'
Выделять в отдельный тест точно стоит другие сценарии, которые требуют других данных или выполняют другую последовательность действий. Но такой подход работает не всегда. Нередко он приводит к серьезному раздуванию кода и дублированию. При этом выгода неочевидна.
Тестируем дополнительную функциональность
Следующим тестом будет тест на дополнительные функции стека. К таким у нас относится проверка на пустоту:
def test_emptiness():
stack = []
assert not stack
stack.append('one')
assert bool(stack) # not not stack
stack.pop()
assert not stack
В этом тесте проверяются сразу три ситуации:
- Начальное состояние стека
- Состояние стека после добавления элементов
- Состояние стека после извлечения всех элементов
В принципе, этого достаточно. Хотя в теории возможны ситуации, при которых проверка на not
все равно сломается. Нужно ли пытаться найти все варианты? Не нужно. Каждая написанная строчка кода в проекте — потенциальное место для изменения в случае правок. Если есть сомнения, нужно ли писать проверку или нет, то лучше не пишите. Так вы найдете тот минимум, который стоит писать. Редкие ситуации требуют покрытия тестами только тогда, когда они критичны для работоспособности.
Пограничные случаи
Ну, и последнее, что можно протестировать — поведение функции pop()
, когда в стеке нет ни одного элемента. По задумке стек выбрасывает исключение, если из него попытались взять элемент, когда тот был пустой. То есть эта ситуация рассматривается как ошибочная, программист всегда должен убеждаться в том, что стек не пустой:
import pytest
def test_pop_with_empty_stack():
stack = []
# проверить что вызывается конкретное исключение можно с помощью конструкции with pytest.raises()
# если внутри блока вызовется исключение, то тест будет пройден
with pytest.raises(IndexError):
stack.pop()
https://replit.com/@hexlet/python-testing-unit-tests
Не всегда пограничные случаи так легко увидеть. Маловероятно, что любой программист сможет сразу написать все нужные тесты.
Представим, что в коде возникла ошибка, для которой не было теста. В таком случае сначала напишите тест, который воспроизводит эту ошибку, и затем уже чините ее. Только так можно поддерживать достаточный уровень надежности, не превращая разработку в непрерывную починку багов.
Самостоятельная работа
- Выполните все шаги из этого урока в пакете hexlet_pytest
- Залейте код на GitHub
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.