はじめに

今回のレッスンでは、CircleCIというCIツールを使って、RubocopとRSpecを自動実行する仕組みを構築していきます。

一見するととっつきにくい印象があるCIツールですが、実は簡単に設定することができますので、ひとつずつ、着実に進んでいきましょう!

CIとは何か

CIとは、Continuous Integration の頭文字をとった略称で、継続的インテグレーションと呼ばれています。

もともとはエクストリーム・プログラミングと呼ばれるソフトウェア開発手法があり、その項目の一つとされているものです。

と説明されてもよくわからないと思いますので、まずは次のように理解してもらえればOKです。

まずCIという語源は、

  • Continuous...継続的
  • Integration...統合

という意味の言葉を組み合わせてできています。

つまり、機能追加などの変更があるたびに、定期的に実行させたい処理を連鎖的に実行すること といったイメージですね。

図にすると、

このようなかたちになります。

コードの変更をCIツールが自動的に検知して、定期的に実行したい処理を行い、通知してくれるので、CIツールを使うと開発者は「コードの実装」に集中するだけでよくなるわけです。

そして、この CIを開発するにあたって実現するためのソフトウェアが、CircleCIをはじめとしたCIツールになります。

CircleCIとは何か

CircleCIはクラウドやオンプレミス(自社運用)で利用できるCIツールの一種です。

基本的にはフルサポートしているDockerを使い、そのコンテナ内で各処理が実行するように設計することが多いですね。

Docker以外にもLinuxやWindowsなどの各種OSも仮想マシン上で構築できるようになっているため、用途に合わせた使い方ができます。

CircleCIの特徴

CIツール自体はCircleCI以外にも、以下のように様々な種類があります。

CI ツールの種類
  • Jenkins
  • AWS CodeBuild
  • Shippable
  • TravisCI

他のCIツールは設定が複雑なものもあるのですが、その中でも CircleCI は取り扱いやすく、以下の3ステップだけで構築できます。

  1. GitHubと連携
  2. リモートリポジトリ選択
  3. 設定ファイルをpush

非常に手軽ですね。

CI環境をスピーディに構築することができるため、メルカリやサイバーエージェントなどのメガベンチャー企業でも採用事例が多いのも特徴です。

CircleCIのアカウントを作成

さて、それではここから本格的にCircleCIを扱っていきます。はじめに、CircleCIを利用するにあたって、アカウントを作成します。

GitHubのアカウントがあればすぐにアカウント 登録できるので、ぱぱっと済ませてしまいましょう。

CircleCI と GitHub の連携設定

下記の公式サイトからGIF動画のようにAll Reposをクリックして登録してください。

Continuous Integration and Delivery - CircleCI

画面遷移した後、CircleCIとGitHubを互いに認証させるSign Up with GitHubをクリックすると、画像のような認証画面になるのでAuthorize circleciをクリックしてください。

無事に認証出来ると、連携が完了し、ダッシュボードへと画面遷移します。下記画像のようなページが表示されていればOKです!

リポジトリの連携

アカウントの作成が完了したら、CircleCIとリポジトリを連携しましょう。

アカウント名のタブを開くと、画像のような状態になっているかと思います。

その中から、Set Up New Projectsをクリックしてください。

  • Operating SystemにはLinux
  • LanguageにはRuby

上記それぞれ選択した状態であることを確認したら、Start Buildingをクリックしましょう。

これでCircleCIのビルドが実行されるようになります。

ビルドとは?

CircleCI では、設定ファイルに基づいて実行させたい処理の準備をすることをビルドと呼んでいます。

実行するとエラーになりますが、現段階では設定ファイルを作成していないので、そのままで問題ありません。

ビルドパイプラインの設計を行う

CircleCIの設定ファイルを書いていく前に、ビルドパイプラインの設計を行いましょう。

ビルドパイプラインとは何か

ビルドパイプラインとは、設定ファイルに記述したそれぞれの処理の実行順序を意味します。

今回は、 CircleCI上で次のようなビルドパイプラインを作るための設定ファイルを作成し、そのファイルを編集することでCI環境を作っていきましょう。

  1. ローカルでソースコードを変更する
  2. ローカルの内容をリモートリポジトリにpush
  3. pushされたコミットをCircleCIが検知
  4. RSpecとRubocopの処理が実行される
  5. それぞれの結果をリモートリポジトリに通知する

具体的には、.circleci/config.ymlというファイルに各パイプラインの設定にあたる部分を書いていくのですが、現時点ではまだこのファイルがありません。

なので、次から

  • CircleCIのビルドパイプランを記述するための設定ファイル
  • CircleCIで使うdatabaseと接続するための設定ファイル

これらを先に準備していきましょう。

Rails側の設定を修正

それでは早速、先ほどお伝えした2つの設定ファイルを作っていきましょう。

