Автоматизація перевірки pull requests

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Вітаю, колеги! Мене звати Назарій Мошенський. Я Android Software Engineer у компанії Grid Dynamics. За більш ніж 5 років у цій сфері я працював на проєктах різного розміру і складності, проте найбільше мене захоплювали задачі з автоматизації процесів. Ця стаття буде цікавою для розробників, які хочуть покращити швидкість написання коду, його якість, а також формалізувати процеси і стандарти.

Перевірка пул-реквестів — це одна з наймарудніших робіт у розробці програмного забезпечення. Під час створення нового функціоналу може виникати багато побічних нюансів: описки, новий код міг щось зламати в решті кодової бази, з’явились невикористані ресурси після рефакторингу тощо.

Більшість з цих речей можна автоматизувати, і далі ми поговоримо, як це зробити. Сенс статті — показати не набір команд, які можуть автоматизувати будь-який проєкт, а набір інструментів, які ви можете налаштувати під свої потреби.

Як має виглядати pull request

На мою думку, ідеальний пул-реквест має невеликий набір характеристик:

  1. Невеликий розмір. Погодьтесь, що перевіряти дуже великі пул-реквести досить важко. Особливо, коли ви не до кінця володієте контекстом задачі. Зі збільшенням кількості змін в пул-реквесті зберігати фокус і тримати все в голові стає все важче. Тобто розмір пул-реквеста треба якось обмежити.
  2. Статичний аналізатор коду не видає помилок. Ніхто не хоче перевіряти мінорні помилки чи помилки, які легко можна проаналізувати готовими інструментами. В Android ми можемо писати власні правила перевірки коду і використовувати вже готові, налаштовувати стиль написання коду і перевіряти, як він підтримується тощо. Тому треба якось вивести ці помилки в пул-реквесті. Якщо ми бачимо нові помилки, то перевіряти його поки немає сенсу.
  3. Видно контекст задачі. Ми хочемо бачити, над яким тікетом проводилась робота і що саме було зроблено. Для цього було б зручно, якби ми могли фейлити пайплайн, якщо PR не має опису, в заголовку додавати номер тікета і його тайтл. В ідеалі ще додати скріншоти, щоб було наочно видно зміни.
  4. «Зелені» тести. Ми покриваємо код тестами і якщо зміни в коді «ламають» тести, то очевидно, що такий PR поки не готовий до рев’ю, бо всі тести мають успішно проходити. Тому було б зручно унеможливити мердж такого коду.
  5. Зрозумілі меседжі комітів. Коли ми знайомимось з пул-реквестом, то дуже легко відстежити послідовність дій автора, коли він створює атомарні коміти, додаючи до них зрозумілі меседжі. В ідеалі треба нав’язати стиль написання таких меседжів.
  6. Автоматичне визначення рев’юерів. Якщо у вас великий проєкт, на якому працює декілька команд, то було б зручно ділити код на зони відповідальності. Якщо ви робите зміни в репозиторії сусідньої команди, то було б зручно, якби неможливо було змерджити їх без апрува цієї команди.
  7. Pull request template (checklist). Перед тим, як передавати пул-реквест на перевірку, автор повинен переконатись, що він нічого не забув і виконав всі необхідні підготовчі дії. Для цього зручно мати список з чекбоксами, де автор повинен відмітити дії, які він виконав. Тоді рев’юер буде бачити, що пул-реквест дійсно готовий до перевірки.

Для всього цього є багато інструментів, які спростять нам життя і автоматизують це все.

Danger

Для автоматизації рутинних задач в процесі СІ ми використовуємо Danger. Він досить легко налаштовується і має багато плагінів, які вміють працювати з популярними інструментами. Також ви можете писати власні за потреби. Ось, наприклад, набір готових плагінів для ознайомлення — danger.systems/ruby.

Danger можна інтегрувати, наприклад, в GitHub Actions і видавати результати його роботи в pull request за допомогою бота.

