Github Actions に RuboCop, Erb Lint, RSpec( Model, System ) を 導入 (Docker + Rails 8.0.0 環境)

# 概要

今回は、現在開発中のWebアプリにGithub ActionsによるCIを実装した際、Rubocop, Erb Lint, RSpecの導入に２日ほど費やしてしまったため、今後のために備忘録としてまとめたものになります。
初学者の身であり、Github Actionsへの理解も甘いため、間違って認識している点もあるかと思いますので、ご指摘いただけると幸いです。

# Github ActionsによるRuboCop, Erb LintのCI設定
#### 作成したファイル
- .github/workflows/ci.yml
- .erb_lint.yml
- .rubocop.yml
- .rubocop_todo.yml

#### 使用したGem
- rubocop-rails-omakase
- rubocop
- rubocop-performance
- rubocop-rails
- rubocop-rspec
- erb_lint


```ruby:.github/workflows/ci.yml

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true
       
      - name: RuboCop
        run: bundle exec rubocop --fail-level W
        
      - name: ERB Lint
        run: bundle exec erb_lint --lint-all
```
ci.ymlはRails7以上であればrails newで自動生成されるかと思います。
ここでは、`lint`という`job`の一つの`step`として`RuboCop`と`ERB Lint`というテストを実行するよう設定しています。
```ruby:
- name: RuboCop
        run: bundle exec rubocop --fail-level W
```
RuboCopを実行するよう指示しています。
``--fail-level W``：これはテスト実行後にfailを判定する基準を指定しています。ここではW（Worning）を指定しているので、　``Worning``以上の違反がある場合のみテストが通らないようになっています。（W未満の違反でも検知はしてくれるので、適宜確認して修正することは可能です）

``` ruby:
- name: ERB Lint
        run: bundle exec erb_lint --lint-all
```
Erb Lint（erbファイルのリントチェック）の実行を指示しています。
``erblint --lint-all``のように``_``がなくても実行はできますが、今は警告が出てしまうみたいなので``erb_lint``としました。

```ruby:.erb_lint.yml
---
EnableDefaultLinters: true
glob: "**/*.{html,text,js}{+*,}.erb"
exclude:
  - '**/vendor/**/*'
  - '**/node_modules/**/*'
linters:
  ErbSafety:
    enabled: true
  Rubocop:
    enabled: true
    rubocop_config:
      inherit_from:
        - .rubocop.yml
      Style/FrozenStringLiteralComment:
        Enabled: false
      Layout/InitialIndentation:
        Enabled: false
```
Erb Lintの設定ファイルになります。
``Rubocop: ``以下の記述があることで、RuboCopによる解析と連動させることができます。

``` ruby:
Style/FrozenStringLiteralComment:
        Enabled: false
      Layout/InitialIndentation:
        Enabled: false
Layout/InitialIndentation:
        Enabled: false
```
この部分ではRuboCopのルール設定をしています。これに関しては、私自身まだ何が必要で何が不要なのかの判断はつかないため、各々の環境に合わせて設定していただくのがいいと思います。
おすすめの設定があれば教えていただきたいです。


```rb: .rubocop.yml
inherit_from: .rubocop_todo.yml

inherit_gem: { rubocop-rails-omakase: rubocop.yml }
```
RuboCopの設定ファイルになります。``bundle exec rubocop --auto-gen-config``を実行すると自動で生成されます。
ここに必要なルールを設定していくことで現場にあったテストを実行できるようになります。

``.rubocop_todo.yml``は空のままで大丈夫です。

以上でGihub ActionsにRuboCop、Erb Lintを導入するためのCI設定は完了です。
続いてRSpecを導入していきますが、まずはローカル（docker）環境でRSpecを実行するための環境づくりから見ていきましょう。

# RSpecの導入

ここではまず、ローカルでRSpecを実行するための環境づくりから始めていきます。

#### 作成したファイル
- rails_helper.rb
- spec_helper.rb
- .rspec
- capybara.rb
- database.ci.yml

#### 追加したGem
- deveopment, test環境
    - factory_bot_rails
    - database_cleaner"
    - faker
    - rspec_rails
---
- development環境
    - spec_rails
---
- test環境
    - capybara
    - selenium_webdrivers
    - webdrivers
---

##### RSpecの導入
`bundle install`で必要なgemをインストールしたら、下記のコマンドを実行しましょう。
```
bundle exec rails g rspec:install
```
これによりRSpecの設定に必要な以下のファイルが自動で生成されます。
`.rspec`
`spec/spec_helper.rb`
`spec/rails_helper.rb`

まずはDockerの設定にRSpec SystemでCapybaraを使用するための設定を追加していきます。
``` yml: docker-compose.yml
web:
    container_name: rails_app
    build:
      context: .
      dockerfile: Dockerfile.prod
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    tty: true
    stdin_open: true
    volumes:
      - .:/myapp
      - bundle_data:/usr/local/bundle:cached
      - node_modules:/myapp/node_modules
    environment:
      TZ: Asia/Tokyo
      SELENIUM_DRIVER_URL: http://chrome:4444/wd/hub # この行を追加
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
  chrome: # このセクションを追加
    image: seleniarm/standalone-chromium:latest
    ports:
      - 4444:4444
```
docker-compose.ymlに`SELENIUM_DRIVER_URL:`の一行と`chrome:`のセクションを追加します。この設定によりDocker環境でCapybaraを使用したブラウザ操作を自動化したテストが可能になります。

