GitHub Actions CI¶
Мета: кожен
git pushавтоматично запускає ті самі тести, які ти щойно навчився писати. Без твоєї участі. На чистому сервері. З результатом у GitHub за 2–3 хвилини.
Де живе workflow¶
notes_chat_app/ ← корінь репозиторію
└── .github/
└── workflows/
└── ci.yml ← GitHub бачить тільки files у .github/workflows/
GitHub Actions читає workflow ТІЛЬКИ з директорії .github/workflows/ у кореневій папці репозиторію.
Що відбувається при git push¶
Ти: git push origin main
│
▼
GitHub: Detect event → push до main
│
▼
Azure: Provision Ubuntu 22.04 VM (нова, чиста)
│
├── Job 1: Unit + Integration + Consumer (~1-2 хв)
│ checkout → python 3.12 → pip install
│ → PostgreSQL service → Redis service
│ → manage.py test models/services/forms/views/consumers
│ Результат: ✅ або ❌
│
└── Job 2: Selenium E2E (~1-2 хв) — тільки якщо Job 1 ✅
checkout → pip install → migrations
→ PostgreSQL + Redis + Selenium Chrome service
→ manage.py test test_selenium
Результат: ✅ або ❌
│
▼
GitHub: Зелений/червоний статус на коміті + email якщо ❌
Повний workflow YAML рядок за рядком¶
Ця назва відображається у вкладці Actions у GitHub.
on — визначає коли запускати workflow.
| Ключ | Що означає |
|---|---|
push |
При кожному git push |
branches |
Тільки для гілок main або master |
pull_request |
При відкритті або оновленні PR |
workflow_dispatch |
Кнопка "Run workflow" у GitHub UI → Actions |
Job 1: Unit + Integration + Consumer Tests¶
runs-on: ubuntu-latest — свіжа Ubuntu 22.04 VM на Azure. Після завершення VM знищується. Між запусками немає стану.
services:
# PostgreSQL у GitHub Actions (окремий container)
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_notes_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U test_user -d test_notes_db"
--health-interval 5s
--health-retries 5
# Redis для channel layer
redis:
image: redis:7-alpine
ports: ['6379:6379']
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
services — додаткові Docker containers, що запускаються поруч з runner VM.
health-cmd + health-interval + health-retries — GitHub Actions чекає поки сервіс стане healthy перед початком steps.
env:
DATABASE_URL: postgres://test_user:test_pass@localhost:5432/test_notes_db
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: ci-secret-key-not-for-production
DEBUG: "False"
ALLOWED_HOSTS: localhost,127.0.0.1
Змінні середовища для Django. DATABASE_URL вказує на PostgreSQL service container (localhost:5432).
Step 1 — Checkout
Клонує весь репозиторій на VM. Без цього кроку на runner немає жодного файлу з твоїм кодом.
Step 2 — Setup Python
Встановлює Python 3.12. cache: pip — кешує ~/.cache/pip між запусками (аналог actions/cache, але вбудований у setup-python).
Step 3 — Install dependencies
Step 4 — Run Unit + Integration tests
- name: Run Unit + Integration tests
run: |
python manage.py test \
notes_app.tests.test_models \
notes_app.tests.test_services \
notes_app.tests.test_forms \
notes_app.tests.test_views \
notes_app.tests.test_consumers \
-v 2
Запускає 123+ тестів. Явно перераховані модулі без test_selenium — Selenium в окремому job.
-v 2 — verbose: кожен тест виводиться окремим рядком. В CI це корисно: одразу видно яке ім'я тесту впало.
Якщо будь-який тест повертає FAILED → step повертає exit code 1 → GitHub Actions вважає step провалений → job зупиняється → Job 2 (selenium) не запускається.
Job 2: Selenium E2E¶
selenium-e2e:
name: Selenium E2E tests
runs-on: ubuntu-latest
needs: test # ← запускається ПІСЛЯ успіху unit тестів
needs: test — Job 2 запускається тільки після того як Job 1 завершився успішно. Якщо unit тести впали — Selenium навіть не стартує.
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_notes_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U test_user"
--health-interval 5s
--health-retries 5
redis:
image: redis:7-alpine
ports: ['6379:6379']
selenium:
image: selenium/standalone-chrome:latest
ports: ['4444:4444']
options: --shm-size=2g # Chrome потребує shared memory
--shm-size=2g — Chrome потребує достатньо shared memory. Без цього Chrome може падати з OOM.
env:
DATABASE_URL: postgres://test_user:test_pass@localhost:5432/test_notes_db
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: ci-secret-key
DEBUG: "False"
ALLOWED_HOSTS: localhost,127.0.0.1
SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub
WEB_HOST: localhost
SELENIUM_REMOTE_URL—_make_driver()використовує Remote WebDriver замість локального ChromeWEB_HOST—_DockerLiveServerMixinзамінює0.0.0.0наlocalhostуlive_server_url
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run migrations
run: python manage.py migrate --noinput
- name: Run Selenium E2E tests
run: python manage.py test notes_app.tests.test_selenium -v 2
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: selenium-screenshots
path: /tmp/selenium-*.png
if: failure() — крок виконується ТІЛЬКИ якщо попередній крок провалився. Якщо тест падає і зберігає screenshot → він доступний у GitHub Actions → Artifacts.
Повний workflow (для копіювання)¶
# .github/workflows/ci.yml
name: CI — Test Suite
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
jobs:
test:
name: Unit + Integration + Consumer tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_notes_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U test_user -d test_notes_db"
--health-interval 5s
--health-retries 5
redis:
image: redis:7-alpine
ports: ['6379:6379']
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
env:
DATABASE_URL: postgres://test_user:test_pass@localhost:5432/test_notes_db
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: ci-secret-key-not-for-production
DEBUG: "False"
ALLOWED_HOSTS: localhost,127.0.0.1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install dependencies
run: pip install -r requirements.txt
- name: Django system check
run: python manage.py check
- name: Run Unit + Integration tests
run: |
python manage.py test \
notes_app.tests.test_models \
notes_app.tests.test_services \
notes_app.tests.test_forms \
notes_app.tests.test_views \
notes_app.tests.test_consumers \
-v 2
- name: Generate coverage report
run: |
coverage run manage.py test \
notes_app.tests.test_models \
notes_app.tests.test_services \
notes_app.tests.test_forms \
notes_app.tests.test_views \
notes_app.tests.test_consumers
coverage report --show-missing
coverage xml -o coverage.xml
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
selenium-e2e:
name: Selenium E2E tests
runs-on: ubuntu-latest
needs: test
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_notes_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U test_user"
--health-interval 5s
--health-retries 5
redis:
image: redis:7-alpine
ports: ['6379:6379']
selenium:
image: selenium/standalone-chrome:latest
ports: ['4444:4444']
options: --shm-size=2g
env:
DATABASE_URL: postgres://test_user:test_pass@localhost:5432/test_notes_db
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: ci-secret-key
DEBUG: "False"
ALLOWED_HOSTS: localhost,127.0.0.1
SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub
WEB_HOST: localhost
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run migrations
run: python manage.py migrate --noinput
- name: Run Selenium E2E tests
run: python manage.py test notes_app.tests.test_selenium -v 2
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: selenium-screenshots
path: /tmp/selenium-*.png
Coverage report¶
Вивід:
Name Stmts Miss Cover Missing
-----------------------------------------------------------
notes_app/models.py 87 3 97% 45, 67, 89
notes_app/services.py 124 8 94% 34-41
notes_app/views.py 198 12 94% ...
notes_app/consumers.py 56 4 93% ...
-----------------------------------------------------------
TOTAL 465 27 94%
coverage xml -o coverage.xml — зберігає у XML для інтеграції з Codecov / SonarQube.
Як переглянути результати¶
1. Actions tab у репозиторії
Тут список всіх workflow runs. Зелений — all green. Червоний — щось впало.
2. Граф Jobs
Клікни на конкретний run → бачиш два прямокутники:
3. Logs конкретного step
Клікни на job → клікни на step → розгорни:
Run python manage.py test ...
Creating test database for alias 'default' ...
Found 123 test(s).
test_str_returns_hash_name (notes_app.tests.test_models.TagModelTest) ... ok
test_unique_together_same_user_same_name_raises (...) ... ok
...
----------------------------------------------------------------------
Ran 123 tests in 34.2s
OK
4. Download artifacts
Actions run → Artifacts (внизу сторінки) → coverage-xml → завантажити.
5. Статус на коміті
Кожен коміт отримує значок:
- ✅ — всі workflow пройшли
- ❌ — хтось впав
- 🟡 — виконується
Зв'язок: тести ↔ CI/CD¶
Ти пишеш: CI/CD запускає: Ти отримуєш:
test_models.py ─────► Job 1 (unit+integration) ──► ✅ або ❌ за 30 сек
test_services.py ─────► ──► Traceback прямо в GitHub
test_forms.py ─────►
test_views.py ─────►
test_consumers.py ─────►
test_selenium.py ─────► Job 2 (needs: job1) ──► ✅ або ❌ після Job 1
──► Chrome у Docker container
Що означають рядки у лозі при помилці¶
FAIL: test_note_list_shows_only_user_notes
↑ Назва тесту → одразу зрозуміло ЩО зламалось
(notes_app.tests.test_views.NoteListViewTest)
↑ Клас і файл → можеш одразу відкрити
Traceback (most recent call last):
File ".../test_views.py", line 89, in test_note_list_shows_only_user_notes
self.assertNotContains(response, "Bob's Secret Note")
AssertionError: Response should not contain "Bob's Secret Note"
↑ Очікували що рядка НЕМАЄ, але він є → нотатки Bob'а видимі Alice!
→ Баг у selectors.get_user_notes() — забули фільтр по user
Process completed with exit code 1
↑ Django test runner повернув 1 → GitHub Actions вважає step провалений
Посилання на документацію¶
| Тема | Посилання |
|---|---|
| GitHub Actions — Quick Start | https://docs.github.com/en/actions/writing-workflows/quickstart |
| Understanding GitHub Actions | https://docs.github.com/en/actions/about-github-actions/understanding-github-actions |
| Events that trigger workflows | https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows |
| Workflow syntax (повний довідник) | https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions |
| Caching dependencies | https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows |
| GitHub-hosted runners | https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners |
| actions/checkout | https://github.com/marketplace/actions/checkout |
| actions/setup-python | https://github.com/marketplace/actions/setup-up-a-specific-version-of-python |
| actions/cache | https://github.com/marketplace/actions/cache |
| actions/upload-artifact | https://github.com/marketplace/actions/upload-a-build-artifact |
| Coverage.py | https://coverage.readthedocs.io/en/latest/ |
| Django system check | https://docs.djangoproject.com/en/5.2/topics/checks/ |
Практика: перевір що CI/CD працює¶
- Відкрий у браузері:
https://github.com/<user>/<repo>/actions - Знайди workflow "CI — Test Suite"
- Клікни на останній run
- Розгорни Job 1 → Step "Run Unit + Integration tests"
- Знайди рядок
Ran ... tests in ... OK - Перейди до Job 2 → подивись як запускається Selenium у Docker container
Самостійно: зроби навмисну помилку в будь-якому тесті (assertFalse(True)), зроби git push, подивись як CI показує FAIL і Traceback у GitHub Actions.
Далі¶
- Checkpoint — очікуваний результат, чеклист, практичне завдання