Взагалі Danger — це інструмент, який заслуговує окремої серії статей. Ми розберемо базові можливості для розуміння, що можна з ним робити. Для детальнішого розбору можливостей раджу зазирнути на офіційний сайт або безпосередньо в документацію до плагінів, які ви для нього обрали.

Наприклад, вирішимо декілька проблем, які ми описували вище:

  1. Порожній description пул-реквеста.
  2. Занадто великий пул-реквест.
  3. Тести «падають».
  4. Lint warnings не видно.

Ми використовуємо danger/ruby. Два основних файли, які нам потрібні,— це Gemfile і Dangerfile.

В Gemfile ви додаєте danger-плагіни, які ви плануєте використовувати, наприклад, danger-junit, danger-android_lint тощо.

Виглядати він буде приблизно таким чином:

source 'https://rubygems.org'
gem 'danger'
gem 'danger-junit'
gem 'danger-android_lint'
gem 'danger-checkstyle_format'

В Dangerfile ви пишете скрипт за допомогою Ruby DSL, використовуючи Danger-плагіни, які ви вказали в Gemfile.

Ось так в декілька рядків в Dangerfile ми можемо перевірити, чи пул реквест не занадто великий і чи є в нього опис.

Щоб перевірити результати тестів, я встановлюю Danger-плагін JUnit. Він парсить звіти виконання юніт-тестів. Ви можете їх відформатувати, як вам зручно, і вивести у ваш pull request.

У моєму проєкті декілька модулів, тому я створюю патерн обходу директорій і використовую методи parse & report:

Насправді це найбазовіший конфіг. Власне, сам плагін здатний на значно більше.

Також ми можемо налаштувати Android Lint, який проаналізує код і виведе результати.

Так буде виглядати результат:

Це знову ж таки зроблено з мінімальними конфігураціями і без нічого особливого:

Ідея така ж: запускаємо Lint, він кладе результати своїх спостережень в папку, яку ми потім парсимо та відображаємо результати за допомогою Danger.

Ми можемо легко інтегрувати Danger з GitHub Actions, створивши подібний workflow:

name: Test,Verify,Report
# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  pull_request:
    types: [synchronize, opened, reopened, labeled, unlabeled, edited]
jobs:
  danger:
    runs-on: ubuntu-latest
    if: github.event_name  == 'pull_request' 
    steps:
    - uses: actions/checkout@v2
    - name: Set up Java SDK
      uses: actions/setup-java@v1
      with: {java-version: 1.8}
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: '3.0'
    - uses: actions/cache@v1
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile.lock') }} # change your gemfile path
        restore-keys: |
          ${{ runner.os }}-gems-
    - name: Run unit tests
      run: ./gradlew test
    - name: Run linter
      run: ./gradlew runChecksForDanger
    - name: danger
      env:
        DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
          bundle install
          bundle exec danger

Перед запуском Danger я запускаю команди ./gradlew test та ./gradlew runChecksForDanger. Перша генерує звіти юніт-тестів, а друга звіти лінтера. Вони будуть потрібні для того, щоб Danger їх розпарсив і вивів результати на екран.

Також потрібно додати токен, щоб бот міг виводити результати роботи Danger в ваш PR.

Commit messages

Щоб коміт-меседжі легко читались і було зрозуміло, що там було зроблено, пропоную розглянути ідею Conventional Commits.

Це ідеологія, яка формалізує структуру коміт меседжів. Детальніше про неї можна прочитати тут. Її сенс полягає в тому, щоб у кожному коміті було вказано тип коміта, скоуп, до якого він відноситься, короткий опис. Також для читабельності визначені і вимоги до форматування таких меседжів.

Якщо ви, як і я, користуєтесь IntelliJ IDEA чи Android Studio, то хороша новина в тому, що є готовий плагін, який буде підсвічувати вам помилки в структурі та форматуванні коміт меседжів.



Також у нього є зручний Wizard, в якому ви просто вводите значення в поля вводу і він форматує код за вас.



Branch protection rules

