LoginSignup
1
1

More than 5 years have passed since last update.

rails、君に決めた!!~13

Last updated at Posted at 2018-03-27

一連の記事の目次
rails、君に決めた!!~目次

テスト

実行環境の構築

サンプル用アプリを作る

mkdir rspec-sample
cd rspec-sample
bundle init

Gemfileのgem "rails"のコメントアウトを外し、

bundle install --path vendor/bundle --jobs=4
bundle exec rails new ./ -d mysql --skip-turbolinks --skip-sprockets --skip-test

次にデータベースを用意する

mysql.server start
bin/rake db:create RAILS_ENV=test

データベースが作成されているか確認

bin/rake db:create RAILS_ENV=test

mysql -u root

mysql> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| rails_app              |
| rails_app5_development |
| rails_app5_test        |
| rails_app_test         |
| rspec-sample_test      |
| sys                    |
+------------------------+
9 rows in set (0.01 sec)

mysql> exit
Bye

テスト用のgemのインストール

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'database_cleaner'
  gem 'faker'
  gem 'pry-rails'
  gem 'pry-coolline'
end

ここ、間違えてgroup :developmentに書かないように注意!

bundle install

次にRspecのインストールと使用準備

bin/rails g rspec:install

Running via Spring preloader in process 49994
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

railsに関する設定: rails_helper.rb
rspecに関する設定: spec_helper.rb

DatabaseCleanerを設定する。
テストする前のデータベースの状態が常に同じになるようにする?
テストすると元のデータが変更されてしまうから元の状態に戻したいよねってことかな

テストは意図しない結果にならないように、そのテストに必要最低限なデータだけを準備して実行する必要がある。
例えば、あるテストケースを実行する際に他のテストケースで生成されたデータの影響を受けてテストケースが失敗するということを避けなければならない。
database_cleanerを利用すれば、テスト前にDBを初期化・テスト後にテストで作ったデータの後始末を実行することができて、テストの独立性を担保することができる。

fmfm

spec/rails_helper.rb

RSpec.configure do |config|
  # テストを開始する際の処理を記述
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  # サンプル実行前後の処理を記述
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
end

次にFactoryBotを設定
よくわからないけどテストデータを簡単に生成してくれるんだと。

spec/rails_helper.rb

Rspec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

これでRspecの準備は完了

テストを書く

まずテストのための簡単なモデルを作る

bin/rails g model Article title:text body:text status:integer published_at:datetime

# migrationを実行
bundle exec rake db:migrate RAILS_ENV=test

モデルやマイグレーションファイルの他に、spec/models/article_spec.rbとspec/factories/article.rbが作られる

spec/models/article_spec.rb

require 'rails_helper'

RSpec.describe Article, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end

Articleモデルのテストケースを追記していくファイル(スペックと呼ぶ)

spec/factories/articles.rb

FactoryBot.define do
  factory :article do
    title "MyText"
    body "MyText"
    status 1
    published_at "2018-03-26 18:42:13"
  end
end

FactoryBotの動作確認してみよー

bin/rails c test --sandbox

[1] pry(main)> FactoryBot.create(:article)                                                                                                       

=> #<Article:0x007fb089158d08
 id: 1,
 title: "MyText",
 body: "MyText",
 status: 1,
 published_at: Mon, 26 Mar 2018 18:42:13 UTC +00:00,
 created_at: Mon, 26 Mar 2018 12:40:31 UTC +00:00,
 updated_at: Mon, 26 Mar 2018 12:40:31 UTC +00:00>

よっしゃ〜

最後にArticleモデルに下記の変更を加える
app/models/article.rb

class Article < ApplicationRecord
  enum status: {draft: 0, published: 1}

  def abbreviated_title
    title.size >= 20 ? "#{title.slice(0, 19)}..." :title
  end

  def publish
    return if self.published?
    update({status: Article.statuses['published'], published_at: Time.current})
  end 
end

abbreviate: 短縮する

Rspecの基本文法

Rspec内で利用できるメソッドをいくつか見ていく

it

サンプルを定義する

テストのサンプルを定義できるメソッド。実際に書いてみたほうがわかりやすい
spec/models/article_spec.rb
(ここ本ではapp/models/article.rbに書けって書いてあるけど誤植かな? p193)

