GitHub Actionsでテストを自動化させてみた（Rspec ,Rubocop）

## はじめに
個人開発にて、`GitHub Action`を使用して`Docker`環境下におけるテストの自動化を行いました。
自動テストを書いた後に、「なんか便利そう」という気持ちで実装を始めましたが、必要な設定の全貌が掴めず、時間を大幅に溶かしてしまいました…💦
これから実装する方に向けて、コードを見ながら流れを掴んでいただけるように記載していきます！

:::note warn
私はプログラミング学習中で、初学者です。
内容に間違いがあるかもしれませんので、もし間違いがあれば教えていただけると幸いです。
:::

## GitHub Actionsとは
GitHub Actionsは、あらかじめ定義した処理と条件の組合せを自動化するGitHubの機能です。
今回は、リポジトリへのプッシュをトリガとして、YAML ファイルに定義したテスト（Rspec）と体裁チェック（Rubocop）を自動で実行します。

https://docs.github.com/ja/actions/about-github-actions/understanding-github-actions

## 環境
* Ruby 3.2.3
* Ruby on Rails 7.1.3
※前提として、RspecやRubocopの実行に必要なGemや設定などは導入済みであるものとします。

## 手順
### 1. GitHub Actionの導入
### 2. main.ymlの作成((1)~(9)に分けて説明)


