Разбираемся с Continuous Integration

9 min read

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

Простые тесты тут не помогут, поскольку окружения на машинах разработчиков и на production могут различаться. Потребуется целая система сборки и тестирования. Такая система (а вернее принцип или подход) и называется Continuous Integration.

Схема работы с использованием CI

Рассмотрим типовой сценарий, который понимается под фразой “Использовать CI в своём проекте”.

  1. Разработчик создаёт pull request или commit в репозиторий проекта
  2. Проект автоматически разворачивается на вспомогательном сервере
    а. Создаётся пустой контейнер, например с Ubuntu
    б. Устанавливается необходимый софт для сборки проекта
    в. Клонируется репозиторий с кодом проекта из текущей ветки
    г. Выполняются скрипты настройки конфигураций
  3. Происходит запуск Unit-тестов
  4. Разработчику приходит оповещение о результате
  5. Если тесты не пройдены, реквест или коммит будет направлен на доработку

Где взять CI

Continuous Integration может быть настроен на вашем сервере с использованием таких инструментов как TeamCity или Jenkins. Однако, для open-source проектов зачастую удобно использовать готовые облачные решения, например, Travis CI или Semaphore CI.

Оба облачных решения позволяют легко сконфигурировать нужное вам окружение, поддерживают множество языков программирования и интегрируются с GitHub.

Как подключить CI

Мы продемонстрируем настройку CI для простейшего веб-приложения с использованием сервиса Semaphore CI. Дополнительно разберёмся с тестированием, оповещением о результатах проверки и интеграцией с GitHub.

Создаём тестовый проект

Напишем веб-приложение, которое будет выводить информацию о погоде в любом городе. Воспользуемся для этого бесплатным погодным API. На сайте достаточно зарегистрироваться и получить токен доступа в личном кабинете. После чего можно формировать запросы вида:

http://api.openweathermap.org/data/2.5/weather?q=Sankt-Peterburg&appid=bf3c0bec12bec9560fde3da3fad53de8

и получать в ответ информацию о погоде в выбранном городе:

{ "coord":{ "lon":30.25, "lat":59.92 }, "weather":[ { "id":800, "main":"Clear", "description":"clear sky", "icon":"01n" } ], "base":"stations", "main":{ "temp":260.15, "pressure":1018, "humidity":78, "temp_min":260.15, "temp_max":260.15 }, "visibility":10000, "wind":{ "speed":2, "deg":110 }, "clouds":{ "all":0 }, "dt":1520019000, "sys":{ "type":1, "id":7267, "message":0.0045, "country":"RU", "sunrise":1519966347, "sunset":1520004641 }, "id":536203, "name":"Sankt-Peterburg", "cod":200 }

Приведём код приложения на языке Python, которое взаимодействует с данным API.

from flask import Flask import requests from flask import request # Замените своим токеном с openweathermap APP_ID = 'bf3c0bec12bec9560fde3da3fad53de8' app = Flask(__name__) # Извлекает данные о погоде и конвертирует из Кельвина в Цельсий def parse_weather(data): try: temp = data['main']['temp'] return f'The temperature is: {float(temp)-273.15:.1f}°' except Exception as e: return f'Data error: {e}' # Получить данные о погоде через API def get_weather(city): try: r = requests.get(f'http://api.openweathermap.org/data/2.5/weather?q={city}&appid={APP_ID}') if r.status_code != 200: return f'Invalid response code: {r.status_code}' else: return parse_weather(r.json()) except Exception as e: return f'API error: {e}' # Выводит сведения о погоде в выбранном городе @app.route("/") def weather(): city = request.args.get('city', 'Sankt-Peterburg') return get_weather(city) @app.route("/") def index(): return "Welcome! The service takes a date and gives corresponding day of the week" if __name__ == "__main__": app.run()

Добавляем Unit-тесты

Чтобы автоматически контролировать работоспособность приложения после каждой модификации кода, реализуем следующие тесты:

Создайте файл tests.py со следующим содержимым