RSpec.describe Article, type: :model do
  it '記事タイトルがそのまま帰ること' do
    article = Article.new(title: 'タイトルだよ')
    expect(article.abbreviated_title).to eq 'タイトルだよ'
  end

  it '記事が公開状態になること' do
    article = Article.new(status: :draft)
    article.publish
    expect(article.published?).to be_truthy
  end
end

describe

あるサンプルが、どのテストの対象なのかを明らかにする

RSpec.describe Article, type: :model do
  describe '.abbreviated_title' do
    it '記事タイトルがそのまま帰ること' do
      article = Article.new(title: 'タイトルだよ')
      expect(article.abbreviated_title).to eq 'タイトルだよ'
    end
  end

  describe '.publish' do
    it '記事が公開状態になること' do
      article = Article.new(status: :draft)
      article.publish
      expect(article.published?).to be_truthy
    end
  end
end

context

サンプルケースの状況を細かく説明する

テストのサンプルが失敗した時に、ログがわかりやすくなる

RSpec.describe Article, type: :model do
  describe '.abbreviated_title' do
    context '記事タイトルが20文字未満の場合' do
      it '記事タイトルがそのまま帰ること' do
        article = Article.new(title: 'タイトルだよ')
        expect(article.abbreviated_title).to eq 'タイトルだよ'
      end
    end

    context '記事タイトルが20文字以上の場合' do
      it '記事タイトルが省略されること' do
        article = Article.new(title: 'a' *20)
        expect(article.abbreviated_title).to eq "#{'a' *19}..."
      end
    end
  end

  describe '.publish' do
    context '記事が非公開状態の場合' do
      it '記事が公開状態になること' do
        article = Article.new(status: :draft)
        article.publish
        expect(article.published?).to be_truthy
      end
    end

    context '記事が公開状態の場合' do
      it '記事が公開状態のままであること' do
        article = Article.new(status: :published)
        article.publish
        expect(article.published?).to be_truthy
      end
    end
  end
end

matcher(マッチャ)

expect(article.abbreviated_title).to eq 'タイトル'
expectメソッドの後にチェインしているtoメソッドの第一引数に指定されているもの。
上の例ではeq

フック

テスト前後で実行しておきたい処理を定義できる仕組み

before(:each) { @article = Article.new }

before(:each) do
  @article = Article.new
end

例えば、テスト実行前にデータベースを掃除する処理で使える

え、DatabaseCleanerでやるんじゃなかったの??
違いがよくわからないw

インスタンス変数を定義することで、サンプル間で同じオブジェクトを参照しあうことが可能

全然わからぬ〜。テストAとテストBで同じオブジェクトを利用できるってこと?

before(:each) { @article = Article.new }

before(:each) do
  @article = Article.new
end

フック

before
after
around

フックスコープ

each,example
all, context
suite

pending, skip

pending:未決定の、〜中の

英語の勉強になる笑

pending:どうしても落ちてしまうテストを保留する
skip:実装途中のサンプルなどに対して一時的なスキップを適用する

どういうシチュエーションで使うのかいまいちイメージできないけど、
expect(1).to eq 2みたいに当然落ちるテストを保留にできるのがpending

skipは、pendingと違いコールされたところで処理が中断され、結果に保留が表示される。

skipはわかる、ちょっと保留したい時便利。pendingはよくわからないw。いつ使うの??

モデル以外のテスト

コントローラのテスト

マイページにはログイン状態でナイトアクセスできない、というのをテストするとき、

RSpec.describe MyPageController, type: :controller do
  describe 'GET #index' do
    context 'ログイン状態の場合' do
      it 'マイページが表示されること' do
        login
        get :index
        expect(response.status).to render_template('mypage/index')
      end
    end

    context '未ログイン状態の場合' do
      it 'トップページにリダイレクトされること' do
        get :index
        expect(response).to redirect_to root_path
      end
    end
  end
end

ヘルパーのテスト

RSpec.describe ApplicationHelper, type: :helper do
  describe '#image_with_size' do
    it 'GETクエリに指定パラメータが付与されたURLが返ること' do
      expect(helper.image_with_size('http://example.com/hoge.jpg', 30)).to eq('http://example.com/hoge.jpg?size=30')
    end
  end
end
1
1
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
1
1