パーフェクトRuby on Rails 9章 メモ・雑感(GitHub Actions, Rubocop, Dependabot, SimpleCov, Coverall, Skylight, rack-mini-profiler)

# 9 コード品質を上げる

## 雑感

この章はCIによる自動テストや静的解析、パフォーマンス検証などのアプリケーションコードの品質チェックを行う外部サービスの内容が主でした。
コミット前フックでのrubocop実行などは軽く紹介程度でしたが、業務で使えそうだったので少し本書の内容から拡張したバージョンのコードを作成してみました。

## 9.1 継続的インテグレーション

## GitHub Actions の設定方法

GitHub Actions は GitHub 上にあるリポジトリでの変更を検知して自動でテストを実行してくれる Web サービス。

#### 1. GitHub Actions の設定ファイルの作成

リポジトリ上部の`Actions`タブ > いずれかの`Set up this workflow`ボタン(`Ruby Gem`や`Ruby`など用途ごとに分かれているが設定ファイルを独自に変更する場合はどれを選んでも構わない)と進むと以下の yml ファイルの設定画面へ移行する。このファイルを編集・コミットすることで任意のテストを実施させる。同様の設定ファイルを`.github/workflows/ruby.yml`としてリポジトリへ加えることでも利用可能。

:::note info

私の場合、`perfect-rails`というリポジトリの中に本書で扱っているアプリケーション全てをまとめているので、`perfect-rails/awesome_events`内でのみテストを実施するために追加でワーキングディレクトリの指定もしています。

:::

```diff:perfect-rails/.github/workflows/ruby.yml
name: Rails Tests
on: [push, pull_request] # pushまたはPR作成時にトリガーされる
jobs: # この配下に実行する処理を記述
  build:
    runs-on: ubuntu-latest # 実行環境をubuntuに指定
+   defaults:
+     run:
+       working-directory: awesome_events  # ここでワーキングディレクトリを指定
    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby 2.7
      uses: ruby/setup-ruby@v1 # ruby/setup-rubyモジュールを指定
      with:
        ruby-version: 2.7
+       working-directory: ./awesome_events  # Rubyのセットアップディレクトリも指定
    - name: Build and test
      env:
        RAILS_ENV: test
      run: |
        sudo apt-get -yqq install libsqlite3-dev
        bundle install --jobs 4 --retry 3
        bin/rails db:create
        bin/rails db:migrate
        bin/yarn install
        bin/rails test
        bin/rails test:system
```

#### 2. システムテストを行うための設定

GitHub Actions でシステムテストを行うためには`headless Chrome`を使うように設定する必要がある。

```test/application_system_test_case.rb
require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
end
```

## GitHub Actions で Elasticsearch とそのプラグインを使う

Elasticsearch をアプリケーションのロジックとして組み込んでいる場合は、テスト実行のためにGitHub Actions 上でも利用可能にする必要がある。

#### 1. 作成するアクション用のリポジトリを作成

GitHub Actions 上でも Elasticsearch を利用するためには、Elasticsearch 環境を作るアクションを実装する必要がある。
アクションはパブリックリポジトリを作成してそこにファイルを置くだけで利用可能。
必要ファイルは下記となる。

- README.md
- Dockerfile
- action.yml
- run-elasticsearch.sh

