3
0

More than 3 years have passed since last update.

RSpec導入してCircleCIに組み込んで見る

Posted at

今までプロダクト優先で自動テストを書くのが億劫だったことを反省し、ついに意を決してRSpecを導入しました。
目標は

  • 重要なモデルのバリデーションテスト
  • コントローラー(request)の200確認・DB更新確認
  • ボタンやリンク経由で画面遷移のシナリオテスト(フィーチャーテスト)
  • CircleCIでRSpecのテストをデプロイ前に入れ込み、OKとならなければデプロイしないようにする

をすることです。もちろん究極は先にSpecを書いて、コードを実装するテストドリブン開発が理想ですが、今回後付になってしまったので現状の挙動を担保するという思想でテストを作成してます。
ただ、テストコードを書くと、意外なことにModelを端折ってしまって書いてないので、テストコード自体が動作しないというケースもあったりしてソース自体にも手を加える必要は出てきます。そうすると動いているソースに手を入れない的な昔からあるようなセオリーで押しきれないことがわかります。これもソースのメンテナンス性を上げるためのRSpecの効能と考えていいでしょう。

Gemfile
group :development, :test do
  gem "rspec-rails" # RSpec本体
  gem "factory_bot_rails" # FactoryBot
  gem 'spring-commands-rspec' # RSpecのパフォーマンスを上げるGem
  gem 'capybara', '~> 2.15.2' # フィーチャーテストのためのCapibara
  gem 'rspec_junit_formatter' # CircleCI、JenkinsなどCIでSpec結果を読みやすくするためのGem
end

ちなみにRSpecは実行すると自動的に環境はRAILS_ENV=testになります。なので専用のデータベースをdevelopment用と別に作成する必要があります。またRails 5.1以降はテストで発生したデータを消すためのdatabase_cleanerは不要だそうです。これは5.1以降はテスト後にDBが自動的にRollbackしてデータを消すようになっているためだとか。データベースの作成に合わせてDBの設定にも以下を追加します。ちゃんとテスト用のDB名にしてあげるのがミソです。

config/database.yml
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: test_db

また、config/environments/test.rbも作成します。環境別固有設定を施すためです。
ほとんどdevelopment環境と一緒なのでconfig/environments/development.rbをコピーするだけですが、自分はweb_consoleを使っていてRSpecが動かないことがあったので以下の設定を追加しています。

config/environments/test.rb
  # web_console
  config.web_console.development_only = false

ここまでお膳立てしてようやく bundle とジェネレーターで環境を整えます。

bash
# RSpecインストール
$ bundle install

# 定型的な設定ファイルの作成
$ rails generate rspec:install
Running via Spring preloader in process 266
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

rails_helper.rbとspec_helper.rbの役割の違いはよくわからないですが、ほとんどのSpecファイルにrails_helper.rbへの読み込みが入るので、こちらにもっぱらヘルパーの設定を加えます。
今回はdevise使うのでそれ用のヘルパーとダミーデータ作成のためのFactoryBotに設定のヘルパーを加えています。コードの最後の方に追加しました。

spec/rails_helper.rb
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.include FactoryBot::Syntax::Methods

Deviseを使用しているアプリケーションの場合、ヘルパー本体はこちらに書きます。

spec/support/request_spec_helper.rb
module RequestSpecHelper
  include Warden::Test::Helpers

  def self.included(base)
    base.before(:each) { Warden.test_mode! }
    base.after(:each) { Warden.test_reset! }
  end

  def sign_in(resource)
    login_as(resource, scope: warden_scope(resource))
  end

  def sign_out(resource)
    logout(warden_scope(resource))
  end

  private

  def warden_scope(resource)
    resource.class.name.underscore.to_sym
  end
end

ベースとなる準備は以上です。

Seed-fuでマスターデータを用意

アプリケーションにもよると思いますが、ある程度マスターデータが用意されてないと凝ったテストが出来ないという人はFactoryBotではなくてSeedでDBに値を事前に入れておくのがいいかと思います。Railsに元からあるseedよりもseed-fuの方が使い勝手良さそうなのでこちらをインストールします。データの自動投入に汎用的に使えるGemです。

Gemfile
# Seed -fu
gem "seed-fu"

bundleで入れたら以下のファイルを作成します。

db/seed.rb
Spree::Core::Engine.load_seed if defined?(Spree::Core)
Spree::Auth::Engine.load_seed if defined?(Spree::Auth)

