こんにちは。
Cryptogamesで働いているじゅん(@BoNingennnN)です。
Railsでは標準でminitestが入ってきます。
しかし、弊社ではminitestを使わずRSpecを使っています。
また、個人プロジェクトでもRSpecを使っています。
今回はRailsプロジェクトにRSpecを導入する方法を説明し、そしてテストの手始めとしてモデルのテストを書いていこうと思います!
まずはRailsプロジェクトにRSpecを導入しよう
既存のプロジェクトにRspecを導入する際には、既存のtestを削除する必要があります。
まずtestディレクトリを削除しましょう。
続いてRspecのgemをいれていきます
group :development, :test do
gem 'rspec-rails'
end
bundle install
もしまだ既存のプロジェクトでtest用のdbを作成していない場合はそれを作成します。
config/database.ymlにtest用の記述があることを確認したら以下のコマンドを打ちましょう
rails db:create
これでtest用のdbの作成が完了です。
続いてRspecの導入を進めます。
以下のコマンドでRspecの設定ファイルなどを生成しましょう。
rails g rspec:install
ここはお好みになりますが、testの実行結果が見やすいように以下の記述を.rspec内に書いておくといいです。
--format documentation
これでRspecの導入は完了しました!
早速以下のコマンドを実行してテストを実行しましょう。
bundle exec rspec
結果は、、
No examples found.
Finished in 0.00079 seconds (files took 0.25041 seconds to load)
0 examples, 0 failures
大成功です。
まだテストを書いていないので、0 expamleです。
exampleはテストの数、failuresは通らなかったテストの数を返します。
まだテストを書いていないので、もちろんexample、failures共に0です。
では続いて超初歩的なモデルのテストを書いていきましょう。
モデルのテストをRspecで書こう
ここからはモデルのテストを書きます。
ここではすでにPostモデルがあると仮定します。
(ここは各自のアプリケーションにあるモデルと置き換えて考えてください🙇♂️)
class Post < ApplicationRecord
validates :title, presence: true, length: {maximum: 20}
validates :content, presence: true
def content_length
self.content.length
end
end
こんな単純なモデルはありえないですが、入門ということで簡単なモデルにしました。
既存のモデルのテストを作るには以下のコマンドを実行します。
rails g rspec:model post
すると以下のファイルが生成されます。
require 'rails_helper'
RSpec.describe Post, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
ここで先ほどと同様にtestを実行してみましょう。
bundle exec rspec
すると先ほどとは異なる出力が見られます。
Pending: (Failures listed here are expected and do not affect your suite's status)
1) Post add some examples to (or delete) /Users/junya/workspace/myprojects/rails-rspec-test/spec/models/post_spec.rb
# Not yet implemented
# ./spec/models/post_spec.rb:4
Finished in 0.00684 seconds (files took 4.16 seconds to load)
1 example, 0 failures, 1 pending
注目して欲しいのは、exmapleが0から1に変わった部分です。
これは先ほど生成したpost_spceを生成したからです。
pendingとなっているのは、「このテストは一旦後回しにしますよー」という意味です。
生成されるspecファイルは、初期でpendingとなっています。
この部分を実際に書き換えていきましょう!
と、その前にまずモデルテストでテストすべきことを書いていきます。
基本的には、そのモデルのバリデーションのテストを書きます。
さらに、そのモデルがインスタンスメソッドを持っているのであればそのテストも書きますし、クラスメソッドを持っていればクラスメソッドのテストも書きます。また、scopeに関するテストももちろん必要です。
今回の例だと、バリデーションとインスタンスメソッドがあるので、それぞれのテストを書いていきます。
バリデーションのテストを書いてみる
早速バリデーションのテストを書いていきましょう。
require 'rails_helper'
RSpec.describe Post, type: :model do
it "is valid with title and content" do
post = Post.new(title: "test", content: "testcontent")
expect(post).to be_valid
end
end
先ほどpendingと書かれていた部分を削除し、実際にテストを書いてみました。
テストを書く際には
it "テストの内容の説明" do
テストの処理
end
という形で書きます。
モデルテストを書く際には、まずちゃんとバリデーションが通ること(be_valid)をテストします。
ここでテストを実際に実行してみましょう。
bundle exec rspec
出力は以下のようになります。
.
Finished in 0.01384 seconds (files took 3.45 seconds to load)
1 example, 0 failures
見事に1exampleが実行され、failure(テストの失敗)は0です!
ここで一度テストをわざと失敗させてみましょう。
先ほど作成したテストの中身を少し書き換えてみます。
require 'rails_helper'
RSpec.describe Post, type: :model do
it "is valid with title and content" do
post = Post.new(title: "test", content: "testcontent")
expect(post).not_to be_valid #toからnot_toに変更
end
end
そしてテストを実行してみると、、、
F
Failures:
1) Post is valid with title and content
Failure/Error: expect(post).not_to be_valid
expected #<Post id: nil, title: "test", content: "testcontent", created_at: nil, updated_at: nil> not to be valid
# ./spec/models/post_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0.0629 seconds (files took 3.57 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:4 # Post is valid with title and content
見事に1example 1failureになっています!
テストが失敗したことがわかるかと思います。
こんな感じで、もし通らないテストがあるとちゃんと表示されます。
先ほど変更した部分を元に戻したら、バリデーションテストの続きを書いていきましょう。
今回のPostモデルには3つのバリデーションがあります。
・titleが存在すること
・titleの最大文字数は20文字
・contentが存在すること
それらのバリデーションのテストを実際に書いていきます
it "is invalid without title" do
post = Post.new(title: nil)
post.valid?
expect(post.errors[:title]).to include("can't be blank")
end
it "is invalid withoug content" do
post = Post.new(content: nil)
post.valid?
expect(post.errors[:content]).to include("can't be blank")
end
titleとcontentのpresence: trueのバリデーションのテストをまず書きました。
もしtitleがnilなら「titleは空欄じゃダメですよ」っていうエラーがあるよね、ということをテストしています。(contentも同様)
このパターンはバリデーションのテストの際によく出てくるパターンです。
続いて文字数の部分のテストを書きます。
it "is invalid when title is more than 20 characters" do
post = Post.new(title: ("a" * 21))
post.valid?
expect(post.errors[:title]).to include("is too long (maximum is 20 characters)")
end
21文字だとエラーが出るよね、ということをテストしています。
本来だったら20文字の場合はエラーだなくてvalidだよねというのもテストした方が良さそうですが、ずいぶん長くなってきたので割愛します。
以上がバリデーションのテストです。
バリデーションのテストは結構パターンがあるので慣れればすんなり書けます。
また、バリデーションをうっかり忘れていた、なんてことも気付けたりしますので、そういう意味でもバリデーションテストはしっかり書いておいたほうがいいです。
インスタンスメソッドのテストを書いてみる
続いてインスタンスメソッドのテストも書いてみます。
今回の例だと、
def content_length
self.content.length
end
このcontent_lengthというインスタンスメソッドのテストを書きます。
インスタンスメソッドやクラスメソッドでは、適当に入力する値をいれ、その出力が正しいかをテストします。
と言ってもよく分からないかもしれないので、実際にコードを書いていきましょう。
it "returns length of content" do
post = Post.create(title: "test", content: ("a" * 100))
expect(post.content_length).to eq(100)
end
なんとなく見ればわかるかと思いますが、contentが100文字なpostを作成し、
post.content_lengthが100と等しいことをテストしています。
これは非常に簡単な例ですが、複雑になってきても基本はこれです。
こんな感じでどんどんインスタンスメソッドやクラスメソッドのテストも書いていきましょう!!
さいごに
本当はもっと複雑なメソッドのテスト等も書きたかったのですが、最初から難しいめのテストを書くと挫折してしまう可能性があると思ったので、今回は超簡単なものだけにしました。
(本当はただ書くのが面倒くさかっただけかもです。笑)
今回の例は初歩の初歩で、factory_botも使っていませんし、モデル以外のテストも全く出てきませんでした。
実際のプロジェクトでテストを書くとなると、学ぶべきことは他にも色々ありますが、とりあえずはこのモデルの基本的なテストを書けるようになっておけばあとは色々ググりながらどうにかなるかと思います!
もし今担当しているプロジェクトや、個人開発のプロジェクトでテストを書いていないのであれば、まずはモデルテストから書いてみてください!