Юнит-тесты на Python: Быстрый старт
Перевод статьи подготовлен специально для студентов курса «Python QA Engineer».
Юнит-тестирование кода является неотъемлемой частью жизненного цикла разработки программного обеспечения. Юнит-тесты также формируют основу для проведения регрессионного тестирования, то есть они гарантируют, что система будет вести себя согласно сценарию, когда добавятся новые функциональные возможности или изменятся существующие.
В этой статье я продемонстрирую основную идею юнит-тестирования на одном классе. На практике вам придется писать множество тестовых случаев, добавлять их в тестовый набор и запускать все вместе. Управление тест-кейсами мы рассмотрим в следующей статье.
Сегодня мы сосредоточимся на тестировании бэкенда. То есть разработчик реализовал некоторый проект согласно спецификациям (например, Calculator.py), а ваша задача состоит в том, чтобы убедиться, что разработанный код действительно им соответствует (например, с помощью TestCalculator.py ).
Предположим, что вы написали класс Calculator для выполнения основных вычислительных функций: сложения, вычитания, умножения и деления.
Код для этого здесь ( Calculator.py ):
Теперь я хочу запустить юнит-тест, чтобы понять, что функциональность в приведенном выше классе работает, как запланировано.
Юнит-тест имеет следующую структуру:
setUp() и tearDown() – это стандартные методы, которые поставляются с фреймворком unittest (они определены в классе unittest.TestCase). В зависимости от вашего тестового случая вы можете переопределять или не переопределять два этих метода по умолчанию.
Всякий раз, когда выполняется этот тест-кейс, сначала выполняется метод setUp(). В нашем случае мы просто создаем объект класса Calculator и сохраняем его как атрибут класса. В родительском классе есть несколько других методов по умолчанию, которые мы рассмотрим позже.
Вы увидите вывод, сходный со следующим:
Что делать, если что-то не работает, как ожидалось? Давайте изменим ожидаемое значение test_divide с 5 на 6 (5 – правильное значение, сейчас мы посмотрим, что случится при сбое. Это не ошибка в исходном коде, а ошибка в тестовом наборе, у вас тоже могут быть ошибки в тестовых наборах, поэтому всегда проверяйте тестовые сценарии на наличие ошибок!)
При запуске этого тест-кейса, вы получите следующий результат:
Здесь написано, что 3 из 4 тестов прошли успешно, а один не удался. В реальном сценарии, предполагается, что ваш тестовый случай является верным, то есть таким образом он помогает определять неправильно реализованную функцию.
Как писать профессиональные модульные тесты на Python
Тестирование, это основа серьёзной разработки программного обеспечения. Существует много видов тестирования, но наиболее важный вид, это модульное тестирование. Модульное тестирование даёт уверенность в том, что вы сможете использовать хорошо протестированные блоки в качестве базовых элементов, полагаться на них и использовать при создании программы. Они увеличивают ваш инструментарий из проверенного кода за пределами ваших конструкций и стандартной библиотеки. Кроме того Python предоставляет отличную поддержку для написания модульных тестов.
Действующий пример
Прежде чем погрузиться в принципы, эвристики и руководства, давайте посмотрим репрезентативный модульный тест в действии. Класс SelfDrivingCar это частичное выполнение логики вождения автопилота автомобиля. Главным образом он контролирует скорость автомобиля. Он рспознаёт объекты впереди, ограничение скорости, а также прибытие или нет в пункт назначения.
Вот модульный тест для метода stop() чтобы раззадорить ваш аппетит. Я расскажу подробности позже.
Руководство по модульному тестированию
Основные идеи
Написание хороших модульных тестов это тяжелый труд. Написание модульных тестов занимает время. Когда вы меняете код, необходимо изменять тесты. Иногда в вашем тесте будут ошибки. Это означает, что вы должны быть по-настоящему идейным. Польза огромна, даже для небольших проектов, но это не бесплатно.
Будьте дисциплинированы
Вы должны быть дисциплинированным. Будьте последовательным. Убедитесь, что все тесты выполнены. Не отказывайтесь от тестов, только потому что вы «знаете», что код в порядке.
Автоматизируйте
Чтобы помочь вам быть дисциплинированным, необходимо автоматизировать модульные тесты. Тесты должны запускаться автоматически на значимых этапах, таких как проектирование или развертывание. В идеале ваша система управления версиями должна отклонять код, который не прошел все тесты.
Непротестированный код плохой по определению
Если вы не проверили его, вы не сможете сказать, что он работает. Это значит, что вы должны рассматривать его как плохой. Если это критический код, не разворачивайте его в производство.
Пояснения
Что такое модуль?
Модуль в смысле тестирования это файл, содержащий набор определённых функций или класс. Если у вас есть файл с несколькими классами, вы должны написать модульный тест для каждого из них.
Делать TDD или не делать TDD
Тест драйв разработка, это практика, где вы пишете тесты, до того как вы пишете код. Есть несколько преимуществ этого подхода, но я рекомендую отказаться от него, если у вас есть возможность написать тесты позже.
Причина заключается в том, что я проектирую код. Я пишу код, смотрю на него, переписываю, еще раз смотрю и быстро переписываю ещё раз. Написание тестов сначала ограничивает и замедляет меня.
После того, как я сделаю первоначальный дизайн, я сразу напишу тесты, до интеграции со всей системой. Тем не менее, это отличный способ найти себя в создании модульных тестов, и это гарантирует, что весь ваш код будет проверен.
Unittest модуль
Следующий шаг — написать специфические методы теста для тестирования кода внутри теста — в этом случае класс SelfDrivingCar — делает то, что он должен делать. Структура тестового метода довольно обычная:
Обратите внимание, что результат не должен быть результатом метода. Он может быть изменением состояния класса, сторонним эффектом, например как добавление новой строки в базе данных, записью файла или отправкой сообщения по электронной почте.
Здесь на самом деле два теста. Первый тест, чтобы убедиться, что если скорость автомобиля равна 5 и stop() вызывается, то скорость становится равна 0. И еще один тест, чтобы убедиться, что ничего не случится, если вызвать stop() снова, когда автомобиль уже остановился.
Позже я расскажу о других тестах для дополнительных функциональных возможностей.
Doctest модуль
Doctest модуль очень интерестный. Он позволяет использовать интерактивные примеры в docstring и проверять результаты, включая исключения.
Как вы видите, docstring намного больше, чем код функции. Это не улучшает читаемость кода.
Запуск тестов
OK. Вы написали модульные тесты. Для большой системы у вас будет десятки / сотни / тысячи модулей и классов, возможно,размещенных в разных папках. Как вы будете запускаете все тесты?
Чтобы найти и запустить тесты на основе unittest, просто введите в командной строке:
Существует несколько флагов, которые управляют операцией:
Определение степени покрытия кода
Если оба являются строками, то он пытается преобразовать их в целые числа и добавить. В противном случае он вызывает исключение. Функция test_add() проверяет функцию add() с аргументами, которые являются как целыми числами, так и аргументами, которые являются плавающим значением и проверяют правильное поведение в каждом случае. Но степень покрытия теста является незавершенной. В случае, если строковые аргументы были не протестированы. В результате тест проходит успешно, но ошибка в ветке, где аргументы были строками, не были обнаружены (см. «intg»?).
Практические Unit Tests
Написание тестов промышленной мощности нелегкая и непростая задача. Есть несколько вещей, которые нужно учитывать и компромиссы, на которые следует пойти.
Разработка тестирования
Затраты и выгоды
Количество усилий, которые вы вкладываете в тестирование, должно быть сопоставимо с затратами на неудачу, насколько стабильным является код и насколько легко его можно исправить, если проблемы обнаружены в строке.
С другой стороны, если одна кнопка в вашем веб-приложении на странице, которая расположена тремя уровнями ниже главной страницы, немного мерцает, когда кто-то ее щелкает, вы можете это исправить, но, вероятно, вы не будете добавлять отдельный модульный тест для этого случая. Данный метод не оправдывает себя экономически.
Мышление тестирования
Мышление тестирования очень важно. Один из принципов, который я использую, состоит в том, что каждый кусок кода имеет как минимум двух пользователей: код, который использует его, и пользователь, который его тестирует. Это простое правило помогает с разработкой и зависимостей. Если вы помните, что вам нужно написать тест для своего кода, вы не добавите много зависимостей, которые трудно восстановить во время тестирования.
Например, предположим, что ваш код должен что-то вычислять. Для этого ему необходимо загрузить данные из базы, прочитать файл конфигурации и динамически обратиться к некоторыми REST API для получения актуальной информации. Все это может потребоваться по разным причинам, но при этом, ели вы разместите всё это в одной функции, это очень затруднит тестирование. Это возможно звучит смешно, но гораздо лучше изначально структурировать ваш код.
Чистые функции
Почему их легко проверить? Потому что нет необходимости устанавливать специальную среду для их тестирования. Вы просто передаете аргументы и проверяете результат. Вы также знаете, что до тех пор, пока тестируемый код не изменится, ваш тест тоже не должен изменяться.
Сравните его с функцией, которая считывает XML-файл конфигурации. Ваш тест должен будет создать XML-файл и передать его имя файла в тестируемый код. Не трудная задача. Но предположим, что кто-то решил, что XML просто ужасен, и все файлы конфигурации должны находиться в JSON. Они занимаются своим делом и конвертируют все файлы конфигурации в JSON. Они проводят все тесты, включая ваши, и все они успешно завершаются!
Почему? Потому что код не изменился. Он все еще ожидает файл конфигурации XML, и ваш тест по-прежнему создает XML-файл для него. Но при выполнении ваш код получит файл JSON, который он не сможет проанализировать.
Обработка ошибок тестирования
Как правило, вы хотите проверить ввод в общедоступном интерфейсе, потому что вам не обязательно знаеть, кто будет обращаться к вашему коду. Давайте посмотрим на метод drive() автопилота автомобиля. Этот метод ожидает параметр «destination». Параметр «destination» будет использоваться позже в навигации, но метод «drive» ничего не делает, чтобы мы смогли убедиться в его корректности.
Чтобы проверить, как обработались ошибки в тесте, я передаю неверные аргументы и думаю, что они должным образом будут отвергнуты. Вы можете сделать это, используя self.assertRaises() метод unittest.TestCase. Этот метод очень удачный, если код под тестированием действительно вызывает исключение.
Давайте посмотрим на это в действии. Метод test_drive() пропускает широту и долготу вне допустимого диапазона и ждет, что метод drive() вызовет исключение.
Тест не выполняется, поскольку метод drive () не проверяет его аргументы на достоверность и не вызывает исключения. Вы получите хороший отчет с полной информацией о том, что не удалось, где и почему.
Теперь все тесты проходят.
Тестирование частных методов
Должны ли вы проверять каждую функцию и метод? В частности, следует ли тестировать частные методы, называемые только вашим кодом? Обычный неудовлетворительный ответ: «В зависимости от ситуации».
Как организовать ваши модульные тесты
В большой системе не всегда ясно, как провести тесты. Должен ли быть один большой файл со всеми тестами для пакета или отдельный тестовый файл для каждого класса? Должны ли тесты быть в том же файле, что и тестируемый код, или в том же каталоге?
Этот подход имеет ряд преимуществ. Это дает определённость уже при просмотре каталогов, сразу видно, что вы не забыли протестировать. Это также помогает сохранять тесты в приемлимых размерах. Предполагая, что ваши модули имеют приемлимый размер, тестовый код для каждого модуля будет в собственном файле, который может быть немного больше, чем тестируемый модуль, но все же удобно размещён в одном файле.
Заключение
Юнит- тесты являются основой качествнного кода. В этом уроке я изучил некоторые принципы и рекомендации для работы с модульным тестированием и объяснил нескольких лучших практик. Чем больше система, которую вы строите, тем более важными становятся модульные тесты. Но одних модульных тестов недостаточно. Другие типы тестов также необходимы для крупномасштабных систем: интеграционные тесты, тесты производительности, тесты нагрузки, тестирование на уязвимость, тесты на прием данных и другие.
Python Testing с pytest. Начало работы с pytest, Глава 1
Вернуться Дальше
Я обнаружил, что Python Testing с pytest является чрезвычайно полезным вводным руководством к среде тестирования pytest. Это уже приносит мне дивиденды в моей компании.
Chris Shaver
VP of Product, Uprising Technology
Примеры в этой книге написаны с использованием Python 3.6 и pytest 3.2. pytest 3.2 поддерживает Python 2.6, 2.7 и Python 3.3+.
Исходный код для проекта Tasks, а также для всех тестов, показанных в этой книге, доступен по ссылке на веб-странице книги в pragprog.com. Вам не нужно загружать исходный код, чтобы понять тестовый код; тестовый код представлен в удобной форме в примерах. Но что бы следовать вместе с задачами проекта, или адаптировать примеры тестирования для проверки своего собственного проекта (руки у вас развязаны!), вы должны перейти на веб-страницу книги и скачать работу. Там же, на веб-странице книги есть ссылка для сообщений errata и дискуссионный форум.
Под спойлером приведен список статей этой серии.
Поехали
Вот как это выглядит при запуске:
Если у вас есть цветной терминал, PASSED и нижняя строка зеленые. Прекрасно!
Это неудачный тест:
То, как pytest показывает сбои при тестирование, является одной из многих причин, почему разработчики любят pytest. Давайте посмотрим, что из этого выйдет:
Отлично! Пробный тест test_failing получает свой раздел, чтобы показать нам, почему он не прошел.
И pytest точно сообщает, что первый сбой: index 0 — это несоответствие.
Значительная часть этого сообщения красного цвета, что делает его действительно выделяющимся (если у вас есть цветной терминал).
Вот это да!
pytest добавляет символ «карет» (^), чтобы показать нам в чем именно отличие.
Если вы уже впечатлены тем, как легко писать, читать и запускать тесты с pytest и как легко читать выходные данные, чтобы увидеть, где случилась неудачка, ну… вы еще ничего не видели. Там, откуда это прилетело, чудес намного больше. Оставайтесь и позвольте мне показать вам, почему я думаю, что pytest-это лучшая тестовая платформа.
В оставшейся части этой главы вы установите pytest, посмотрите на различные способы его запуска и выполните некоторые из наиболее часто используемых опций командной строки. В будущих главах вы узнаете, как написать тестовые функции, которые максимизируют мощность pytest, как вытащить код установки в разделы настройки и демонтажа, называемые фикстуры, и как использовать фикстуры и плагины, чтобы реально перегружать тестирование программного обеспечения.
Еще одно полезное применение тестов программного обеспечения-это проверка ваших предположений о том, как работает тестируемое программное обеспечение, что может включать в себя тестирование вашего понимания сторонних модулей и пакетов и даже построение структур данных Python.
Проект Tasks использует структуру Task, основанную на фабричном методе namedtuple, который является частью стандартной библиотеки. Структура задачи используется в качестве структуры данных для передачи информации между пользовательским интерфейсом и API.
В остальной части этой главы я буду использовать Task для демонстрации запуска pytest и использования некоторых часто используемых параметров командной строки.
Before we jump into the examples, let’s take a step back and talk about how to get pytest and install it.
Прежде чем перейти к примерам, давайте сделаем шаг назад и поговорим о том, где взять pytest и как установить его.
Добываем pytest
Штаб-квартира pytest https://docs.pytest.org. Это официальная документация. Но распространяется он через PyPI (индекс пакета Python) в https://pypi.python.org/pypi/pytest.
Как и другие пакеты Python, распространяемые через PyPI, используйте pip для установки pytest в виртуальную среду, используемую для тестирования:
Если вы не знакомы с virtualenv или pip, я вас познакомлю. Ознакомьтесь с Приложением 1 «Виртуальные среды» на стр. 155 и в Приложении 2, на странице 159.
Как насчет Windows, Python 2 и venv?
Пример для virtualenv и pip должен работать на многих POSIX системах, таких как Linux и macOS, а также на многих версиях Python, включая Python 2.7.9 и более поздних.
Источник venv/bin/activate в строке не будет работать для Windows, используйте вместо этого venv\Scripts\activate.bat.
Выполните это:
Для Python 3.6 и выше, вы можете обойтись venv вместо virtualenv, и вы не должны беспокоиться о том что бы установить его в первую очередь. Он включен в Python 3.6 и выше. Тем не менее, я слышал, что некоторые платформы по-прежнему ведут себя лучше с virtualenv.
Запускаем pytest
Без аргументов pytest исследует ваш текущий каталог и все подкаталоги для тестовых файлов и запустит тестовый код, который найдёт. Если вы передадите pytest имя файла, имя каталога или список из них, то будут найдены там вместо текущего каталога. Каждый каталог, указанный в командной строке, рекурсивно исследуется для поиска тестового кода.
Для примера, давайте создадим подкаталог, называемый задачами, и начнем с этого тестового файла:
The test_member_access() test is to demonstrate how to access members by name nd not by index, which is one of the main reasons to use namedtuples.
Let’s put a couple more tests into a second file to demonstrate the _asdict() and _replace() functionality
Вы можете использовать __new __.__ defaults__ для создания объектов Task без указания всех полей. Тест test_defaults() предназначен для демонстрации и проверки того, как работают умолчания.
Тест test_member_access() должен продемонстрировать, как обращаться к членам по имени nd не по индексу, что является одной из основных причин использования namedtuples.
Давайте добавим еще пару тестов во второй файл, чтобы продемонстрировать функции _asdict() и _replace()
Для запуска pytest у вас есть возможность указать файлы и каталоги. Если вы не укажете какие-либо файлы или каталоги, pytest будет искать тесты в текущем рабочем каталоге и подкаталогах. Он ищет файлы, начинающиеся с test_ или заканчивающиеся на _test. Eсли вы запустите pytest из каталога ch1, без команд, вы проведете тесты для четырёх файлов:
Чтобы выполнить только наши новые тесты задач, вы можете предоставить pytest все имена файлов, которые вы хотите запустить, или каталог, или вызвать pytest из каталога, где находятся наши тесты:
Часть выполнения pytest, где pytest проходит и находит, какие тесты запускать, называется test discovery (тестовым обнаружением). pytest смог найти все те тесты, которые мы хотели запустить, потому что мы назвали их в соответствии с соглашениями об именах pytest.
Ниже приведен краткий обзор соглашений об именах, чтобы ваш тестовый код можно было обнаружить с помощью pytest:
Давайте более подробно рассмотрим результат запуска только одного файла:
Результат говорит нам совсем немного.
pytest предоставляет изящный разделитель для начала тестового сеанса. Сеанс-это один вызов pytest, включая все тесты, выполняемые в нескольких каталогах. Это определение сеанса становится важным, когда я говорю о области сеанса по отношению к фикстурам pytest в определении области фикстур, на странице 56.
Платформа darwin — у меня на Mac. На компьютере с ОС Windows платформа отличается. Далее перечислены версии Python и pytest, а также зависимости от пакетов pytest. И py, и pluggy — это пакеты, разработанные командой pytest, чтобы помочь с реализацией pytest.
rootdir: /path/to/code/ch1/tasks, inifile:
Это две тестовые функции в файле.
Эта строка относится к числу пройденных тестов и времени, затраченному на весь сеанс тестирования. При наличии непроходных тестов здесь также будет указан номер каждой категории.
Результат теста-это основной способ, благодаря которому пользователь, выполняющий тест или просматривающий результаты, может понять, что произошло в ходе выполнения теста. В pytest тестовые функции могут иметь несколько различных результатов, а не просто пройти или не пройти. Вот возможные результаты тестовой функции:
Выполнение Только Одного Теста
Пожалуй, первое, что вы захотите сделать, после того, как начали писать тесты, — это запустить только один. Укажите файл напрямую и добавьте имя ::test_name :
Теперь давайте рассмотрим некоторые варианты.
Использование Опций
Ниже приведены несколько вариантов, которые весьма полезны при работе с pytest. Это далеко не полный список, но этих опций для начала вполне достаточно.
—collect-only
-k EXPRESSION
Ага! Это были правильные тесты.
-m MARKEXPR
—maxfail=num
У нас пока нет никаких операторов печати в наших тестах; демонстрация была бы бессмысленной. Тем не менее, я предлагаю вам немного поиграть с ними, чтобы вы увидели это в действии.
На цветном терминале в отчете вы также увидите красный результат FAILED и зеленые PASSED.
До сих пор у нас не было неудачных тестов с локальными переменными. Если я возьму тест test_replace() и изменю
—tb=style
—tb=line in many cases is enough to tell what’s wrong. If you have a ton of failing tests, this option can help to show a pattern in the failures:
—tb=line во многих случаях достаточно, чтобы показать, что не так. Если у вас гора неудачных тестов, этот параметр может помочь отобразить шаблон в сбоях:
Это определенно достаточно, чтобы рассказать вам, что происходит.
Есть три оставшихся варианта трассировки, которые мы пока не рассмотрели.
—durations=N
Поскольку наши тесты не достаточно длинные, я добавлю time.sleep(0.1) к одному из тестов. Угадайте, какой:
Медленный тест с дополнительным sleep появляется сразу же после вызова метки, за которым следует установка и опровержение. Каждый тест по существу состоит из трех этапов: call(вызов), настройки(setup) и опровержения(teardown). Установка и опровержение также являются фикстурой, и вы можете добавить код для получения данных или тестируемой системы программного обеспечения в состояние предварительного условия до запуска теста, а также, при необходимости, очистить их. Я подробно освещаю фикстуры в главе 3, pytest Fixtures, на стр. 49.
—version
Поскольку мы установили pytest в виртуальную среду, pytest будет находиться в каталоге site-packages этой виртуальной среды.
Последний часть информации текста справки отображает это примечание:
Это примечание важно, поскольку параметры, маркеры и фикстуры могут изменяться в зависимости от каталога или тестового файла. Это происходит потому, что по пути к указанному файлу или каталогу pytest может найти файлы conftest.py, которые могут включать функции-ловушки (hook functions), создающие новые параметры, определения фикстур и определения маркеров.
Возможность настраивать поведение pytest в файлах conftest.py и тестовых файлах позволяет настраивать поведение локально для проекта или даже подмножество тестов для проекта. Вы узнаете о файлах conftest.py и ini, таких как pytest.ini в главе 6 «Конфигурация», на странице 113.
Упражнения
Практикуйте активацию и деактивацию виртуальной среды несколько раз.
Установите pytest в новой виртуальной среде. См. Приложение 2, pip, на странице 159, если у вас возникли проблемы. Даже если вы думали, что у вас уже установлен pytest, вам нужно установить его в виртуальную среду, которую вы только что создали.
Создайте несколько тестовых файлов. Вы можете использовать те, которые мы использовали в этой главе или сделать свои собственные. Попрактикуйте pytest на этих файлах.
Измените операторы assert. Не просто используйте assert something == something_else; попробуйте такие вещи, как: