🏃 Continuous Integration (CI): как настроить и какую роль она играет в цикле разработки
Разберем роль CI в жизненном цикле разработки (что в себя включает эта часть процесса, какие задачи решает) и на практике настроим CI для приложения API сервера.
В прошлой статье 🏃 Работаем нон-стоп: непрерывная интеграция и непрерывное развертывание кода (CI/CD) мы обзорно познакомились с процессом CI\CD. Настало время углубиться и расшириться в детали. Начнем с разбора первой половины процесса – это CI (Continuous Deployment). Разберем цели этого процесса, инструменты и на практическом примере реализуем кейс.
Роль CI в жизненном цикле разработки
Вспомним как выглядит процесс CI.
Цели, которые преследует этот процесс следующие:
- Фиксация всех изменений версий через систему контроля версий.
- Компиляция кода и обработка ошибок при компиляции.
- Автоматическое тестирование выполнения кода.
- Создание артефактов (оповещения, документация и т. д.).
Самое главное – это все выполняется полностью в автоматическом режиме. Человек участвует только в первоначальной настройке процесса, то есть при создании unit-тестов. Такой подход гораздо быстрее ручного тестирования, так как позволяет максимально избежать ошибок по вине человека. В итоге внесенное изменение проходит через тесты и если тесты не прошли, всегда можно вернуться на предыдущий коммит в системе контроля версии. В общем-то и все, предлагаю приступить к реализации этого процесса.
Настраиваем CI в GitHub
Нам потребуется система СКВ (система контроля версий), а также среда, где будет компилироваться наш код и запускаться тесты. Все эти задачи (и еще много чего интересного) полностью предоставляет сервис GitHub.
Кейс: Настроить CI с помощью функционала сервиса GitHub для API сервера написанного на Python.
Подробно описывать код самого сервера и как залить его на git не буду. Тема статьи о другом.
Имеем в репозитории Git следующую структуру файлов:
Цветом выделены файлы, с которыми будем работать. Разберем для чего каждый файл.
from fastapi import FastAPI import uvicorn from typing import Optional from pydantic import BaseModel import secrets from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials class Item(BaseModel): name: str description: Optional[str] = None price: int tax: Optional[float] = None app = FastAPI() security = HTTPBasic() def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): correct_username = secrets.compare_digest(credentials.username, "test") correct_password = secrets.compare_digest(credentials.password, "test") if not (correct_username and correct_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", headers={"WWW-Authenticate": "Basic"}, ) return credentials.username @app.get("/") def read_root(credentials: HTTPBasicCredentials = Depends(get_current_username)): return {"Hi": "from API Server)"} @app.get("/test/{item_id}") async def read_item(item_id: int, credentials: HTTPBasicCredentials = Depends(get_current_username)): print(item_id) return {"item_id": item_id} @app.get("/check") def hello(): return "Hello World" @app.post("/items/") async def create_item(item: Item, credentials: HTTPBasicCredentials = Depends(get_current_username)): return item
Это основной файл нашего приложения (API сервера). Сервер имеет три endpoint. Мы будем работать с /
и check
. Endpoint /check
необходим как раз для тестов.
Пройдемся по основному функционалу приложения. Метод get_current_username
необходим для базовой аутентификации по логину и паролю. Далее у нас идут четыре endpoint
:
- @app.get(“/”)
- @app.get(“/test”)
- @app.get(“/items”)
- @app.get(“/check”)
Endpoint – это точки входа для API обращений. Когда у нас будут отрабатывать автоматические тесты, мы постучимся /check
и если получим код возврата 200
, значит все хорошо и сервис работает.
Следующий у нас файл requirements.txt
:
uvicorn==0.14.0 pydantic==1.8.2 fastapi==0.66.1 requests==2.26.0 pytest==6.2.5
Тут перечислены зависимости по библиотекам для запуска нашего приложения. Если мы в дальнейшем захотим использовать дополнительный функционал из внешней библиотеке, необходимо добавить имя и версию.
install: pip install --upgrade pip &&\ pip install -r requirements.txt test: python -m pytest -vv test_hello.py
Этот файл интереснее. В нем прописаны команды для сборки нашего приложения. Видим, что сначала устанавливаются все библиотеки и вторым шагом запускается файл test_hello.py
– это наши тесты.
from fastapi.testclient import TestClient from app import app client = TestClient(app) def test_valid_id(): response = client.get("/check") assert response.status_code == 200
Собственно через get стучимся на эндпоинт /check
и если код возврата 200
, то все ок – наш сервер как минимум отвечает по этому эндпоинту. Это минимальный вариант теста, можно его расширить и настроить под свое приложение. Смотрим в документацию
В итоге у нас есть файл с приложением, файл с необходимыми зависимостями, файл с тестами и Makefile
для последовательности запуска этого хозяйства. Вопрос на внимательность: чего не хватает?
Если вспомнить цели, которых добивается процесс CI (там было что-то про автоматический запуск), то все это делается через описание PipeLine
в файле main.yaml
.
Обратите внимание на расположение этого файла. Корень вашего репозитория, в нем директория .github/workflows/
.
name: Run Test on: push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.10.1 uses: actions/setup-python@v1 with: python-version: 3.10.1 - name: Install dependencies run: | make install - name: Test run: | make test
На языке YAML описывается: если у нас есть новый коммит в ветке main
, необходимо выполнить Job
c шагами 1-3.
- Использовать Python 3.10.1
- Выполнить команды из блока
install
файлаMakefile
. - Выполнить команды из блока
test
файлаMakefile
.
Если на всем этом длинном пути нашего PipeLine
не возникнет ошибок, то у нас соберется наше приложение, запустятся тесты. Проследить это можно на вкладке Action вашего репозитория.
Если провалиться в детали – то можно посмотреть логи каждого шага
Поздравляю, тесты прошли успешно – код успешно запускается без привязки к прод. окружению. И теперь запуск всей цепочки действий (наш PipeLine) будет происходить при каждом коммите в репозиторий.