from flask import request import main import unittest class MainTestCase(unittest.TestCase): def test_appid_is_set(self): assert len(main.APP_ID) == 32 def test_parse_error(self): assert main.parse_weather([]) == 'Data error: list indices must be integers or slices, not str' def test_convert_temperature(self): assert main.parse_weather({'main': {'temp': '300'}}) == 'The temperature is: 26.9°' assert main.parse_weather({'main': {'temp': '273.15'}}) == 'The temperature is: 0.0°' def test_request(self): with main.app.test_client() as c: rv = c.get('/?city=Moscow') assert rv.status_code == 200 if __name__ == '__main__': unittest.main()

Для запуска тестов достаточно выполнить следующую команду в директории проекта:

python -m unittest tests.py

Вы получите примерно следующий результат:

.... ---------------------------------------------------------------------- Ran 4 tests in 0.144s OK

Подробнее о тестировании кода вы можете узнать из нашей статьи Автоматизация тестирования на PHP

Настраиваем CI

Разверните репозиторий на GitHub и добавьте туда созданные выше файлы. Пример можно клонировать по ссылке: https://github.com/codex-team/article-ci

Теперь перейдите на сайт https://semaphoreci.com и зарегистрируйте личный аккаунт. Удобно создать аккаунт напрямую через GitHub с помощью кнопки  “Get started with GitHub”.

Главная страница сайта

Нажмите на ссылку "Create new" для создания нового проекта.

Выберите репозиторий
Выберите ветку master
Выберите ваш профиль на semaphore
Напротив проекта нажмите на картинку с шестеренкой (Settings)  

Платформа Semaphore автоматически подберёт оптимальные настройки, проанализировав ваш репозиторий. Остаётся только отредактировать список команд, которые будут выполнены после каждого коммита или pull-реквеста в репозиторий на GitHub.

Semaphore создаст виртуальный контейнер с Linux, клонирует ваш проект из GitHub и выполнит следующие команды:

pip install -r requirements.txt
python -m unittest tests.py
Панель настроек с командами для выполнения

Проверяем работу

Сделайте любой коммит в основную ветку репозитория на GitHub и перейдите в соответствующий ему проект на Semaphore.

При неуспешном прохождении тестов вы увидите информацию об ошибке сборки и сможете изучить подробный вывод тестов.

Не был задан API ключ

В случае успешной сборки статус проекта изменится на passed, а в списке коммитов на GitHub вы увидите зелёную галочку.

Карточка успешной сборки на сайте Semaphore
Статусы коммитов на GitHub

Безопасность приватной информации

Чтобы не хранить пароли и ключи доступа к API в открытом виде, их можно поместить в переменные окружения, создаваемые через Semaphore.

Для этого зайдите в настройки проекта и выберите пункт "Environment Variables".

Добавление APP_ID в переменную окружения

Замените строчку APP_ID = '...' в скрипте main.py следующим текстом, чтобы брать APP_ID из переменной окружения.

import os APP_ID = os.environ.get('APP_ID', '')

Получаем оповещения

Semaphore имеет внутренний механизм для выполнения команд в зависимости от результатов тестирования. В случае успеха, переменная окружения SEMAPHORE_THREAD_RESULT примет значение passed, в противном – Failed.

Можно добавить ещё одну команду в настройках проекта на Semaphore, которая будет выполнять действия исходя из значения переменной окружения.

Например, можно посылать результаты проверки напрямую в Telegram-чат через платформу CodeX Bot.

if [ "$SEMAPHORE_THREAD_RESULT" = "passed" ]; then curl --data "message=Success" https://notify.bot.ifmo.su/u/<token>; else curl --data "message=Failed" https://notify.bot.ifmo.su/u/<token>; fi;
Пример интеграции CI с Telegram

Итоги

Continious Integration позволяет автоматически развернуть и протестировать ваше приложение на системе идентичной рабочему серверу. Для упрощения этого процесса можно воспользоваться универсальными сервисами (например Semaphoreci). Такие сервисы совместимы с большинством современных языков программирования и поддерживают интеграцию с GitHub.

Подробнее о возможностях Semaphoreci – https://semaphoreci.com/docs/

Репозиторий проекта для этой статьи – https://github.com/codex-team/article-ci