## 1. GitHub Actionの導入
まず、`Actions`タブを開くとこの画面が出てくるので、`set up a workflow yourself`を押して下さい。
[![Image from Gyazo](https://i.gyazo.com/8bf4104971339be02fea0603ac297790.png)](https://gyazo.com/8bf4104971339be02fea0603ac297790)

このような画面が出て来るので、YAML ファイルを編集していきます。
[![Image from Gyazo](https://i.gyazo.com/0d6bdb529e323a360bc0f6c0c9b2209f.png)](https://gyazo.com/0d6bdb529e323a360bc0f6c0c9b2209f)

## 2. main.ymlの作成
今回作成するRspec（Capybara）、Rubocopを自動化する`main.yml`、`database.yml.ci`は以下のように記述しています。
非常に長くなってしまったので、コードの後ろに(1)等の数字を付けて、説明していきます！

```ruby:.github/workflows/main.yml
name: Run rspec, rubocop          (1)                

on:                               
  push:                           (2)

env:
  RENDER_DB_URL: ${{secrets.RENDER_DB_URL}}      (3)

jobs:
  rspec:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    services:
      postgres:
        env:
          POSTGRES_PASSWORD: password
          POSTGRES_DB: test
        image: postgres
        ports:
          - 5432:5432
      chrome:
        image: seleniarm/standalone-chromium:latest
        ports:
          - 4445:4444            (4)

    steps:
      - name: Checkout code
        uses: actions/checkout@v2     

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Set up Chrome
        uses: browser-actions/setup-chrome@v1
        with:
          chrome-version: 120
          install-dependencies: true            (5)

      - name: Cache node modules
        uses: actions/cache@v2
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-node-                      (6)

      - name: Bundler and gem install
        run: |
          gem install bundler
          bundle install --jobs 4 --retry 3 --path vendor/bundle

      - name: Yarn install
        run: yarn install --check-files                 (7)

      - name: Check for port conflicts and set CAPYBARA_PORT
        run: |
          PORT=4444
          if lsof -i:$PORT; then
            echo "Port $PORT is in use, killing the process"
            lsof -ti:$PORT | xargs kill -9
          fi
          CAPYBARA_PORT=3001
          echo "CAPYBARA_PORT=$CAPYBARA_PORT" >> $GITHUB_ENV
          echo "DEBUG: Capybara port set to $CAPYBARA_PORT"     (8)

      - name: Database create and migrate
        run: |
          cp config/database.yml.ci config/database.yml
          bundle exec rails db:create RAILS_ENV=test
          bundle exec rails db:migrate RAILS_ENV=test

      - name: assets precompile
        run: bundle exec rake assets:precompile RAILS_ENV=test

      - name: Run tests
        run: |
          bundle exec rspec
        env:
          SELENIUM_DRIVER_URL: http://localhost:4445/wd/hub
          CAPYBARA_PORT: ${{ env.CAPYBARA_PORT }}              (9)

  rubocop:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Run rubocop
        run: bundle exec rubocop
```
```ruby:config/database.yml.ci
test:
  adapter: postgresql
  encoding: unicode
  username: postgres
  password: password
  host: localhost
  database: myapp_test
  ```


以下に各コードの説明をしていきます。（GitHub公式ドキュメントは[こちら](https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions)）
**(1)`name: Run rspec, rubocop`**
`name:`は、リポジトリの [アクション] タブに表示されるワークフローの名前になります。 

**(2)`on: push:`**
`on:`は、GitHub Actionsをいつ動かすのかを設定するものになります。今回は、プッシュ時に実行するよう設定します。

**(3)`env: RENDER_DB_URL: ${{secrets.RENDER_DB_URL}} `**
`env:`は、ワークフロー全体に環境変数を定義することが出来、GitHub公式ドキュメントの[こちら](https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/variables#creating-configuration-variables-for-a-repository)を参考にして、`RENDER_DB_URL`をリポジトリ上に定義しています。

**(4)**
```ruby
jobs:
  rspec:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    services:
      postgres:
        env:
          POSTGRES_PASSWORD: password
          POSTGRES_DB: test
        image: postgres
        ports:
          - 5432:5432
      chrome:
        image: seleniarm/standalone-chromium:latest
        ports:
          - 4445:4444
```
* `jobs:`は、ワークフローの一連の処理を定義します。今回は、`rspec:`と`rubocop:`の二つの処理を定義しています。
* `services:`は、ワークフローでDBに接続してテストを行うために必要なサービスコンテナを表しています。
**サービスコンテナ**とは、GitHub Action上で使う専用のDockerコンテナ（今回はPostgreSQL）のことで、専用のコンテナを立ち上げて、そのコンテナを利用してテストを行います。
* `postgres:`は、自身のリポジトリ上の`config/database.yml`、`compose.yml`に合わせて記載をしています。
* `chrome:`は、自身のリポジトリ上の`compose.yml`に合わせているものの、ポート番号を`4444:4444`と設定したところ、実行時に`Address already in use - bind(2) for "10.1.0.91" port 4444 (Errno::EADDRINUSE)`とエラーが表示されているため、`4445:4444`と設定しています。

**(5)**
```
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Set up Chrome
        uses: browser-actions/setup-chrome@v1
        with:
          chrome-version: 120
          install-dependencies: true
```
* `steps:`は、`jobs:`中にどのような流れで、下に定義している`name:`を実行するかを設定しています。
* テストを実行する環境を作るため、`actions/checkout@v2`で自身のリポジトリをダウンロードし、`ruby/setup-ruby@v1`でrubyを実行出来る環境を作り、`browser-actions/setup-chrome@v1`でテストが実行出来るようにchromeブラウザをダウンロードしています。

**(6)**
```
      - name: Cache node modules
        uses: actions/cache@v2
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-node- 
```
`actions/cache@v2`を使って、今回は`node_modules`の依存関係をキャッシュして、次回以降に依存関係を再利用出来るため、次回以降の実行時間が短縮されます。

<details><summary>キャッシュアクションを使用する際の流れ</summary>

まず、指定した`key`に基づいて完全に当てはまるキャッシュを見つけた場合、`path`にキャッシュされたファイルが復元されます。また、別のブランチからキャッシュを復元する場合等の理由で、部分的に一致する可能性がある場合は、`restore-keys`を使用して、`key`での指定同様、`path`にファイルが復元されます。（GitHub公式ドキュメントの[こちら](https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#using-the-cache-action)を参照）

</details>

**(7)**
```
      - name: Bundler and gem install
        run: |
          gem install bundler
          bundle install --jobs 4 --retry 3 --path vendor/bundle

      - name: Yarn install
        run: yarn install --check-files
```
テストを実行する環境を作るため、yarnをインストールし、gem のバージョン等を管理する bundlerをインストールしています。

**(8)**
```
      - name: Check for port conflicts and set CAPYBARA_PORT
        run: |
          PORT=4444
          if lsof -i:$PORT; then
            echo "Port $PORT is in use, killing the process"
            lsof -ti:$PORT | xargs kill -9
          fi
          CAPYBARA_PORT=3001
          echo "CAPYBARA_PORT=$CAPYBARA_PORT" >> $GITHUB_ENV
          echo "DEBUG: Capybara port set to $CAPYBARA_PORT"
```
GitHub Actionsの実行時に、`main.yml`で定義をしているchromeと、リポジトリ上の`spec/rails_helper.rb`で定義しているCapybaraのポート番号が被っていることによるエラーが発生していたので、以上の(8)を追記をしましたが、エラーが発生していなければ、記載は不要かもしれません。

**(9)**
```
      - name: Database create and migrate
        run: |
          cp config/database.yml.ci config/database.yml
          bundle exec rails db:create RAILS_ENV=test
          bundle exec rails db:migrate RAILS_ENV=test

      - name: assets precompile
        run: bundle exec rake assets:precompile RAILS_ENV=test

      - name: Run tests
        run: |
          bundle exec rspec
        env:
          SELENIUM_DRIVER_URL: http://localhost:4445/wd/hub
          CAPYBARA_PORT: ${{ env.CAPYBARA_PORT }} 
```
テストを実行する環境を作るため、`database.yml.ci`で記載しているテスト実行用のデータベースファイルの内容をコピーし、データベースを作成して、マイグレーションを実行した後、アセット（画像、CSS、JavaScriptなど）をコンパイルしてテストを実行しています。

`rubocop:`の内容は、上記の`rspec:`の説明内容と被るため、割愛させていただきます。

以上の内容をコミットして、GitHub Actionsでの設定は終了となります！🎉🎉

## おわりに
文字に起こしてみることで、実装する際に理解が不足していた点を気づくことができました。
未だ勉強不足な点が多く、引き続き学習していきたいと思います。

お忙しい中、最後まで読んでいただきありがとうございました。



## 参考資料

https://docs.github.com/ja/actions/about-github-actions/understanding-github-actions

https://zenn.dev/ryouzi/articles/cd6857c08e60e7

https://qiita.com/s3i7h/items/b50ceb0008edc3c0312e

https://zenn.dev/sue/articles/378fb1689f7289e556e9#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A

https://eng-blog.iij.ad.jp/archives/18963