[Перевод] 10 шагов к успешному Python-проекту

Материал, перевод которого мы сегодня публикуем, посвящён инструментам, которые позволяют оснащать Python-проекты средствами форматирования кода, тестирования, непрерывной интеграции и анализа зависимостей. Это помогает ускорить процесс разработки, способствует повышению качества, единообразия и безопасности кода. Предполагается, что у читателя этого материала уже есть некоторый опыт Python-разработки и проект на Python, с которым он, в ходе чтения, будет экспериментировать. Если такого проекта у вас нет — здесь можно узнать о том, как подготовить среду разработки и создать Python-пакет. Примеры, которые будут здесь приводиться, подготовлены с использованием macOS и Python 3.7.

nnpfirpitm1blak2m4jfuludveo.jpeg

Шаг 1. Установка Black


00e346c01d193e87d70225c59cd191c3.png


Код проекта должен следовать соглашениям о стиле кода. Black — это Python-пакет, который автоматически форматирует код, приводя его внешний вид к стандарту PEP 8. Black — это сравнительно новый проект, но у него уже набралось больше миллиона загрузок. Его использование быстро стало признаком хорошего тона в Python-разработке. Вот руководство по Black.

Я, в качестве редактора кода, использую Atom, поэтому я добавил в Atom пакет Python-Black. О том, как его установить, можно узнать здесь. После установки этого пакета Atom будет переформатировать код после сохранения файла.

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

Добавьте параметр black==18.9b0 в первую нашедшуюся свободную строку файла requirements_dev.txt и выполните команду install -r requirements_dev.txt.

Black, по умолчанию, устанавливает длину строки кода равной 88 символов. В некоторых руководствах по стилю, например — в Sphinx, требуется использовать длину строки, равную 79 символов. В настройках пакета Black-Atom можно задать желаемую длину строки.

Теперь, когда мы обзавелись инструментом, который поможет экономить время на форматировании кода, подумаем о том, как ускорить и упростить отправку кода в PyPI.

Шаг 2. Создание файла .pypirc


Когда для отправки сборок приложения в TestPyPI и PyPI используется twine, сведения для входа в систему требуется вводить вручную. Если вы не знакомы с twine — взгляните на этот материал. Сейчас мы будем автоматизировать этот процесс.

Twine умеет работать с файлом .pypirc, который должен находиться в нашей домашней директории. Это средство, выгружая данные, берёт из данного файла URL, логин и пароль.

Итак, создадим в домашней директории файл .pypirc:

touch ~/.pypirc


Добавим в него следующий текст:

[distutils]
index-servers =
    pypi
    testpypi

[testpypi]
repository: https://test.pypi.org/legacy
username = your_username
password = your_pypitest_password

[pypi]
username = your_username
password = your_pypi_password


Понятно, что сюда надо вписать ваши реальные имя пользователя и пароль. Кроме того, проверьте, чтобы этот файл был бы сохранён в домашней директории, а не в текущей рабочей директории. Если вы хотите защитить этот файл от других пользователей, вы можете, пользуясь средствами командной строки, настроить его разрешения:

chmod 600 ~/.pypirc


Теперь ваш пакет можно загружать в TestPyPI пользуясь следующей командой:

