GitHub Actionsを用いた自動テストの実行と結果集計

## 1. はじめに

[GitHub Actions](https://docs.github.com/ja/actions)を用いて自動テストの実行と結果の集計を行う方法を説明します。

具体的には、ソースコードがGitHubへ`push`されたタイミングで、pythonで書かれたテストを`pytest`を使って実行し、GitHub上に下図のサマリを表示します。

![summary1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/8b823f2b-72e4-a4f8-d277-1305976192a5.png)

今回、GitHub Actionsを初めて使ったので、学習のためにGitHub Actionsの基本についても触れています。

## 2. GitHub Actionsとは

GitHubのCI/CDツール。

push, pull requestなどのGiHub上のアクティビティやスケジュールした時間、外部イベントをトリガーとして、ワークフローを作成できます。

**特徴**

- Linux, macOS, Windowsすべてのコンテナに対応
- Node.js, Python, Java, Rubyなど、様々な言語に対応
- 複数のジョブを並行してビルド可能
 
### 用語

- **ワークフロー**
  - トリガーと1つ以上のジョブで構成される手順
  - リポジトリ内の`.github/workflows`ディレクトリにYAML形式で定義
- **イベント**
  - ワークフローをトリガーする特定のアクティビティ
  - 外部イベントをトリガーしたい場合は、webhookを使用する
- **ジョブ**
  - 一連のステップ
  - 1つのワークフローに複数のジョブが存在する場合は、並行して実行される
- **ステップ**
  - ジョブでコマンドを実行できる個々のタスク
  - アクションまたはシェルコマンドで設定できる
- **アクション**
  - ワークフローを作成するための最小単位のコマンド
  - 独自のアクションを作成することも、オープンソースのアクションを使用することも可能
- **成果物（Artifacts）**
  - ワークフロー実行中に生成されるファイル

### ワークフローの構造

GitHub Docsにあるサンプルを用いて各項目を説明します。それぞれの詳細は、[GitHub Actionsのワークフロー構文](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)が詳しいです。

```yml
name: learn-github-actions
on: [push]
jobs:
  check-bats-version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
      - run: npm install -g bats
      - run: bats -v
```

| 項目名                       | 説明                                                                          |
| ---------------------------- | ----------------------------------------------------------------------------- |
| `name:` | ワークフローの名前。GitHubのActionsに表示される。なんでもよい                 |
| `on:`                        | 必須。トリガーのイベント。上の例では`push`する度にジョブが実行される          |
| `jobs:`                      | ワークフローで実行されるジョブのグループ。複数のジョブの集合                  |
| `check-bats-version:`        | ジョブのID。英字または`_`で始める必要があり、英数字と`-`、`_`しか使用できない |
| `runs-on`                    | 必須。ジョブが実行されるマシンの種類。OSバージョンを指定することもできる      |
| `steps:`                     | ジョブで実行されるステップのグループ                                          |
| `- uses:`                    | 使用するアクション                                                            |
| `- run:`                     | 実行するシェルコマンド                                                        |


以下の階層をイメージするとよいと思います。

```yml
name: ワークフローの名前
on: トリガーのイベント
jobs:
  ジョブのID
    runs-on: ジョブが実行されるマシンの種類
    steps:
        - uses: アクション
        - run: シェルコマンド
```

### ワークフローの作り方

ワークフロー（YAMLファイル）を作成するには大きく２つの方法があります。完成するファイルは同じなので、使いやすい方でOKです。

**方法①テキストエディタ**

VSCodeなどのテキストエディタでリポジトリ内の`.github/workflows`ディレクトリにYAMLファイルを作成する方法。VSCodeの場合、[GitHub Actions](https://marketplace.visualstudio.com/items?itemName=cschleiden.vscode-github-actions)という拡張機能を使うとよい。

**方法②GitHub**

GitHubのActionsタブ＞「New workflow」＞「set up a workflow yourself」で新規作成。テンプレートに沿って作成することができ、下図の[Marketplace](https://github.com/marketplace?category=&query=&type=actions&verification=)からアクションを検索しやすい。

![market.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/2ac13a94-c1c6-b0bc-1c3d-37b459ac5fa6.png)



## 3. 自動テストの実行と結果の集計

今回は、自動テスト（pytest）の実行と結果の集計を行うワークフロー（YAMLファイル）を作成します。

**大まかな流れ**

1. トリガーの指定
2. ジョブの条件
3. python環境のセットアップ
4. 自動テストを実行する
5. 自動テストの結果をGitHub上に表示する
6. ワークフローの実行と確認

先に、YAMLファイルの完成版は以下の通り。各トリガーやステップの詳細は後述します。

```yaml:action.yml
name: Run pytest
on: [push]

jobs:
  pytest:
    runs-on: ubuntu-latest

    steps:
      # リポジトリをチェックアウト
      - name: Checkout
        uses: actions/checkout@v2

      # Pythonの環境をセットアップ
      - name: Set up Python 3.7
        uses: actions/setup-python@v2
        with:
          python-version: 3.7

      # pytestをインストール
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest

      # pytest -> JUnit xml形式で結果を出力
      - name: PyTest
        run: |
          python -m pytest test --junit-xml results/pytest.xml
        continue-on-error: true

      # テスト結果の表示
      - name: Upload Unit Test Results
        if: ${{ always() }}
        uses: actions/upload-artifact@v2
        with:
          name: Unit Test Results (Python 3.7)
          path: results/*.xml

      - name: Download Artifacts
        if: success() || failure()
        uses: actions/download-artifact@v2
        with:
          path: artifacts

      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        with:
          junit_files: artifacts/**/*.xml
```
※2022/10/21 `files`だとWarningが出たので`junit_files`に修正。

### トリガーの指定

`push`したタイミングで自動テストを実行したいので、`push`を指定します。

```yaml
on: [push]
```

他にも、

- `Pull Request`のタイミング
- `Pull Request Review`のタイミング
- ブランチの指定
- タグの指定

などをトリガーにできます。その他のイベントは[ワークフローをトリガーするイベント](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows)を参照。

```yaml
# 例：mainブランチでpushもしくはPull Requestが行われたタイミングで実行
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
```

### ジョブの条件

ジョブ（pytest）の実行マシンを指定します。今回は、`ubuntu-latest`を使用します。`macOS`、`Windows`も使用でき、その場合は「Publish Unit Test Results」のアクションの書き方が異なります。

```yaml
jobs:
  pytest:
    runs-on: ubuntu-latest
```

### python環境のセットアップ

ここからはステップの記述です。

自動テストを実行するためにpython環境をセットアップします。今回は`python-version: 3.7`を使っています。

**使用するアクション**

- [Checkout](https://github.com/marketplace/actions/checkout)
    - ワークフローがリポジトリにアクセスできるように、リポジトリをチェックアウトする
- [Setup Python](https://github.com/marketplace/actions/setup-python)
    - python環境をセットアップする

```yaml
steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Set up Python 3.7
      uses: actions/setup-python@v2
      with:
        python-version: 3.7

    - name: Install dependencies
      run: |  # コマンドを複数行記述したい場合は「|」を使う
        python -m pip install --upgrade pip
        pip install pytest
```

### 自動テストを実行する

自動テストの実行とJUnit XML形式の結果の出力をコマンドで記述します。また、ステップが失敗しても次のジョブへ進むために、`continue-on-error: true`を設定します。

```yaml
    - name: PyTest
      run: |
        python -m pytest test --junit-xml results/pytest.xml
      continue-on-error: true
```

### 自動テストの結果をGitHub上に表示する

自動テストの結果をGitHubのArtifacts上にアップロードし、結果を表示します。

**使用するアクション**

- [Upload a Build Artifact](https://github.com/marketplace/actions/upload-a-build-artifact)
  - Artifactをアップロードしジョブ間でデータを共有する
- [Download artifact](https://github.com/marketplace/actions/download-artifact)
- [Publish Unit Test Results](https://github.com/marketplace/actions/publish-unit-test-results)
  - テスト結果を分析し結果をGitHub上で表示する。JUnit XML形式に対応しており、Linux、macOS、Windows上で動作する

```yaml
      # テスト結果の表示
      - name: Upload Unit Test Results
        if: ${{ always() }}
        uses: actions/upload-artifact@v2
        with:
          name: Unit Test Results (Python 3.7)
          path: results/*.xml

      - name: Download Artifacts
        if: success() || failure()
        uses: actions/download-artifact@v2
        with:
          path: artifacts

      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        with:
          junit_files: artifacts/**/*.xml
```

ArtifactsはActionsタブの以下から取得することができます。

![artifacts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/c939518f-14b5-f4ba-cd75-f1740808ad6c.png)

### ワークフローの実行と確認

何かしら変更を加えて`push`すると、ワークフローが実行されます。

実行したワークフローは、GitHubのActionsタブまたはcommit履歴から閲覧できます。

▼GitHubのActionsタブ（コミットメッセージは適当です）

![workflows.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/f5599ecc-13d7-ce5f-5e62-09968c19590c.png)

ワークフローを選択すると、実行結果のサマリを確認することができます。

![summary2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/988f5a2a-f98f-7d88-8299-20c12438fde4.png)

## 4. Tips: Selenium + pytestを実行する

Seleniumを使った自動テストを実行したい場合も前述のワークフローを流用できますが、２つポイントがあります。

- `webdriver_manager`を用いて、ブラウザバージョンに対応するdriverを自動で適用する
- Chromeを`"--headless"`モードで実行する

まず、`Install dependencies`のステップに以下のコマンドを追加し、[webdriver-manager](https://pypi.org/project/webdriver-manager/)をインストールします。

```yaml
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pytest
        pip install webdriver-manager
```

テストスクリプト内での使い方は次の通り。

```python
# Selenium 4
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

options = Options()
options.headless = True
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
```

```python
# Selenium 3の場合
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=options)
```

※`"--headless"`をoptionに設定せずにGitHub Actionsを実行すると、次のエラーメッセージが表示されてしまいます。

```sh
selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally.
```

あとは通常通りGitHub Actionsを実行すればOKです。

## 5. 所感

サードパーティ含むアクションのライブラリが充実していて、思ったよりもゴリゴリコマンドを書く必要がない印象を受けました。また、GitHub Actionsに関するドキュメントが整備されていたのがありがたかったです。

CI/CDツールといえばJenkinsやCircleCIのイメージが強かったのですが、GitHubでソースコード管理をしている場合は十分候補になりそうです。

## 6. 参考

- [GitHub Actionsのワークフロー構文 - GitHub Docs](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)
- [Github Actionsの使い方メモ - Qiita](https://qiita.com/HeRo/items/935d5e268208d411ab5a)
- [Github Actionsを使って自動テストをした話 - Qiita](https://qiita.com/dosukoi_android/items/a3464548b3aa293c62dd)