では続いて、生成された`rails_helper.rb` `.rspec`に設定を追加していきます。
```ruby:spec/rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
# この行はコメントアウトを解除するだけで大丈夫です
    ...
RSpec.configure do |config|
    ...
    config.include FactoryBot::Syntax::Methods # この行を追加
    ...
    config.before(:each, type: :system) do # このブロックを追加
    if ENV['SELENIUM_DRIVER_URL']
      driven_by :remote_chrome
      Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
      Capybara.server_port = 4444
      Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
      Capybara.ignore_hidden_elements = false
    else
      driven_by :selenium_chrome_headless
    end
  end
end
```
`config.include FactoryBot::Syntax::Methods`
この設定により、FactoryBotをspec内で簡単に使用できるようになります。
例）設定なしの場合
`user = FactoryBot.create(:user)`

例）設定ありの場合
`user = create(:user)`

`config.before(:each, type: :system) do`
このブロックでは、Capybaraによるブラウザ操作を行う際のホスト、ポートなどを指定しています。`if ENV['SELENIUM_DRIVER_URL']`の条件により、環境変数が指定されている場合、つまりDockerコンテナ内でRSpecを実行する場合にはSelenium（remote_chrome）を使用し（ローカルでのDocker開発環境でRSpecを実行する場合ならこっち）、Dockerコンテナを使わないで実行する場合には、その環境にインストールされているChromeを使用するように分岐させています。今回、Github ActionsではDockerコンテナを立ち上げずに実行しているのでSeleniumの方を使用することになります。
https://qiita.com/ryouzi/items/fe0f583dcc93e06b6e64

```ruby:.rspec
--format documentation # この行を追加
```
この設定により、RSpecのテスト結果の出力形式を「ドキュメンテーション形式」に設定し、テストの実行結果がより読みやすくなります。

```ruby:capybara.rb
require 'capybara/rspec'
require 'selenium-webdriver'

Capybara.register_driver :remote_chrome do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument('no-sandbox')
  options.add_argument('headless')
  options.add_argument('disable-gpu')
  options.add_argument('window-size=1680,1050')
  Capybara::Selenium::Driver.new(app, browser: :remote, url: ENV['SELENIUM_DRIVER_URL'], capabilities: options)
end

Capybara.javascript_driver = :selenium_chrome_headless
```
CapybaraがリモートのChromeドライバーを使うために必要なオプションを指定しています。
`url: ENV['SELENIUM_DRIVER_URL']`により、docker-compose.ymlで指定した環境変数`SELENIUM_DRIVER_URL`をサーバーのurlとして指定しています。

```ruby: config/environments/test.rb
config.hosts << "許可したいホストのIP"
```
Railsサーバーに特定のホストからのリクエストを許可するための記述になります。RSpec Systemの実行時にホストが拒否される場合に追加してください。

以上で、ローカルでRSpec Model, Systemを実行するための環境は整いました。実際のテストコードについては、ここでは割愛します。
では続いて、Github ActionsにRSpecを導入していきましょう。

## Gihub ActionsにRSpecを導入するためのCI設定

私はここで一番時間を取られました。
まずは`ci.yml`の設定を追加・変更していきます。

```yml: ci.yml

test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: # ユーザー名を指定
          POSTGRES_PASSWORD: # パスワードを指定
        ports:
          - 5432:5432
        options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3

      timeout 5s --health-retries 5
    env:
      RAILS_ENV: test # この行を追加
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/myapp_test

    steps:
      - name: Install packages
        run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libvips postgresql-client

      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Database create and migrate # このセクションを追加
        run: |
          cp config/database.ci.yml config/database.yml
          bundle exec rails assets:precompile #ここで詰まった
          bundle exec rails db:create
          bundle exec rails db:migrate

      - name: Run rspec # このセクションを追加
        run: bundle exec rspec
```
`env:`の部分でこれ以下の処理をテスト環境で実行するように指定しています。
`name: Database create and migrate`のセクションではRSpec Systemを実行するために一時的にデータベースを作成しています。

```yml:database.ci.yml
test:  
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db
  username: # ユーザー名
  password: # パスワード
  database: myapp_test
```
ここでテスト用のデータベースを設定しています。

### 私が時間を取られたポイント
#### アセットのプリコンパイルができていなかった
私は初め、`bundle exec rails assets:precompile`が抜けていたために、`ActionView::Template::Error:The asset 'application.js' was not found in the load path.`
というエラーが出てしまい、プリコンパイルができていないということに気づくまでに時間を取られました。

`entorypoint.sh`にも同様のコードを書いていますが、Github ActionsではDockerコンテナを起動していないため、`entorypoint.sh`も実行されていなかった、ということだと解釈しました。

#### ChromeとChromeDriverのバージョンの不一致
`gem "webdrivers"`はインストールされているChromeのバージョンに適するChromeDriverを見つけてくれるらしいのですが、それがうまく見つからないのか、
`Webdrivers::NetworkError:
            #   Net::HTTPClientException: 404 "Not Found" with https://chromedriver.storage.googleapis.com/LATEST_RELEASE_131.0.6778`
というエラーが出てしまい、テストが実行できませんでした。
Gemfile.lockを確認すると、`webdrivers (5.2.0)`がインストールされていたので、最新版の`webdrivers (5.3.1)`をバージョンを指定してインストールしてみたところ、エラーが解消されました。

## 終わりに
本記事はほとんど自分用として今回取り組んだことをまとめたものになりますので、色々と至らない点があるかと思いますがご了承ください。
間違っている点などありましたらご指摘いただけると幸いです。

## 参考にさせていただいた記事

https://qiita.com/k-sukesakuma/items/f6fc0968113a77e80d0e

https://qiita.com/JZ8xNeXY/items/fef7efb5eb9495b62faf

https://qiita.com/ryouzi/items/fe0f583dcc93e06b6e64

https://qiita.com/JZ8xNeXY/items/fef7efb5eb9495b62faf

https://qiita.com/terufumi1122/items/ad55bf8713c0df053f58