作成例は[Elasticsearch 公式](https://github.com/elastic/elastic-github-actions/tree/master/elasticsearch)から提供されており、ほとんどがそのまま使用できるが、アプリケーションの仕様に応じて一部変更が必要な場合がある。

```Dockerfile
FROM docker:stable
RUN apk add --update bash
COPY run-elasticsearch.sh /run-elasticsearch.sh
ENTRYPOINT [ "/run-elasticsearch.sh" ]
```

`action.yml`は`Author`を自分に指定する。本書ではさらにプラグインの設定も追加しているが、こちらは公式ですでに追加対応済。

```diff:action.yml
name: 'Run Elasticsearch'
description: 'This action spins up an Elasticsearch instance that can be accessed and used in your subsequent steps.'
+author: 'Saito-3815'

branding:
  icon: 'database'
  color: 'green'

inputs:
  stack-version:
    description: 'The version of the Elastic Stack you want to run'
    required: true
  security-enabled:
    description: 'Enable or disable HTTPS, enabled by default'
    default: 'true'
    required: false
  nodes:
    description: 'Number of nodes in the cluster'
    required: false
    default: 1
  port:
    description: 'Port where you want to run Elasticsearch'
    required: false
    default: 9200
  elasticsearch_password:
    description: 'The password for the user elastic in your cluster'
    required: false
    default: 'changeme'
  plugins:
    description: 'Plugins that you want to include'
    required: false
  wait:
    description: 'Number of seconds to wait after launch'
    required: false
    default: 10
  network-name:
    description: 'Name of the network to create with docker'
    required: false
    default: elastic
  container-name:
    description: 'Name for the container'
    required: false
    default: es

runs:
  using: 'docker'
  image: 'Dockerfile'
  env:
    STACK_VERSION: ${{ inputs.stack-version }}
    NODES: ${{ inputs.nodes }}
    PORT: ${{ inputs.port }}
    PLUGINS: ${{ inputs.plugins }}
    SECURITY_ENABLED: ${{ inputs.security-enabled }}
    ELASTICSEARCH_PASSWORD: ${{ inputs.elasticsearch_password }}
    WAIT: ${{ inputs.wait }}
    NETWORK_NAME: ${{ inputs.network-name }}
    CONTAINER_NAME: ${{ inputs.container-name }}
```

`run-elasticsearch.sh`は指定されたプラグインをインストールするコマンドを組み立てる部分と、それを実行してインストールする部分を追加しています。(このファイルに関しては本書を見様見真似で作成しましたが、理解度が薄く冗長な記述となっているのでおそらくベストプラクティスではないと思います。一応、正常に動作はします。)

```diff:run-elasticsearch.sh
#!/bin/bash

set -euxo pipefail

if [[ -z $STACK_VERSION ]]; then
  echo -e "\033[31;1mERROR:\033[0m Required environment variable [STACK_VERSION] not set\033[0m"
  exit 1
fi

+PLUGIN_INSTALL_CMD=""
+PLUGINS_STR=`echo ${PLUGINS} | sed -e 's/\n/ /g'`
+if [ -n "$PLUGINS_STR" ]; then
+  ARRAY=(${PLUGINS_STR})
+  for i in "${ARRAY[@]}"
+  do
+    PLUGIN_INSTALL_CMD+="elasticsearch-plugin install --batch ${i} && "
+  done
+fi

MAJOR_VERSION=`echo ${STACK_VERSION} | cut -c 1`
NETWORK_NAME=${NETWORK_NAME:-elastic}
CONTAINER_NAME=${CONTAINER_NAME:-es}
SECURITY_ENABLED=${SECURITY_ENABLED:-true}

docker network inspect $NETWORK_NAME >/dev/null 2>&1 || docker network create $NETWORK_NAME

mkdir -p /es/plugins/
chown -R 1000:1000 /es/

if [[ ! -z $PLUGINS ]]; then
  docker run --rm \
    --user=0:0 \
    --network=$NETWORK_NAME \
    -v /es/plugins/:/usr/share/elasticsearch/plugins/ \
    --entrypoint=/usr/share/elasticsearch/bin/elasticsearch-plugin \
    docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} \
    install ${PLUGINS/\\n/ } --batch
fi

for (( node=1; node<=${NODES-1}; node++ ))
do
  port_com=$((9300 + $node - 1))
  UNICAST_HOSTS+="es$node:${port_com},"
done

for (( node=1; node<=${NODES-1}; node++ ))
do
  port=$((PORT + $node - 1))
  port_com=$((9300 + $node - 1))
  if [ "x${MAJOR_VERSION}" == 'x6' ]; then
    docker run \
      --rm \
      --env "node.name=${CONTAINER_NAME}${node}" \
      --env "cluster.name=docker-elasticsearch" \
      --env "cluster.routing.allocation.disk.threshold_enabled=false" \
      --env "bootstrap.memory_lock=true" \
      --env "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
      --env "xpack.security.enabled=false" \
      --env "xpack.license.self_generated.type=trial" \
      --env "discovery.zen.ping.unicast.hosts=${UNICAST_HOSTS}" \
      --env "discovery.zen.minimum_master_nodes=${NODES}" \
      --env "http.port=${port}" \
      --ulimit nofile=65536:65536 \
      --ulimit memlock=-1:-1 \
      --publish "${port}:${port}" \
      --publish "${port_com}:${port_com}" \
      --detach \
      --network=$NETWORK_NAME \
      --name="${CONTAINER_NAME}${node}" \
      -v /es/plugins/:/usr/share/elasticsearch/plugins/ \
      docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} \
+     /bin/sh -vc "${PLUGIN_INSTALL_CMD} /usr/local/bin/docker-entrypoint.sh"
  elif [ "x${MAJOR_VERSION}" == 'x7' ]; then
    docker run \
      --rm \
      --env "node.name=${CONTAINER_NAME}${node}" \
      --env "cluster.name=docker-elasticsearch" \
      --env "cluster.initial_master_nodes=es1" \
      --env "discovery.seed_hosts=es1" \
      --env "cluster.routing.allocation.disk.threshold_enabled=false" \
      --env "bootstrap.memory_lock=true" \
      --env "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
      --env "xpack.security.enabled=false" \
      --env "xpack.license.self_generated.type=trial" \
      --env "http.port=${port}" \
      --env "action.destructive_requires_name=false" \
      --ulimit nofile=65536:65536 \
      --ulimit memlock=-1:-1 \
      --publish "${port}:${port}" \
      --detach \
      --network=$NETWORK_NAME \
      --name="${CONTAINER_NAME}${node}" \
      -v /es/plugins/:/usr/share/elasticsearch/plugins/ \
      docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} \
+     /bin/sh -vc "${PLUGIN_INSTALL_CMD} /usr/local/bin/docker-entrypoint.sh"
  elif [ "x${MAJOR_VERSION}" == 'x8' ] || [ "x${MAJOR_VERSION}" == 'x9' ]; then
    if [ "${SECURITY_ENABLED}" == 'true' ]; then
      elasticsearch_password=${ELASTICSEARCH_PASSWORD-'changeme'}
      docker run \
        --rm \
        --env "ELASTIC_PASSWORD=${elasticsearch_password}" \
        --env "xpack.license.self_generated.type=trial" \
        --env "node.name=${CONTAINER_NAME}${node}" \
        --env "cluster.name=docker-elasticsearch" \
        --env "cluster.initial_master_nodes=es1" \
        --env "discovery.seed_hosts=es1" \
        --env "cluster.routing.allocation.disk.threshold_enabled=false" \
        --env "bootstrap.memory_lock=true" \
        --env "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
        --env "http.port=${port}" \
        --env "action.destructive_requires_name=false" \
        --ulimit nofile=65536:65536 \
        --ulimit memlock=-1:-1 \
        --publish "${port}:${port}" \
        --network=$NETWORK_NAME \
        --name="${CONTAINER_NAME}${node}" \
        --detach \
        -v /es/plugins/:/usr/share/elasticsearch/plugins/ \
        docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} \
+       /bin/sh -vc "${PLUGIN_INSTALL_CMD} /usr/local/bin/docker-entrypoint.sh"
    else
      docker run \
        --rm \
        --env "xpack.security.enabled=false" \
        --env "node.name=${CONTAINER_NAME}${node}" \
        --env "cluster.name=docker-elasticsearch" \
        --env "cluster.initial_master_nodes=es1" \
        --env "discovery.seed_hosts=es1" \
        --env "cluster.routing.allocation.disk.threshold_enabled=false" \
        --env "bootstrap.memory_lock=true" \
        --env "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
        --env "xpack.license.self_generated.type=trial" \
        --env "http.port=${port}" \
        --env "action.destructive_requires_name=false" \
        --ulimit nofile=65536:65536 \
        --ulimit memlock=-1:-1 \
        --publish "${port}:${port}" \
        --network=$NETWORK_NAME \
        --name="${CONTAINER_NAME}${node}" \
        --detach \
        -v /es/plugins/:/usr/share/elasticsearch/plugins/ \
        docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} \
+       /bin/sh -vc "${PLUGIN_INSTALL_CMD} /usr/local/bin/docker-entrypoint.sh"
    fi
  fi
done

if ([ "x${MAJOR_VERSION}" == 'x8' ] || [ "x${MAJOR_VERSION}" == 'x9' ]) && [ "${SECURITY_ENABLED}" == 'true' ]; then
  docker run \
    --network $NETWORK_NAME \
    --rm \
    appropriate/curl \
    --max-time 120 \
    --retry 120 \
    --retry-delay 1 \
    --retry-connrefused \
    --show-error \
    --silent \
    -k \
    -u elastic:${ELASTICSEARCH_PASSWORD-'changeme'} \
    https://${CONTAINER_NAME}1:$PORT
else
  docker run \
    --network $NETWORK_NAME \
    --rm \
    appropriate/curl \
    --max-time 120 \
    --retry 120 \
    --retry-delay 1 \
    --retry-connrefused \
    --show-error \
    --silent \
    http://${CONTAINER_NAME}1:$PORT
fi

sleep $WAIT

echo "Elasticsearch up and running"
```

作成後は`run-elasticsearch.sh`に実行権限を付与しておく。

```
% chmod a+x run-elasticsearch.sh
```

全て完了したら、v1 タグをつけてコミットし、リポジトリへプッシュする。

```
# 通常通りコミットを作成
git add .
git commit -m "add: GitHub Actions用の設定ファイルを追加"

# コミットにタグを付ける
git tag v1
```

#### 2. アプリケーションの CI を実行する GitHub Actions を Elasticsearch のアクションを呼びだして実行する

Elasticsearch 用のアクションを作成した後にアプリケーションのテストを実行する GitHub Actions の設定ファイルへそのアクションを利用する記述を追加する。
このファイルを追加してコミットし、GitHub へプッシュすると GitHub Actions でワークフローが実行される。

```diff:.github/workflows/ruby.yml
name: Rails Tests
on: [push, pull_request] # pushまたはPR作成時にトリガーされる
jobs: # この配下に実行する処理を記述
  build:
    runs-on: ubuntu-latest # 実行環境をubuntuに指定
    defaults:
      run:
        working-directory: awesome_events  # ここでワーキングディレクトリを指定
    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby 2.7
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: 2.7
        working-directory: ./awesome_events  # Rubyのセットアップディレクトリも指定
+   # ElasticsearchをGitHub Actions上で利用するための設定    
+   - name: Configure sysctl limits 
+     run: |
+       sudo swapoff -a
+       sudo sysctl -w vm.swappiness=1
+       sudo sysctl -w fs.file-max=262144
+       sudo sysctl -w vm.max_map_count=262144
+   - name: Run Elasticsearch
+     # リポジトリ名@タグ名で作成したアクションのリポジトリを指定できる
+     # タグ名はブランチ名としても良い
+     uses: perfect-ruby-on-rails/elasticsearch-with-plugins-action@v1
+     with:
+       stack-version: 7.6.0
+       plugins: |
+         analysis-kuromoji
    - name: Build and test
      env:
        RAILS_ENV: test
      run: |
        sudo apt-get -yqq install libsqlite3-dev
        bundle install --jobs 4 --retry 3
        bin/rails db:create
        bin/rails db:migrate
        bin/yarn install

        bin/rails runner 'Event.search_index.delete rescue nil'
        bin/rails runner 'Event.reindex'

        bin/rails test
        bin/rails test:system
```

## 9.2 Gem の定期 update

gem のアップデートは定期的に手動で実行する場合と、利用するライブラリを定期更新する GitHub Dependabot などのサービスを使用する方法がある。

## dependabot の導入

::: note warn

2022/7/4 の dependabot のアップデートにより設定ファイル名が以下のように変更され、内容の記述方法も変更されたようです。本記事では最新の記述方法に則って設定ファイルを記述しました。

旧: `.dependabot/config.yml`

新: `.github/dependabot.yml`

:::

私の場合、上述した GitHub Actions と同じ理由によりワーキングディレクトリ名を変更しています。

```diff:.github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "bundler"
+   directory: "/awesome_events"
    schedule:
      interval: "weekly"
    allow:
      - dependency-type: "all"
    versioning-strategy: "auto"
```

この設定ファイルをリポジトリへプッシュすると、dependabot の設定が完了する。設定後は、`bundle update`された gem の PR が自動作成される。これを確認してマージすることで更新作業を行うことができる。

::: note warn

```
$ ruby -v
ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [x86_64-darwin23]
$ rails -v
Rails 6.0.6.1
```

上記の私の環境では dependabot のアクション実行時に`Bundler::SolveFailure with message: Could not find compatible versions`が発生しました。`versioning-strategy`や`ignore`の設定を変更することで対応を試みましたが、`every version of bundler depends on Ruby >= 3.1.0`というエラーメッセージから yml ファイル内の設定では解決できない根本的な互換性の問題があるようです。
解決のためには Ruby 3.1.0 以上へのアップグレードが必要なようなのですが、アプリケーションの他の部位への影響が出る可能性を考慮し、今回はこのエラーの解決は断念しました。

:::

## Rails での rubocop の使い方

### Rails への導入

以下の gem を**development**環境へだけ追加

```Gemfile.rb
group :development do
  gem 'rubocop', require: false
  # 以下は元々上記に組み込まれていたが現在は拡張gem扱い
  gem 'rubocop-performance', require: false
  gem 'rubocop-rails', require: false
end
```

設定ファイルへ拡張 gem を読み込む設定を追加する

```.rubocop.yml
# 拡張gemの読み込み
require:
  - rubocop-performance
  - rubocop-rails
```

`bundle install`すると`bundle exec rubocop`で静的解析実行可能になる

### Git のコミット前フックで rubocop を実行

Git の pre-commit フックを使うと、コミット前に Rubocop の検査を実施できる。
Git のフック機能で実行するスクリプトファイルは`.git/hooks`ディレクトリ配下へ置き、コミット前フックを実行するには`pre-commit`というファイルを作成する。
以下は、下記の要件を追加したスクリプトの例

- ステージングされたファイルのみを対象とする
- 特定のディレクトリ内(`/awesome_events`)でのみ実行する

```.git/hooks/pre-commit
#!/bin/bash

# ステージングされているRubyファイルを取得
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rb$')

if [ -n "$files" ]; then
  # awesome_events内のファイルのみを対象とする場合の処理
  awesome_files=$(echo "$files" | grep "^awesome_events/")
  if [ -n "$awesome_files" ]; then
    # awesome_events内のパスに変換してrubocopを実行
    (cd awesome_events && bundle exec rubocop $(echo "$awesome_files" | sed 's|^awesome_events/||'))
  fi
fi
```

### 一部の cop のみを有効化

チーム方針などで rubocop の一部の検査機能のみを有効にしたい場合は`rubocop.yml`に設定を追加する。以下は Lint 種別と Security 種別のみを有効化する例。

```diff:rubocop.yml
require:
  - rubocop-performance
  - rubocop-rails

+AllCops:
+  DisableByDefault: true
+
+Lint:
+  Enabled: true
+
+Security:
+  Enabled: true
```

### 特定の部分のみ rubocop を無効化

rboocop の検査を無効化したい場合は、以下の`rubocop:disable cop名`コメントを該当行に追加する。

```ruby
# if文のガード節チェックを無効化する例
if params[:email].present? # rubocop:disable Style/GuardClause
```

### --auto-gen-config による段階的な rubocop の導入

rubocop 導入の際にデフォルトでは検出項目の量が多すぎる場合は、`--auto-gen-config`オプションで初期段階では全てのチェックを無効化した上で段階的に検査項目を増やしながら導入することができる。

以下のオプションコマンドで rubocop を実行すると`.rubocop_todo.yml`が生成され、`rubocop.yml`には自動で設定が追加される。

```
% bundle exec rubocop --auto-gen-config
```

```rubocop.yml
# 上記コマンドで自動で追加される
inherit_from: .rubocop_todo.yml
```

`.rubocop_todo.yml`には rubocop で検出された項目についてそれぞれを抑制・無効化する設定が記述されている。

```.rubocop_todo.yml
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2025-02-09 01:03:40 UTC using RuboCop version 1.71.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
# Include: **/*.gemfile, **/Gemfile, **/gems.rb
Bundler/OrderedGems:
  Exclude:
    - 'Gemfile'
```

`.rubocop_todo.yml`を読んで、抑制したままにしておきたい項目の場合は`.rubocop_todo.yml`から該当項目を削除し、`.rubocop.yml`に以下のように検査を抑制する項目を追加する。

```.rubocop.yml
# .rubocop.yml
inherit_from: .rubocop_todo.yml

# ここに永続的な除外設定を書く
Style/Documentation:
  Enabled: false
```

コードを修正すべき項目はそのまま`.rubocop_todo.yml`へ置いておく。そして、１つずつ項目を確認してコード修正が完了したら`.rubocop_todo.yml`を削除する。

上記までの繰り返しで全ての`.rubocop_todo.yml`内の設定項目が削除されれば、必要な項目のみが有効化された状態で設定が完了されたこととなる。
この状態となったら`.rubocop_todo.yml`を削除し、`.rubocop.yml`内の`inherit_from: .rubocop_todo.yml`も削除する。

## Brakemanで脆弱性を静的解析

`brakeman`gemでRailsアプリケーションの脆弱性を静的解析できる。
導入するには、**developmennt**環境にgemを追加して`bundle install`

```Gemfile.rb
group :development do
  gem 'brakeman' # セキュリティチェック用のライブラリ
end
```

以下のコマンドで実行すると結果のレポートが出力される。オプションコマンドは以下

- `-A`: デフォルトで検査項目として設定されていない項目も含めて全てを検査する
- `-wl`: 検出レベルを軽微なものまで含めたものとする

```
% bundle exec brakeman
略
== Warnings ==

Confidence: High
Category: Unmaintained Dependency
Check: EOLRuby
Message: Support for Ruby 2.7.8 ended on 2023-03-31
File: .ruby-version
Line: 1
```

## SimpleCovでテストカバレッジを測定する

`simplecov`gemを導入するとテスト実行時にテストカバレッジ測定結果をHTMLとして出力できる。
導入は以下のようにする。

### 1. gemを追加してbundle install

**test**環境へ追加する。

```Gemfile.rb
group :test do
  gem 'simplecov', require: false # テストカバレッジ計測用のライブラリ
end
```

### 2. 設定ファイルを編集

minitestであれば`test/test_helper.rb`、rspecであれば`test/spec_helper.rb`へ下記の必要設定を**他のコードをrequireする前に追記**する。また、カバレッジ測定時は並列テストの設定はオフにする。

```diff_ruby:test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
+ require 'simplecov'
+ SimpleCov.start 'rails'

require_relative '../config/environment'
略
class ActiveSupport::TestCase
-  # 並列テストの設定はオフにする
-  # parallelize(workers: :number_of_processors)
略
end
```

### 3. テスト実行

上記設定後にテストを実行すると`coverage/index.html`へカバレッジ測定結果がHTMLで出力される。

![スクリーンショット 2025-02-11 16.00.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790072/6744d9fa-58ff-86ad-6eef-29bfcbc269e7.png)

![スクリーンショット 2025-02-11 16.00.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790072/4f2b857c-16f1-ec27-475d-7338c19b0bc8.png)

## Coverallでテストカバレッジを測定する

`Coverall`はGitHubと連携してCIで実施したテストのカバレッジをWebページへ表示してくれるサービス。
導入は以下のようにする。

### 1. gemを追加してbundle install

**test**環境へ追加する。

```Gemfile.rb
group :test do
  gem 'coveralls', require: false # テストカバレッジをCoverallsに送信するライブラリ
end
```

### 2. 設定ファイルを編集

minitestであれば`test/test_helper.rb`、rspecであれば`test/spec_helper.rb`へ下記の必要設定を**他のコードをrequireする前に追記**する。なお、並列テストについては有効化する設定方法も存在するが、必要がない場合はオフにする。

```diff_ruby:test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
+ require 'coveralls'
+ Coveralls.wear!('rails')
end
```

### 3. テスト実行コードを編集

CI上でテストを実行するコードに`Coverall`でリポジトリごとに発行されるトークンを設定する。

```
COVERALLS_REPO_TOKEN=xxxxxxxxx bin/rails test
```

上記設定でCI実行時にCoverallsでカバレッジが表示されるようになる。

## Skylightで本番環境でのパフォーマンスを測定する

`Skylight`は本番環境におけるアプリケーションのレスポンスタイムを測定できるwebサービス。
導入は以下のようにする。

### 1. Skylightにサインアップ

[Skyligt](https://www.skylight.io/)へアクセスし、`GETTING STARTED`からサインアップしてセットアップトークンを入手する。

### 2. gemを追加してbundle install

```Gemfile.rb
gem 'skylight'
```

### 3. 設定ファイルを作成

入手したトークンを含めて下記コマンドを実行すると`config/skylight.yml`が自動生成される。このファイルの中に`authentication token`が保存されている。

```
% bundle exec skylight setup xxxxxxxxx
```

```config/skylight.yml
---
# The authentication token for the application.
authentication: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 4. 本番環境に設定してデプロイ

上記設定で生成した`authentication token`を`credentials`を使って保存するか、サービス運用環境の環境変数`SKYLIGHT_AUTHENTICATION_TOKEN`へ保存してデプロイする。
この状態でアプリケーションを運用すると`Skylight`のダッシュボードに測定レポートが表示される。

## rack-mini-profilerで開発環境でのパフォーマンスを測定する

`rack-mini-profiler`gemを導入することでローカルで手軽にRailsアプリケーションのパフォーマンスを測定できる。
導入は以下のようにする。

### 1. gemを追加してbundle install

**develop**環境へgemを追加する。

```Gemfile.rb
group :development do
  gem 'rack-mini-profiler', require: false # パフォーマンス計測用のライブラリ
end
```

### 2. 設定ファイルを作成

下記コマンドで必要なファイルが作成される。

```
% bin/rails g rack_profiler:install
```

### 3. ローカル環境でサーバ起動

上記設定を行ってから`rails s`でサーバを起動するとページの左上にプロファイルが表示されパフォーマンス測定結果を確認できる。

![スクリーンショット 2025-02-11 17.05.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790072/a65b42dd-1df4-024f-5c61-538f270ce439.png)