方法としてはいくつかあるのですが、今回はCircleCI用のYAMLファイルを作成し、そこに設定を記述するようにします。

databaseと接続するための設定ファイルの作成

databaseと接続するための設定ファイルにあたるのが、database.yml.ciです。

CircleCI上でRSpecなどのテストを実行するためには、設定ファイル内に記述したデータベースのコンテナと、チェックアウトしてきたRailsアプリを連携させる必要があるんですね。

YAMLファイルに設定を書いていくとは言っても、記述する内容自体はRailsのconfigディレクトリ配下にあるdatabase.ymlとほぼ同じです。

Railsプロジェクトのconfigディレクトリ配下にdatabase.yml.ciを作成し、次の内容で作成しましょう。

test:
   adapter: mysql2
   encoding: utf8
   pool: 5
   username: <%= ENV.fetch("MYSQL_USER", "root") %>
   password:
   host: <%= ENV.fetch("MYSQL_HOST", "127.0.0.1") %>
   port: <%= ENV.fetch("MYSQL_PORT", "3306") %>
   database: <%= ENV.fetch("MYSQL_DATABASE", "circleci-example-for-rails_test") %>

上記のファイルは設定ファイルの中でdatabase.ymlとしてリネームし、その内容を元にtest用のデータベースを作成しています。

ビルドパイプランを記述するための設定ファイル

ビルドパイプラインを記述するための設定ファイルにあたるのが、.circleci/config.ymlです。

それでは書いていきましょう。

まずは、新規でブランチを作成し、そこで作業するようにします。

$ git checkout -b feature/my_circleci_setting

続いて、ターミナルでCircleCIと連携させたいプロジェクトのルートディレクトリにいることを確認してください。

$ pwd

確認ができたら、設定ファイルを作成していきましょう。

$ mkdir -p .circleci
$ touch .circleci/config.yml

ここで作成している、以下の

  • .circleci
  • config.yml(以後設定ファイル)

これらのファイルは、CircleCIがリモートリポジトリに対する変更を検知するために必要となるため、必ずこの命名にしてください。

RSpecとRubocopの実行環境を準備する

ファイルの作成ができたので、RSpecとRubocopをCircleCI上で実行させるためのDockerイメージを作成していきましょう。

RubyとMySQLを組み合わせたDockerコンテナを作る

まずはCirlcleCI内のコンテナにRubyとMySQLをインストールしたコンテナを作成します。

設定ファイルに次の内容を記述してください。

version: 2.1
executors:
  default_container:
    docker:
      - image: circleci/ruby:2.6.1-node
        environment:
          RAILS_ENV: test
          BUNDLE_JOBS: 4
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          BUNDLER_VERSION: 2.0.1
      - image: circleci/mysql:5.7-ram
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: true
    working_directory: ~/circleci-example-for-rails

RubyとMySQLのバージョンについては、環境ごとの差異をなくすため、連携しているリポジトリと同じバージョンを指定するといいですね。

また、これ以降の処理は、このコンテナをベースにして実行していくことになるのもポイントです。

連携したリポジトリからGit cloneをしてbundle installを行う

先ほどベースとなるコンテナを作成できたので、RubyとMySQLの実行環境が整いました。

続いて、連携しているリポジトリからソースコードを取得して、bundle installをしていきましょう。

設定ファイルに次の内容を記述してください。

commands:
  install_bundler:
    description: Bundler install
    steps:
      - run: gem install bundler -v 2.0.1

jobs:
  fetch_source_code:
    executor:
      name: default_container
    steps:
      - checkout
      - save_cache:
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/circleci-example-for-rails
  bundle_dependencies:
    executor:
      name: default_container
    steps:
      - restore_cache:
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          key: v2-dependencies-{{ checksum "Gemfile.lock" }}
      - install_bundler
      - run:
          name: Bundle Install Dependencies
          command: |
            bundle install
      - save_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle

コンテナを作成した時と比べると複雑な処理に見えますが、

  • 連携したリポジトリからGit clone
  • テストを実行するために必要な各種Gemのインストール

やっていることはこの2つなので、意外とシンプルです。

詳細は省略しますが、公式ドキュメントに各項目の意味について記載されていますので、必要に応じて参照するようにしてください。

参考

CircleCI を設定する - CircleCI

RSpecの設定

どんどんいきましょう。

続いて、RSpecをCircleCI上で実行させるための処理を設定します。

次のように記述してください。