Це дуже важлива частина PR, де ви можете налаштувати правила для різних гілок у вашому git-репозиторії. А особливо нас будуть цікавити налаштування, які стосуються пул-реквестів.

Code Owners

Якщо у вас досить великий проєкт і над ним одночасно працюють декілька команд, то скоріш за все, кожна з них має свою зону відповідальності. Щоб рев’ю обов’язково робили люди, які відповідають за компонент, в який ви вносите зміни, пропоную налаштувати Code Owners.

Це файл, в якому прописані користувачі чи групи користувачів, які відповідають за окремі частини системи. Також в branch protection rules ви можете встановити обов’язкову перевірку коду Code Owner’ами:

Ми можемо встановити, наприклад, 2 обов’язкових апруви і поставити галочку навпроти Require review from Code Owners.

Pull request template (checklist)

Перед тим, як дивитись PR, було б зручно, щоб його автор спочатку перевірив сам себе. Для цього потрібно скласти список підготовчих дій, які повинен виконати кожен, хто створює пул-реквест. В гуглі можна знайти безліч прикладів таких чеклістів. Вам треба обрати, що саме важливо для вас і вашого проєкту і внести це в список. Ось, наприклад, список з мого демо-проєкту:

Шаблон такого чекліста пишеться за допомогою Markdown. Ось приклад мого:

## Checklist:
### Code quality
- [ ] My code follows the style guidelines of this project
- [ ] My changes generate no new warnings
- [ ] I have commented my code, particularly in hard-to-understand areas
### Testing
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have performed a self-review of my own code
### Merge conflicts
- [ ] Any dependent changes have been merged and published in downstream modules

Ви можете встановити застосунок з маркетплейсу, який буде перевіряти, чи всі галочки проставлені, і відображати статус заповнення чек-листа. У разі невиконання всіх умов, змінить її колір з зеленого на сірий:

Як тільки всі умови з чек-листа будуть виконані, кнопка стане зеленою:

Насправді це не обов’язково повинен бути чек-лист. Часто використовують шаблони формату «питання-відповідь» або змішаного:

Ось такі невеличкі поради щодо автоматизації перевірки pull requests. Сподіваюсь, вам було корисно. Буду радий прочитати в коментарях, що ви використовуєте на своїх проєктах, що працює для вас, а що ні.

👍ПодобаєтьсяСподобалось22
До обраногоВ обраному11
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Чому lint а не spotless?

Бо проект на Android. Треба перевіряти специфічні речі. Android Lint з цим справляється нормально. Разом з ним використовую detekt для Kotlin.

— [ ] My code follows the style guidelines of this project
— [ ] My changes generate no new warnings

Також автоматизується статичними аналізаторами, типу сонару. Мінуси — коштує грошей.

— [ ] I have added tests that prove my fix is effective or that my feature works

Автоматизується ковередж-тулами з мінімальним порогом покриття

— [ ] New and existing unit tests pass locally with my changes

автоматизується білдом на комміт з запуском тестів

— [ ] Any dependent changes have been merged and published in downstream modules

Було б добре, якщо воно не збілдилося, якщо нема артифактів з даунстрім модулів.
Якзо почав автоматизовувати — важко зупинитися.
Ніколи не довіряй девелоперу роботу машини.

Тип таски добре може бути видно з бренч неймінг конвеншн.
/feature/team1-1234_Add_login
/bugfix/team2-4321_fix_login
Це також дозволяє лінкувати джиру з гітхабом і автоматично апдейтити статус тасок

Як видно на скріншотах і далі по тексту, в мене тести запускаються в воркфлоу. Так само і результати лінтера виводяться і тд

Чеклист більше для того, шоб рев’юер не лазив по пул реквесту і не шукав чи нічого не забули. Типу як нагадування для автора пул реквестах перевірити себе перед тим, як відправляти його на рев’ю

Крутезна стаття, залишилось зібрати усю інформацію до купи та реалізувати на власному проекті)

Збирайте, реалізовуйте та й пишіть нам наступну крутезну статтю ;)

Підписатись на коментарі