ある日のこと
「さーて、今日もGitHubにコミットをプッシュしていくぞ〜〜」
「ローカルでコミットした変更をgit push origin main
して、、」
「github.com
のレポジトリを見にいくと、、お!反映されているな!Initial Commit
ってちゃんと出ているぜ!」
「そういえば、いつも気にしていなかったけどActions
タブってのがあるな?これってなんだ?」
これがGitHub Actionsです。レポジトリごとに用意されていて、Actionsタブから管理、確認することができます。
「ほ〜。GitHub Actions
っていうのか・・なんのためにあるんだろう?ここで何ができるの?」
GitHub Actionsとは
GitHub ActionsはGitHubがサービスの一環として提供する、ワークフロー自動化サービスです。
簡単に言えば、「開発している時にやりたいことを、GitHub上で自動化できるサービス」です。
「なるほど・・つまりGitHubをコード置き場として使うだけじゃなくて、手元でやるような、よくある手順をGitHub側で自動で実行できるんだ?」
「なんか便利そうだけど、自動化って難しそうだし、あんまり役立つことのイメージがつかないなあ」
触ってみよう
簡単にGitHub Actionsに触れてみましょう!
先ほど触っていたレポジトリにGitHub Actionを導入するところから始めてみます
まず、Actionsタブを開きます。レポジトリになにもGitHub Actionsが設定されていない場合、次のような画面が出てきます。既に何かしらのワークフローが存在する場合は左上のNew Workflow
から同じような画面を開くことができます。
下の方にある簡単設定ボタンを使ってもいいのですが、今回はset up a workflow yourself
を押してみましょう。
このような画面が出てきて、何やらyamlファイルを編集する画面になります。とりあえず中身は読み飛ばして、コミットしてみましょう。
適切なコミットメッセージを追加し、Commit new fileを押すと、GitHub上で新しいコミットを追加することができます。
こうすることで、始めてのGitHub Actionを走らせることに成功しました!
Actionsタブを改めて開いてみましょう!
先ほど定義したアクションが無事に動いていることがわかります。
「なんかよくわからないけど、思ったより簡単にワークフローを作れたみたいだ」
「何が起こっているのかはよくわからないけど、左の緑のチェックマークが気持ちいい!」
「中で何が起こっているのかもうちょっと詳しく見てみたいな」
中身の概要
先ほど読み飛ばしたmain.ymlの中身を見てみましょう!
コード
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
# Runs a single command using the runners shell
- name: Run a one-line script
run: echo Hello, world!
# Runs a set of commands using the runners shell
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
このyamlファイルは大まかに分けると二つのパートで構成されています。
- ワークフローについての情報を定義するパート
- ワークフローの中身を定義するパート
ワークフローについての情報を定義するパート
ここの部分が該当します。
name:
ではワークフローの名前を定義しています。ここで定義した名前がActionsタブにも表示されます。
on:
では、どのタイミングでこのワークフローが走るかが定義されています。今回のワークフローでは
- 何かしらのコミットがGitHubに追加された時(
push
)
・ただし、mainブランチのみ(branches: ["main"]
) - 新しくプルリクエストが作成された時(
pull_request
)
・ただし、mainブランチのみ(branches: ["main"]
) - 明示的に走って!とボタンを押したタイミング(
workflow_dispatch
)
の三種類のタイミングで走るよ、と定義されています。
「なるほど、ここでタイミングが定義されているのか」
「さっき走っていたワークフローのrunをクリックしてみてみると・・」
「確かにmainブランチに対してのon: push
で走った、って書いてある!」
「つまり、GitHub Actionはyamlファイルの中身を読んで、該当するケースに当たったら処理を始めてくれるのね」
ワークフローの中身を定義するパート
ワークフローでどういう処理が行われるかはyamlの後半部分、
ここが該当します。
jobs:
では一つのワークフローの中に含まれる一つ一つの一連の処理を定義します。現在のワークフローにはbuild:
というジョブが一つ存在しています。
(ジョブ名):
の中にそれぞれのジョブの設定、処理内容を書きます。
-
runs-on:
では、どのような仮想環境でジョブの処理を動かすかを定義します。今回はubuntuの最新版を使う、となっています。 -
steps:
に具体的な一つ一つの処理を書いていきます。全部で3つの処理をしています。- 最初のステップでは、外部で定義された別のアクションを呼び出しています。このステップでは
actions/checkout@v3
つまり、自分自身のレポジトリのデータを読み込む、という処理が走っています。このステップから始まるワークフローが一番多いと思われます。(こういった外部呼び出しされるアクションはGitHubで公開されています→actions/checkout) - 次のステップでは単一のシェルスクリプト(
echo Hello, world
)が走っています。 - 最後のステップでは、複数のシェルスクリプトが走っています。
- 最初のステップでは、外部で定義された別のアクションを呼び出しています。このステップでは
これらのジョブ、ステップはActionsタブから実行結果を確認することができます。
緑のチェックマークが付いている場所をクリックしてみるとこのような画面になります。
build
というジョブが見えますね。これをさらにクリックすると、このような画面が表示されます。
前後にセットアップ処理などが含まれていますが、ちゃんと定義した三つのステップが実行されているのが見えます。
「ふむふむ、一つのワークフローの中にはジョブというものとその中で動くステップというものがあるのか」
「ためしにジョブを一個増やしてみよう」
「build
ジョブの下に新しくhoge
ジョブを作ってみた!これでコミットしてみると、、」
「おお!ジョブが増えたぞ!しかも並列に実行されてるっぽい!」
「こうやって並列で動くジョブと順番に動くステップを組み合わせて一つのワークフローとまとめていくんだな。完全に理解した!」
GitHub Actionsでできることの紹介
「でもコミットごとにあいさつをしてもらっても、ちょっと、、って感じだな。。」
「具体的に何ができるのか知りたいな」
このパートでは、逆引き的にGitHub Actionsで達成できることを紹介していきます
この記事では例としてPythonを使ったプロジェクトでの解説を行いますが、Pythonに限らず他の言語でも共通して利用できるような内容にフォーカスしています
自動テストの実行
「最近、プロジェクトに自動テストが導入されたんだよな」
「手元で実行できるけど、これが自動的に動いてくれたら便利かも」
プロジェクトに自動テストが導入されると、コードの動作についての結果や品質を簡単に担保することができるようになります。この自動テストをローカルだけでなく、GitHub Actions上でも実行することで、継続的にコードの動作をモニタリングすることができるようになります。例として以下のような簡単なpythonのプロジェクトを考えてみます。
project/
src/
main.py
tests/
test_main.py
pyproject.toml
src/main.py
import sys
def add(a: int, b: int) -> int:
return a + b
def main():
if len(sys.argv) <= 2:
print("a and b is required")
_, a, b, *_ = sys.argv
print(add(*map(int, (a, b))))
if __name__ == "__main__":
main()
src/test_main.py
from unittest import TestCase
from src.main import add
class MainTestCase(TestCase):
def test_add(self):
self.assertEqual(5, add(2, 3))
この自動テストを実行すると、以下のようになります
これをGitHub Actionsに組み込んでみましょう!
test.yml
を作成してみます
name: Run Tests
on:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Python Setup
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Setup Poetry
uses: Gr1N/setup-poetry@v7
- name: Run poetry install
run: poetry install
- name: Run tests
run: poetry run python -m pytest
これをプッシュすると次のように、Run Tests
というワークフローが新しく実行されていることを確認できます。
中身をみると、正しくローカルで実行したものと同じ結果が得られていますね!
「おお〜!こうすれば手元で毎回実行しなくても、プッシュした時に毎回自動テストを走らせてくれるんだ!」
「これは便利だ」
「なるほど、手元で実行するときはセットアップが終わってるからコマンド一発だけど、GitHub Actionsだとまっさらな状態からスタートするから、事前準備が必要なんだね」
「まずレポジトリをチェックアウトして」 ...(1)
「Pythonをセットアップして」 ...(2)
「パッケージマネージャのPoetryをインストールしたら」 ...(3)
「プロジェクトに必要なパッケージをpoetry install
で入れて」 ...(4)
「...最後にpytestを走らせているのね。面倒臭いけど、考えてみると当たり前のステップだね」
「新しいメンバーの環境構築を手伝ってあげている感覚かも」
プルリクエストにアノテーションをつける
「そういえば自動テストと一緒にリンターも導入されたんだよな」
「コードのフォーマットをみんなで統一してくれるすごいやつなんだけど、こいつを走らせられると便利かもな」
「あ〜でも、走らせて落ちたところでActionsの中身まで確認しに行ってどこに問題があるのか探すのは結構骨が折れるかも・・」
そんな悩みも解決できます。Annotationsという仕組みを使うことで、コードに対してコメントのような形でツールの処理結果を追加することができます。
先ほどと同じようにlint.yml
を作成します。Pythonのflake8というリンターを使い、コードの問題点をチェックするとします。
name: Lint
on:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Python Setup
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Setup Poetry
uses: Gr1N/setup-poetry@v7
- name: Run poetry install
run: poetry install
- name: Lint
run: |
echo "::add-matcher::.github/workflows/flake8-problem-matcher.json"
poetry run python -m flake8 .
この時のpoetry run python -m flake8 .
の実行結果は次のようになります。
$ poetry run python -m flake8 .
./src/main.py:9:24: E222 multiple spaces after operator
./src/main.py:9:31: E261 at least two spaces before inline comment
このように問題があるファイルと行番号・列番号とエラーの詳細を出力してくれますが、GitHub Actionsではこのチェックを自動化しつつ、Annotationsを使うことで問題のある箇所をコード上で確認できるようになります。
Problem Matcherという機能を使います。この機能は、出力されるメッセージをパースし、条件にあった場合にその行をハイライトした上で、Annotationsに変換してくれる、というものになります。
今回はこのようなファイルを用意します。
{
"problemMatcher": [
{
"owner": "flake8",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+?):([0-9]+?):([0-9]+?): ([A-Z][0-9]+) (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 5,
"code": 4
}
]
}
]
}
先ほどのエラーメッセージを正規表現でマッチさせ、ファイル名、行番号、列番号、メッセージ、エラーコードに当たる箇所をグループマッチの番号で指定します。
そして、次の行を足すことで、このmatcherを追加してね、という指示を出すことができます。
- name: Lint
run: |
+ echo "::add-matcher::.github/workflows/flake8-problem-matcher.json"
poetry run python -m flake8 .
これらを設定したものをプッシュし、新しいプルリクエストを開いてみた結果がこちらになります。
まず、Annotationsの中に、flake8のエラーの結果が含まれていることがわかると思います。さらに詳細を見てみましょう。
正しくflake8の出力結果がwarningとして解析されていることがわかります。そしてこの状態でプルリクエストのファイル一覧を開くと・・・
問題のある箇所が一目瞭然にコメントが付けられます。
「おおお!これを使えばActionの実行を意識しなくても、レビューするときに簡単に問題のある箇所を見つけられる!」
「これは自動テストとか静的解析にも応用できそう・・」
ここでは簡単にアノテーションを作成できることを示すためにProblem Matcherを利用しましたが、今回で言うと https://github.com/tayfun/flake8-your-pr など、既製のアクションが用意されている場合も多いので一度検索してみてください!
Artifactsの作成
テストカバレッジ
「自動テストが書いてあると、動作確認が楽だなあ〜」
「あれ?テストは通ってるのに新機能を使ってみたら落ちちゃった・・」
「どれどれ、うわ!新機能作ったはいいけどテストを書いてなかったんだ!」
「あちゃ〜、気づかなかったなあ〜」
こんなことは自動テストを書いていてもよく起こります。このような事態を回避するために使えるのがテストカバレッジです
「テストカバレッジ・・?そういえばテストを書くときにそんなことも言われていたような・・・」
テストカバレッジ(あるいは単にカバレッジとも)はテストを実行したときに、具体的にテストされるコードのどの部分が実行されたかを確認できる仕組みです。
$ poetry add -D pytest-cov
$ poetry run -m pytest --cov --cov-report=html
このように実行すると、テスト中にどこの処理が走ったかを可視化することができます
「おおお!こうすることでaddはちゃんとテストが通っているのにmulは通っていないことが一目瞭然だ!」
「掛け算を期待する場面なのに足し算のままになっているのね・・テストを書かなきゃ!」
GitHub ActionsのArtifacts
「でも、これを自動化しようとすると難しいよな・・」
「CIの実行画面でも見にくいだろうし、Annotationsだとちょっとたくさん書き出されすぎて、それも辛そう・・」
「できれば吐き出されるhtmlファイルをそのままみたいな〜」
という要望もArtifactsを使うと実現できます。Artifactsを使うことでJobの成果物をActionsの結果に紐づけて後からダウンロードできるようになります。
coverage.ymlを追加してみましょう
name: Collect Coverages
on:
pull_request:
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Python Setup
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Setup Poetry
uses: Gr1N/setup-poetry@v7
- name: Run poetry install
run: poetry install
- name: Collect Coverages
run: poetry run python -m pytest --cov --cov-report=term --cov-report=html
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: coverage
path: htmlcov/
このように書くことで、pytestが生成してくれるhtmlcov
というディレクトリをCI上でアップロードしてくれます。
「お、coverageっていうArtifactができてる!こいつをクリックすると...」
「zipファイルがダウンロードされた!」
「中身を見るとちゃんとさっき手元でテストを走らせたときに生成された内容と一致してる!」
「これによって、生成物がある処理もCI上で走らせて後からスイスイダウンロードできるんだ!めっちゃ便利!」
リリース作成・コミット追加
To Be Written
アクション・ワークフローの作成・呼び出し
To Be Written
まとめ
「なるほど、GitHub Actionsを使うことで、何回も走らせることになる処理を人間の手によらずに効率的に回せるようになるんだ」
「そう考えるといろんな使い道が思いつくかも!」
とても簡単に導入できるCIサービス、GitHub Actionsを使いこなし、より快適な開発体験を作ってみましょう!
レポジトリ
今回使ったデモ用のレポジトリはこちらになります