twine upload -r testpypi dist/*


В обычный PyPI можно загружать пакеты так:

twine upload dist/*


После того, как вы обзаведётесь файлом .pypirc, вам больше не придётся вручную вводить имя пользователя и пароль.

Теперь давайте добавим в нашу среду разработки инструменты тестирования, которые позволят проверить правильность работы пакета, созданием которого мы занимаемся.

Шаг 3. Установка и настройка pytest


561860645caff54bbde0e568e9d319ab.jpg


Pytest — это самая популярная, лёгкая в использовании библиотека для тестирования кода, написанного на Python. В этом примере мы добавим в проект простые тесты. Вот, если вас интересуют подробности о pytest, хорошее вводное руководство по этому инструменту.

Добавим сведения о pytest в файл requirements_dev.txt:

pytest==4.3.0


Выполним установку пакета:

pip install requirements_dev.txt


Теперь выполним следующую команду, которая позволит pytest обнаружить наш пакет:

pip install -e .


Если вы деактивировали свою виртуальную среду разработки, то вам, для запуска тестов, понадобится снова выполнить обе команды pip.

Шаг 4. Создание тестов


Добавьте папку test в корневую директорию вашего проекта. Поместите в неё файл test_your_package_name.py. Мой файл называется test_notebookc.py. Если имя файла начинается с test_, pytest может автоматически обнаружить такой файл.

В файл test_notebookc.py я добавил следующий тест, который направлен на проверку того, правильное ли имя выводит функция. Модифицируйте этот код так, чтобы используемые в нём имена файлов и функций соответствовали бы вашим, опишите в нём собственные тесты.

"""Tests for `notebookc` package."""
import pytest
from notebookc import notebookc


def test_convert(capsys):
    """Correct my_name argument prints"""
    notebookc.convert("Jill")
    captured = capsys.readouterr()
    assert "Jall" in captured.out


Что здесь происходит?

Сначала мы импортируем сюда наш модуль. Потом создаём функцию, имя которой строится по шаблону test_my_function_name. Это соглашение об именовании функций позволяет другим людям, читающим код вашего проекта, быстро понять, что именно проверяется в тестах. Кроме того, это нужно для пакета, помогающего контролировать покрытие кода тестами, о котором мы поговорим ниже.

После этого мы вызываем функцию (convert), передавая ей в качестве аргумента имя Jill. Далее — захватываем то, что выводит функция. Тут стоит сказать, что рассматриваемая функция крайне проста. Она берёт параметр my_name и делает следующее:

print(f"I’ll convert a notebook for you some day, {my_name}.")


Pytest проверяет, содержится ли Jall в том, что выводит функция. Там этой строки быть не должно, так как мы передаём функции Jill. Вот документация по pytest, в которой можно найти сведения о перехвате вывода.

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

61ccfb1f6cff485d9cd90f8f68f2d33c.png


В ходе теста выявлена ошибка

Рекомендовано проверять тесты на правильность, описывая их так, чтобы, в определённых условиях, они завершались бы с ошибкой. Не стоит писать тесты, которые выдают лишь сообщения зелёного цвета, так как в противном случае может оказаться так, что тесты проверяют совсем не то, для проверки чего их пишут.

После того, как мы убедились в том, что тест дал сбой — поменяем в утверждении Jall на Jill и снова запустим тестирование. Теперь оно должно завершиться успешно.

c64d72bf51a42f3f209c00dc46e8ee52.png


Успешное завершение теста

Теперь всё хорошо. Тест позволяет убедиться в том, что если кто-то передаёт нашей функции строку, эта строка попадёт в тот текст, который выводит эта функция.

Ещё можно написать тест, который проверяет функцию на то, как она обращается с переданными ей данными. А именно, если она получает данные, тип которых отличается от строкового, она должна вызывать ошибку TypeError. Вот хороший материал по исключениям и по обработке ошибок в Python.

Когда мы создавали предыдущий тест, мы писали код, который приводит к успешному завершению теста. Это называется разработкой через тестировании (Test-Driven Development, TDD). TDD — это доказавший свою ценность подход к программированию, помогающий писать код, в котором оказывается меньше ошибок, чем в нём было бы без использования TDD. Вот полезный материал по TDD.

Теперь, в качестве упражнения, попробуйте написать тест, проверяющий функцию convert() на то, чтобы при передаче в неё чего-то, отличающегося от строки, она выдавала бы ошибку, и реализуйте соответствующие механизмы этой функции. Обратите внимание на то, что целые числа, списки и словари преобразуются в строки.

После того, как пакет успешно прошёл тесты — мы готовы к тому, чтобы воспользоваться системой непрерывной интеграции.

Шаг 5. Регистрация в сервисе Travis CI и его настройка


36ce18b28c14dd932d4194a7eb0031e6.png


Travis CI — это «распределённый веб-сервис для сборки и тестирования программного обеспечения». Его недавно купила компания Idera. Существуют и другие системы непрерывной интеграции, но Travis CI — это популярный, бесплатный для опенсорсного применения и хорошо документированный инструмент, поэтому мы будем пользоваться именно им.

Travis CI позволяет обеспечить интеграцию в ваш проект только того кода, который проходит тесты и соответствует стандартам. Здесь можно почитать подробности о Travis CI, а здесь — о непрерывной интеграции.

Cоздайте учётную запись на сайте https://travis-ci.org/. Далее, щёлкните по ссылке Review and add your authorized organizations на странице профиля. Вам предложат ввести пароль для доступа к GitHub. Щёлкните по Grant в разделе Organization access.

65e6bdd52e958c8335c999e971eec8ec.png


Настройка учётной записи Travis CI

Мне понадобилось синхронизировать аккаунт для того, чтобы в учётной записи появились бы сведения о notebooktoall и о репозитории notebookc. Обычно для того чтобы обработка кода средствами Travis CI заработала бы, требуется что-то около минуты. После этого нужно активировать репозиторий, воспользовавшись переключателем, показанным на следующем рисунке.

44404654c3d1a95209f1418e2b3ea543.png


Активация репозитория

Теперь нужно щёлкнуть по кнопке Settings. Тут нужно указать, может ли Travis выполнять сборку на основе отправленных в репозиторий пулл-запросов или веток.

3611edb25ab1af3484db1d38b0eda38c.png


Настройка сборки проекта

Сейчас пришло время настроить проект, над которым мы работаем, что даст возможность Travis выполнять сборку проекта для каждого пулл-запроса.

Шаг 6. Создание файла .travis.yml


В корневой папке проекта создайте файл .travis.yml со следующим содержимым:

dist: xenial
language: python
python: 3.7.2
install:
  - pip install -r requirements_dev.txt
  - pip install -e .

script:
  - pytest


Строка dist: xenial нужна для того чтобы указать Travis на необходимость использования для организации виртуального окружения Ubuntu Xenial 16.04. Для тестирования кода Python 3.7 нужна именно Ubuntu Xenial, подробности об этом можно почитать здесь.

Раздел install позволяет обеспечить установку пакетов, используемых при разработке проекта. Команда pip install -e . выполняет установку нашего пакета в виртуальном окружении Travis. После этого Travis, запуская pytest, сможет найти наш пакет.

Шаг 7. Тестирование в Travis CI


Выполните коммит изменений, отправьте их на GitHub, выполните PR. Travis должен, в течение нескольких секунд, начать работу.

15a32ad3f0a601e6bf89c06932efc450.png


Travis за работой

Вот чем занимается Travis, обрабатывая проект.

d4ab809a68ef70b62ad28121b055fc12.png


Действия, выполняемые Travis при обработке проекта

Если PR оказался неудачным — Travis об этом сообщит. Обратите внимание на то, что если пулл-запрос оказался неудачным, то можно отправить изменения в ту же ветку и Travis автоматически примется за работу.

Перейдите к странице своего репозитория на сайте Travis и осмотритесь там. Тут вы можете найти много интересного о сборках. Вероятно, в будущем вы станете частым гостем этой страницы, когда будете пытаться понять что стало причиной неудавшейся сборки.

Если же предположить, что всё прошло хорошо, если на странице находятся надписи зелёного цвета, значит проверка и сборка проекта выполнены успешно.

6a6bd07c4f0a9c82e87b9fd1a71b9b10.png


Сборка проекта выполнена успешно

Если же на странице нет ни зелёных, ни красных надписей, откройте меню More options и выберите пункт Requests. Если вы видите тут сообщения об ошибках, выводимые красным цветом — проанализируйте их. Если вы видите сообщение об ошибке Build config file is required, это означает, что Travis не может найти в репозитории ваш файл .travis.yml. Исправьте это и ошибка исчезнет.

Travis отправляет пользователям электронные письма в тех случаях, когда сборка проекта оказывается неудачной, и в тех случаях, когда это удаётся исправить.

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

Теперь проанализируем наш проект на предмет покрытия кода тестами.

Шаг 8. Оценка покрытия кода тестами


Отчёт о покрытии кода тестами позволяет узнать о том, какая часть кода проекта, пусть и небольшая, протестирована. Для создания подобных отчётов мы будем пользоваться пакетом pytest-cov.

В файл requirements_dev.txt добавим следующую строку:

pytest-cov==2.6.1


Выполним такую команду:

pytest --cov=my_project_name


В моём случае, после выполнения команды pytest --cov=notebookc был выведен следующий отчёт.

83c566d2942cbafcedd03de799b0eacd.png


Отчёт о покрытии кода тестами

Как оказалось, весь код проекта обеспечен тестами. Таких показателей очень легко достичь в том случае, если весь проект состоит из нескольких строк кода.

Теперь поговорим о средстве, которое позволяет вести общедоступную историю состояния проекта в плане покрытия его кода тестами.

Шаг 9. Использование Coveralls


Проект Coveralls позволяет поддерживать исторические сведения о покрытии кода тестами.

8c0616ecddd054d1adabf0532c75f69b.png


Coveralls

Для того, чтобы воспользоваться возможностями этого проекта, нужно зарегистрироваться на сайте https://coveralls.io/, используя данные учётной записи GitHub. Потом нужно подключить репозиторий.

В файл requirements_dev.txt нужно добавить строку coveralls==1.6.0. Этот файл, кстати, на данном этапе работы над проектом должен выглядеть так:

pip==19.0.3
wheel==0.33.0
twine==1.13.0
pytest==4.3.0
pytest-cov==2.6.1
coveralls==1.6.0


Отредактируем файл .travis.yml, приведя его к такому виду (в вашем случае здесь будет название вашего проекта):

dist: xenial
language: python
python: 3.7.2
install:
  — pip install -r requirements_dev.txt
  — pip install -e .

script:
  — pytest --cov=my_package_name
after_success:
  — coveralls


Теперь, когда Travis будет собирать проект, он установит необходимые пакеты, запустит тесты и создаст отчёт о покрытии кода тестами. Затем этот отчёт будет отправлен на сервис Coveralls.

Выполните коммит, отправьте код в GitHub и понаблюдайте за тем, что происходит. На то, чтобы отчёт о покрытии кода тестами попал бы в Coveralls, может потребоваться несколько минут.

7eb19d96768a592ef21b611ab69e3509.png


Обработка проекта, отчёт о покрытии кода тестами

Теперь среди проверок PR присутствует и проверка, выполняемая средствами Coveralls.

На странице Coveralls можно убедиться в том, что проект покрыт тестами на 100%.

24af45762ed1b6f80a4b558ab24fcef1.png


Сведения о покрытии кода тестами

Теперь давайте оснастим наш проект ещё одним полезным инструментом.

Шаг 10. Работа с PyUp


Сервис PyUp.io позволяет разработчику узнавать о том, устарели ли используемые им зависимости, а также о том, имеют ли они уязвимости. Этот сервис автоматически выполняет пулл-запросы, направленные на обновление пакета на GitHub. Для того чтобы воспользоваться возможностями этого проекта, нужно зарегистрироваться, пользуясь учётной записью GitHub, на его сайте — https://pyup.io/. При добавлении репозитория рекомендуется установить периодичность обновлений (Update Schedules) в значение every week. При таком подходе, если у вашего проекта множество зависимостей, вы не столкнётесь со слишком большим количеством пулл-запросов.

8fe4d514fe0efef8c8c0bea40451f4d3.png


Настройка обновлений

Вот как выглядят сведения о пакетах, некоторые из которых устарели, на сайте PyUp.io.

37226ae7a12381601d7335b1ae935d17.png


Сведения о пакетах

Пользуясь этим сервисом, вы всегда будете знать о том, когда выходят свежие версии используемых вами пакетов. Знание, как говорится, есть половина победы. А вторая половина — это, очевидно, автоматические пулл-запросы на обновление зависимостей.

Итоги


Из этого материала вы узнали о том, как пользоваться при разработке Python-проектов такими средствами, как Black, pytest, Travis CI, Coveralls и PyUp. Они помогают контролировать зависимости проектов, форматировать и тестировать код, проверять и собирать проекты. Надеемся, вам эти инструменты пригодятся.

Уважаемые читатели! Какими инструментами вы пользуетесь при разработке Python-проектов?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru