一連の記事の目次
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