rspec:
  executor:
    name: default_container
  steps:
    - restore_cache:
        key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
    - restore_cache:
        key: v1-dependencies-{{ checksum "Gemfile.lock" }}
    - run:
        name: Watting stand up database
        command: |
          dockerize -wait \
          tcp://127.0.0.1:3306 -timeout 120s
    # Database setup
    - run: mv ./config/database.yml.ci ./config/database.yml
    - install_bundler
    - run:
        name: Testing DB migration and seed
        command: |
          bundle exec rails db:create db:migrate db:seed db:drop
    - run:
        name: Run rspec
        command: |
          mkdir /tmp/test-results
          mkdir -p ~/rspec
          bundle exec rails db:create db:migrate
          TEST_FILES="$(circleci tests glob \"spec/**/*_spec.rb\" | circleci tests split --split-by=timings)"
          bundle exec rspec --require rails_helper \
                            --color \
                            --format progress \
                            --format RspecJunitFormatter \
                            --out ~/rspec/rspec.xml
    # collect reports
    - store_test_results:
        path: ~/rspec
    - store_artifacts:
        path: ~/tmp/test-results
        destination: test-results

ここでの処理内容としては、

  • ソースコードの復元
  • bundle installした内容の復元
  • RSpecのテスト用データベースの作成
  • RSpecの実行
  • 実行結果のアップロード

大きく分けてこの5つですね。

RSpecの設定は以上です。

Rubocopの設定

次は、Rubocopの設定を行っていきます。

以下の内容を設定ファイルに追記してください。

rubocop:
  executor:
    name: default_container
  steps:
    - restore_cache: # ソースコードの復元
        key: v1-qiita_clone_2019-{{ .Environment.CIRCLE_SHA1 }}
    - restore_cache: # vendor/bundleを復元
        key: v1-dependencies-{{ checksum "Gemfile.lock" }}
    - install_bundler
    - run:
        name: Execute rubocop
        command: |
          bundle exec rubocop

Rubocopの実行はコマンドを叩くだけでOKなので、RSpecの時に比べると記述量も少なくなっています。

処理の流れとしては、

  1. ソースコードの復元
  2. bundle installした内容の復元
  3. bundlerのインストール
  4. Rubocopの実行

このようになります。非常にシンプルですよね。

設定ファイルの全体象

それではここで、最終的な設定ファイルの全体を確認してみましょう。

version: 2.1
executors:
  default_container:
    docker:
      - image: circleci/ruby:2.6.1-node
        environment:
          RAILS_ENV: test
          BUNDLE_JOBS: 4
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          BUNDLER_VERSION: 2.0.1
      - image: circleci/mysql:5.7-ram
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: true
    working_directory: ~/circleci-example-for-rails

commands:
  install_bundler:
    description: Bundler install
    steps:
      - run: gem install bundler -v 2.0.1

jobs:
  fetch_source_code:
    executor:
      name: default_container
    steps:
      - checkout
      - save_cache:
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/circleci-example-for-rails
  bundle_dependencies:
    executor:
      name: default_container
    steps:
      - restore_cache:
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          key: v2-dependencies-{{ checksum "Gemfile.lock" }}
      - install_bundler
      - run:
          name: Bundle Install Dependencies
          command: |
            bundle install
      - save_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
  rspec:
    executor:
      name: default_container
    steps:
      - restore_cache:
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
      - run:
          name: Watting stand up database
          command: |
            dockerize -wait \
            tcp://127.0.0.1:3306 -timeout 120s
      # Database setup
      - run: mv ./config/database.yml.ci ./config/database.yml
      - install_bundler
      - run:
          name: Testing DB migration and seed
          command: |
            bundle exec rails db:create db:migrate db:seed db:drop
      - run:
          name: Run rspec
          command: |
            mkdir /tmp/test-results
            mkdir -p ~/rspec
            bundle exec rails db:create db:migrate
            TEST_FILES="$(circleci tests glob \"spec/**/*_spec.rb\" | circleci tests split --split-by=timings)"
            bundle exec rspec --require rails_helper \
                              --color \
                              --format progress \
                              --format RspecJunitFormatter \
                              --out ~/rspec/rspec.xml
      # collect reports
      - store_test_results:
          path: ~/rspec
      - store_artifacts:
          path: ~/tmp/test-results
          destination: test-results

  rubocop:
    executor:
      name: default_container
    steps:
      - restore_cache: # ソースコードの復元
          key: v1-circleci-example-for-rails-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache: # vendor/bundleを復元
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
      - install_bundler
      - run:
          name: Execute rubocop
          command: |
            bundle exec rubocop

workflows:
  build:
    jobs:
      - fetch_source_code
      - bundle_dependencies:
          requires:
            - fetch_source_code
      - rspec:
          requires:
            - bundle_dependencies

全体でみると、かなり複雑そうに見えますが、一つ一つ紐解いていけば理解しやすいですよね。

まとめ

今回のレッスンでは、CIツールのCircleCIを使って、

上記のようなRSpecrとRubocopを自動実行するビルドパイプラインをを構築しました。

このレッスンで学んだことを応用すれば、CIだけでなく自動デプロイであるCDを実装することも可能です。

ぜひプラスアルファのチャレンジとして調整してみてください。

それでは、お疲れ様でした!