あとは投入データを作成します。投入するためのスクリプトはこちらです。これがそのままDBの定義とseedの実行母体になります。

db/fixtures/test/001_areas.rb
require 'csv'

csv = CSV.read('db/fixtures/test/001_areas.csv')
csv.each do |csvdata|
  Area.seed do |s|
    s.id = csvdata[0]
    s.prefstate_id = csvdata[1]
    s.photo = csvdata[2]
    s.lat = csvdata[3]
    s.lng = csvdata[4]
    s.created_at = csvdata[5]
    s.updated_at = csvdata[6]
  end
end

以下データ。

db/fixtures/test/001_areas.csv
1,13,"",,,"2016-08-15 00:00:00","2016-08-15 00:00:00"
2,13,"",,,"2016-08-15 00:00:00","2016-08-15 00:00:00"
3,13,"",,,"2016-08-15 00:00:00","2016-08-15 00:00:00"

ちなみにdb/fixtures/の下のtestは環境名です。上記を設定したら

RAILS_ENV=test rails db:seed_fu

でデータ投入できますが、この際に環境ごとに投入データを変えられます。そのためにディレクトリで分けているのです。ちなみに実行はファイルの接頭辞の番号順に行われますので、外部キー制約など順番を考慮したい場合はうまく活用してください。

その他DB操作で便利なコマンド

RAILS_ENV=test rails db:reset #テーブル作り直し
RAILS_ENV=test bundle exec rake db:schema:load #テーブル作り直し
RAILS_ENV=test rails db:seed_fu FILTER=029_styles FIXTURE_PATH=./db/fixtures/test #フィクスチャーごとにデータ投入

FactoryBotの作成

テストのたびにbeforeとかにDBのレコードを作成して用意するのが嫌(というかソースも見づらくて気持ち悪い)なので、データの生成をスマートにするためにFactoryBotというツールがあります。元々FactoryGirlという名称でしたが、ジェンダーコンシャスの流れを受けて今の名前になりました。既にGemfileに追加してインストールしているので後はファイルをspec/factories配下に書いていくだけです。必要となる各モデルごとに作成していきます。

FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "TEST_NAME#{n}"} # 名前のバリデーションにかからないもの
    sequence(:email) { |n| "TEST#{n}@example.com"} # メールのバリデーションにかからないもの
    password  { 'password' } # パスワードのバリデーションにかからないもの
  end
end

番号などシーケンシャルなものを挿入したい場合はsequenceというのを使います。

FactoryBot.define do
  factory :review, class: Review do
    restaurant_id {1620}
    user_id {1}
    rating {5}
    title {'What a hell?'}
    image  {Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.jpg'))}
    comment {'It was yummy, indeed'}
  end
end

画像データをカラムに入れている場合は上記のように画像のダミーデータをspec/fixtures配下に置いてRack::Test::UploadedFile.newとします。

リレーションがあるモデル(has_one/has_many/belongs_to)の場合、

app/models/menu.rb
class Menu < ActiveRecord::Base
  has_many :menu_translations, dependent: :destroy
app/models/menu_translation.rb
class MenuTranslation < ActiveRecord::Base
  belongs_to :menu

以下のようにFactoryBotを作成します。

spec/factories/menu.rb
FactoryBot.define do
  factory :menu, class: Menu do
    restaurant_id {1}
    menu_type  {5}
    image  {Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.jpg'))}
    last_update_user_id {1000}
    add_attribute(:public)  {'1'}

    association :restaurant
  end
end

ちなみにカラム名がRSpecの予約語とかぶってしまうものを使っている場合(aliasとかpublicなど)、add_attribute(:public)という書き方をすることが出来ます。

spec/factories/menu_translation.rb
FactoryBot.define do
  factory :menu_translation_ja, class: MenuTranslation do
    menu_id  {}
    menuname {'パルミジャーノとケールのイタリアン菜園サラダ'}
    description {'SサイズとMサイズがあります。'}
    price {1000}
    currency  {'円'}
    lang  {'ja'}

    association :menu
  end
end

associationでリレーションを定義します。

Modelのスペック

deviseをログイン認証機構に用いた場合のモデルのテストは以下の通りです。
deviseで注意すべきはモデル(今回だとUser)に書いてないバリデーションもあり、そちらもテストするのか、それ以外のカスタムバリデーションだけテストするのかを考えてコードを記載します。デフォルトではメールアドレスとパスワードの存在チェックはdeviseに組み込まれているようでした。
それ以外は普通にバリデーションやメソッドについてテストを記載します。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { build(:user)}

  describe 'Check Validations' do
    it 'default OK' do
      expect(user.valid?).to eq(true)
    end

    it 'if no password, then NG' do
      user.password = ''
      expect(user.valid?).to eq(false)
      expect(user.errors[:password]).to include 'パスワードを入力してください'
    end

    it 'if no name, then NG' do
      user.name = ''
      expect(user.valid?).to eq(false)
    end

    it 'if name exceed 120 letters, then NG' do
      user.name = '1234567891234567891234567891234567891234567891234567891234567891234567891234567891234567891234567891234567891234567890000000000000123456789012345678901234567890'
      user.completed_at = Time.current
      expect(user.valid?).to eq(false)
    end
  end
end

こちらがModelの本体です。これをテストするためにSpecを記載しました。

app/models/user.rb
class User < ApplicationRecord
  mount_uploader :pic, ImageUploader
  attr_accessor :current_password

  validates :name,
    length: { minimum: 1, maximum: 120 }, if: :is_registration?

  validates :name, presence: true
end

FactoryBotには正常系を作成しておいて、バリデーションのテストの中で異常データを挿入してvalid?を試すイメージです。

Controllerのスペック

EverydayRails ‐ RSpecによるRailsテスト入門というRSpecの有名書籍によればコントローラーのテストはやがてなくなり、requestsのテストとして集約されるとのことです。いろいろな現場でテストを書かせてもらいましたが、contorollerにテストを書いているところは多いです。コントローラーにアクセスして、所定のキーワードがあるかどうか、関連するモデルの数が増えたり減ったりするかといったテストです。スマホアプリ構築の流れでWebAPIの開発というパターンも多いことからこれをテストするためにrequestsにテストを書くということのようですが、ここにControllerも集約されるぽいです。認識違っていたらすみません。
以下は弊社サービスVegewelでレストランガイドをやっておりまして、ページを表示、レビューの投稿をテストするコードを書いたものです。

spec/requests/restaurant_spec.rb
require 'rails_helper'

RSpec.describe RestaurantController, type: :request do
  describe "GET index" do
    before do
      @restaurants = FactoryBot.create(:restaurant)
    end

    it "responds successfully" do
      get "/restaurant"
      expect(response).to be_success
    end
  end

  describe "GET show on restaurant detail" do
    it "responds successfully" do
      get "/restaurant/#{@restaurants.id}"
      expect(response).to be_success
    end
  end
end

コントローラーのテストの基本の作りはシンプルです。
ルーティングに合わせてget, post, patch, put, delete...を発効して結果をexpectのtoに期待値を書いていくというものです。

require 'rails_helper'

RSpec.describe ReviewsController, type: :request do
  describe "POST review" do
    let(:review) { FactoryBot.create :review }

    before do
      @user = FactoryBot.create(:user)
      @restaurant = FactoryBot.create(:restaurant)
      @user.confirm # deviseのメール認証が必要なケースでは必須
      sign_in @user
    end

    it "responds successfully" do
      expect{
        post '/reviews', params: { user_id: @user.id, restaurant_id: @restaurant.id, review: review }
      }.to change(Review, :count).by(1)
    end
  end
end

post など更新がかかるアクションにはパラメータを設定しておきます。
Factoryの使い方としては事前評価と遅延評価という考え方があり、もともとAだったものがBになるということをテストするならベースとなるデータを事前評価ということでlet!でデータをcreateなどしておいて、更新の結果で増減があるか確かめるという流れです。
単純にレストランデータを作って該当URL(restaurant/1234みたいな)もので表示するだけなら遅延評価でletでデータを作成するので十分かと思います。

ちなみにフォームは確認画面とかはなく、登録・更新ボタン一発で変更が加わるようにしています。
追加したら当該モデルの数が+1、更新したら変更後の該当カラムが想定通りに変更されていることを確認するようなコードにしています。

リレーションのあるモデルを同時に更新する場合のコントローラーのSpecの書き方

コントローラーに以下のようなparamsの設定があるとします。

app/controllers/menu_controller.rb
private
  def menu_params
    params.require(:menu).permit(:id, :restaurant_id , :image1, :image2, :image3, :last_update_user_id, :public, menu_type_list: [], menu_translations_attributes:[:id, :menu_id, :menuname, :description, :price, :currency, :lang])
  end

パラメータに親子関係を設定したものとFactoryの設定にassciationを仕込ませておいた上で以下のようなテストコードを書きます。

spec/requests/menu_spec.rb
  # 新規作成
  describe "POST /menu with authentication" do
    before do
      @admin_user = FactoryBot.create(:admin_user)
      sign_in @admin_user
    end

    it "Create new menu for Japanese" do
      menu_translation_params_ja = {
        menu_translations_attributes: {
          "0": FactoryBot.attributes_for(:menu_translation_ja)
        }
      }
      menu_params = FactoryBot.attributes_for(:menu, restaurant_id: restaurant.id).merge(menu_translation_params_ja)
      expect{
        post '/menu', params: { menu: menu_params }
      }.to change(Menu, :count).by(1)
    end

  # 更新
  describe "PATCH /menu with authentication" do
    let(:restaurant) { FactoryBot.create(:restaurant) }
    let!(:menu) { FactoryBot.create(:menu, restaurant_id: restaurant.id) }

    before do
      @admin_user = FactoryBot.create(:admin_user)
      sign_in @admin_user
    end

    it "Update menu for Japanese" do
      menu_translation_params_ja = {
        menu_translations_attributes: {
          "0": FactoryBot.attributes_for(:menu_translation_ja, menuname: "ベジプレート(大豆ミートの鶏から風)", price: 500, menu_id: menu.id)
        }
      }
      menu_params = FactoryBot.attributes_for(:menu, id: menu.id, restaurant_id: restaurant.id).merge(menu_translation_params_ja)
      patch "/menu/#{menu.id}", params: {menu: menu_params}
      expect(menu.menu_translations.first.menuname).to eq("ベジプレート(大豆ミートの鶏から風)")
      expect(menu.menu_translations.first.price).to eq(500)
    end
  end

上記同様にフォームは確認画面とかはなく、登録・更新ボタン一発で変更が加わるようにしています。
追加したら当該モデルの数が+1、更新したら変更後の該当カラムが想定通りに変更されていることを確認するようなコードにしています。

以上でコントローラーのテストについて記載しました。

サイトの表側のふるまいをテストするフィーチャーテスト

Capybaraを用いてサイト上の動作をシミュレートしたテスト(フィーチャーテスト)をすることが出来ます。
RSpecをやるまではテストのコードを書くというとこっちのイメージでした。Seleniumはこちらのイメージと言えるでしょう。
試しにサイトのトップページからリンクをたどって別ページに飛んで、そこから検索するというスクリプトを書いてみます。

spec/features/projects_spec.rb
require 'rails_helper'

RSpec.feature "Projects", type: :feature do
  scenario "Vegewel success scenario in English" do
    # Vegewel TOP
    visit '/en'
    expect(page).to have_content "Vegewel restaurant guide"
    expect(page).to have_content "Vegewel Style"

    # Restaurant Page
    click_link ("Restaurant")
    expect(page).to have_content "Tasty & Healthy Restaurants"
    expect(page).to have_content "VESPERA"

    # Search Restaurant 
    fill_in('q[g][1][restaurant_search]', with: 'Cafe')
    check('q[g][0][veganmenu_eq]')
    click_button ("Search")
    expect(page).to have_content "Cafe*teria HANIWA"
  end
end

ちなみに
特定のページを開く:visit
リンクを踏む:click_link
ボタンを押す:click_button
チェックボックスを押す:check
テキストボックスに入力:fill_in
で制御できます。ボタン押したり、リンククリックしたりの場所の特定はIDなどのセレクタや、ラベルなどが表示されていればその文言で指定することが出来ます。可読性を上げるためにボタンのラベルに指定するとかもアリでしょう。

その他の動作に関してはこちらを参照して、色々シミュレートしてみることが出来るかと思います。
https://qiita.com/morrr/items/0e24251c049180218db4
ご確認ください。

RSpecのテスト

$ bundle exec rspec

で全ての(モデル、コントローラー、フィーチャー)のテストは実行されます。個別に実行したければファイルを指定すればOKです。テスト通ればSuccessと表示され、NGだとエラー内容が表示されます。

CircleCIにRSpecのテストを組み込み

もともとBitbucketの特定ブランチ(Staging、Master)にソースをコミットしたらデプロイする仕組みを構築していました。そこにRSpecのテストを追加してみました。以下.circleci/config.ymlです。
本当は全体はかなり長いのですが、RSpec組み込んだところだけフォーカスしています。

.circleci/config.yml
version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2

jobs:
  build:
    working_directory: ~/xxxx
    parallelism: 1
    docker:
      - image: circleci/build-image:ubuntu-14.04-XXL-upstart-1189-5614f37

    steps:
      - checkout
      - run:
          (諸々Deployコマンド)
  rspec:
    parallelism: 3
    docker:
      - image: circleci/ruby:2.5.0-node-browsers
        environment:
          - BUNDLER_VERSION: 1.16.1
          - RAILS_ENV: 'test'
      - image: circleci/mysql:5.7
        environment:
          - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
          - MYSQL_ROOT_HOST: '127.0.0.1'
    steps:
      - checkout
      - restore_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ~/circleci-demo-workflows/vendor/bundle

      # Database setup
      - run: mv ./config/database.yml.ci ./config/database.yml
      - run:
          name: Databasesetup
          command: |
             bundle exec rake db:create
             bundle exec rake db:schema:load
             bundle exec rake db:seed_fu
      # run tests!
      - run:
          name: Run rspec
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"

            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES
      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

workflows:
  version: 2
  build-n-deploy:
    jobs:
      - rspec:
          filters:
            branches: #Spec走らせるブランチを限定(これないとフィーチャーブランチのコミット&Pushで勝手にCircleCIが走ってしまう)
              only:
                - staging
                - master
      - build:
          requires:
            - rspec # rspecしてからDeployするように
          filters:
            branches:
              only:
                - staging
                - master

こんな感じでJobを分割してRspecとしたところに
- テスト環境構築
- Seedデータを投入
- RSpecテスト実行
を実行しています。
workflowsのところに全体的な流れとして、先にRSpecを流してからOKならDeployを実行する流れにしています。
あと、これが最も重要なポイントですが、これを実行するためにCircleCIに課金しました。 1ヶ月$30 です。
複数Job実行にするのは無料プランでは出来なかったからです。この金額で25000ポイントもらえるので、各リソースの利用状況ごとにポイントが減らされる仕組みになっています。
弊社のサービスは1回のデプロイで300〜400ポイントくらい消費するので実行は慎重にやることにしています。ポイントが無くなってきたら、自動的に+$30してポイント追加になります。
課金するとJobの並行実行も可能です。複数の同時デプロイなどに使えるかと思うので使い方はご検討ください。

その他トラブル対応

特定のURLで200が返ってこなくてエラー

例えば以下のようなテストを書いたとして、エラーになって返ってこないケースです。

    it "Login returns 200" do
      get index_path
      expect(response).to have_http_status "200"
    end

エラーはこんな感じです。

Failure/Error: expect(response.status).to eq 200

       expected: 200
            got: 302

つまりリダイレクトしているってことですよね?調べたところapplication_controller.rbでdeviseログイン時やhttp->httpsリダイレクトで302をやらかしていました。また、Staging環境ではBasic認証かけたりとか色々やっていて、条件分岐に環境変数を使っていたことからtest環境へのケアが出来ていなかったのも要因でした。
上記エラーになったらapplication_controller.rbのbefore_actionを片っ端からコメントアウトして1個づつつけたり外したりして試してみるのが吉です。

アプリケーションFQDNがwww.example.comとなってしまう?bad URIも発生

テストに以下のようなコードを書いたところbad URIとなり、しかもアプリケーションはwww.example.comというFQDNになってました。

require 'rails_helper'

RSpec.describe "Restaurants", type: :request do
  describe "GET /index" do
    it "index responds successfully" do
      get :index
      expect(response).to be_success
    end
  end
end

以下エラー

URI::InvalidURIError:
  bad URI(is not URI?): http://www.example.com:80index

これは get :indexget '/' とすることでテストが通りました。:indexとすることで文字列にindexを足してしまうようです。また、www.example.comはRailsでActionPackの中にTestのSessionを司る箇所があり、そこで DEFAULT_HOST='www.example.com' と定義されていて、これがRSpec内でURLに指定がなければ自動的に設定されてしまうのが原因でした。ただ、通常のテストではwww.example.comが問題になることがなく「そういうもの」と考えて特に気にしなくて良い模様です。自分の場合はpryで何度止めてもこのFQDNがサーバ名として定義されているので焦りましたが、RSpecのメンターしてくれた先生に聞いたら気にしなくてOKと言われて安堵しました。

CircleCIでMySQLのDockerImage構築中にDBセットアップを始めようとしてエラーになる

4ad72481bdb0fd42bbe0a80b1947c175.png

この図で言うところの Container circleci/mysql5.7 のところが終わっていないのに、DBのセットアップが始まってしまうということがありました。そもそも当初CircleCIは課金していないので基本的に複数Jobを回すようにしておらず、以下のように環境構築、RSpec実行、AWSCLIの設定、ElasticBeanstalkのデプロイまでまとめてやろうとしていました。
その中でいうとimage: circleci/mysql:5.7のところが長い時間かかっているのですが、これと同期をとる方法がわかりませんでした。

version: 2
jobs:
  build:
    working_directory: ~/xxxx
    parallelism: 1
    shell: /bin/bash --login
    environment:
      CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
      CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
    docker:
    - image: circleci/build-image:ubuntu-14.04-XXL-upstart-1189-5614f37
    - image: circleci/ruby:2.5.0-node-browsers
      environment:
        - BUNDLER_VERSION: 1.16.1
        - RAILS_ENV: 'test'
    - image: circleci/mysql:5.7
      environment:
        - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
        - MYSQL_ROOT_HOST: '127.0.0.1'
    steps:
      - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS
      - run:
          working_directory: ~/xxxx
          command: pip install pip==20.3.4
      - run:
          working_directory: ~/xxxx
          command: pip install urllib3==1.26
      - run:
          working_directory: ~/xxxx
          command: pip install awsebcli --upgrade --user
      - checkout
      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle
      # Database setup
      - run: mv ./config/database.yml.ci ./config/database.yml
      - run:
          name: Databasesetup
          command: |
             bundle exec rake db:create
             bundle exec rake db:schema:load

      # run tests!
      - run:
          name: Run rspec
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"

            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

      - run:
          name: Deploy
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              echo "Deploy production"
              eb deploy Vegewel-production
            else
              echo "Deploy staging"
              eb deploy vegewel-staging
            fi

workflows:
  version: 2
  build-n-deploy:
    jobs:
      - build:
          filters:
            branches:
              only:
                - staging
                - master

最終的には課金して複数JobでRSpecとデプロイという形にしましたが、CircleCIのサポートによればDockerizeを使って待機させることもできるそうです。こちらのドキュメントを参照ください。
https://circleci.com/docs/ja/2.0/databases/#dockerize-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E3%81%AE%E5%BE%85%E6%A9%9F

acts_as_taggable_onを使った箇所のテスト

動的なタグ生成のためにacts_as_taggable_onを使って開発しているところもあると思います。非常に便利で私もよく利用しています。
これのテストですが、自分は以下のようにしました。前提として料理の種類をCuisineとしていて、コードのID:1に対してCuisineTranslationの日本語(lang:ja)に”和食”、英語(lang:en)に”Japanese”のように2レコード設定しているようなデータ構造とお考えください。また、タグ用のテーブルはtags, taggingsの2つです。

app/models/cuisine.rb
class Cuisine < ActiveRecord::Base
  ActsAsTaggableOn::Tagging.table_name = 'taggings'
  ActsAsTaggableOn::Tag.table_name = 'tags'

    has_many :cuisine_translations
end
app/models/cuisine_translation.rb
class CuisineTranslation < ActiveRecord::Base
        belongs_to :cuisine
        belongs_to :restaurant

        scope :with_lang , -> { where(lang: I18n.locale )}
end

これを扱うRestaurantモデルは

app/models/restaurant.rb
class Restaurant < ActiveRecord::Base
  ActsAsTaggableOn::Tagging.table_name = 'taggings'
  ActsAsTaggableOn::Tag.table_name = 'tags'

  acts_as_taggable_on :stations, :lines, :cuisines

こんな感じです。Factoryの設定はTagに対して行います。

spec/factories/tag.rb
FactoryBot.define do
  factory :cuisine_list, class: ActsAsTaggableOn::Tag do
    name {Cuisine.first.id}
  end

として、RestaurantのFactoryに以下のようにかけば動くはずです。

spec/factories/restaurant.rb
FactoryBot.define do
  factory :restaurant, class: Restaurant do
    station_list {[FactoryBot.build(:station_list)]}
    line_list {[FactoryBot.build(:line_list)]}
    cuisine_list {[FactoryBot.build(:cuisine_list)]}

最後に

かなり長い文章になってしまいました。こういうときは記事を分けるのがいいかなと思いつつ、大半は他のQiitaにも書いてそうなことなので付加価値出そうとしてボリューミーになってしまいました。多少なりともお役に立つ内容があれば幸いです。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0