質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

RSpec

RSpecはRuby用のBDD(behaviour-driven development)フレームワークです。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

2回答

1630閲覧

RSpecモデルテストの超基礎 どの参考書にもこう書いてあるが、これはダメなような気がする

annaPanda

総合スコア130

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

RSpec

RSpecはRuby用のBDD(behaviour-driven development)フレームワークです。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2020/03/21 02:50

編集2020/03/21 02:54

deviseをデフォルトが状態(emailとpasswordのみ)で使っているアプリのテストです。
ユーザー登録のテストを考えていて、factorybotを使わない状態でのテスト方法に疑問です。
「Everyday Rails - RSpecによるRailsテスト入門」などを参考にしています。

GitHub → https://github.com/annaPanda8170/minimum_rails_application/tree/%E3%80%8CEverydayRails-RSpec%E3%81%AB%E3%82%88%E3%82%8BRails%E3%83%86%E3%82%B9%E3%83%88%E5%85%A5%E9%96%80%E3%80%8D%E8%B3%AA%E5%95%8F%E7%94%A8

require 'rails_helper' RSpec.describe User, type: :model do it "ok" do user = User.new(email: "a@a", password: "111111", password_confirmation: "111111") expect(user).to be_valid end it "ng1" do user = User.new(email: "a@@a") user.valid? expect(user.errors[:email]).to include("is invalid") end it "ng2" do user = User.new(email: "a@@a") user.valid? expect(user.errors[:password]).to include("can't be blank") end end

factorybotであれば、一度ユーザーを作って変更したい部分のみを再代入する方法が有効であると思いますが、上のようにfactorybotを使わないでnew変数を使う場合別のインスタンスが作られるので、"ng1"の中のuser = User.new(email: "a@@a")と、"ok"の中のuser = User.new(email: "a@a", password: "111111", password_confirmation: "111111")は関連がなく、"ng1"と"ng2"のuser = User.new(email: "a@@a")ではpasswordはただただ入力がなされてないものであるので、"ng2"も通ります。

これはテストとして正当なのでしょうか?

先輩方意見をお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

「Everyday Rails - RSpecによるRailsテスト入門」を読んでいただき、どうもありがとうございます。
さっそく回答しようと思うのですが、すいません、僕はそもそも、載せていただいたテストコードに関してご質問の内容と直結しない部分が一番気になりました。
具体的には以下の3点です。

  • a@a というのがまずありえないメールアドレスなので、正しいメールアドレスに見えない
  • it "ng1"it "ng2"では第三者から見て、いったい何をテストしようとしているのかわからない
  • もし「メールアドレスが不正ない場合」を検証したいのであれば、それ以外の項目(ここではパスワード)を正常にした方がテストの意図が伝わりやすい

というわけで、僕だったらこんなふうにテストを書きます。
(コメントは僕からの補足説明です)

ruby

1require 'rails_helper' 2 3RSpec.describe User, type: :model do 4 # itの説明を詳しく書く 5 it "全項目が正常なので検証エラーが起きない" do 6 # メールアドレスをある程度リアルなものにする 7 user = User.new(email: "alice@example.com", password: "111111", password_confirmation: "111111") 8 expect(user).to be_valid 9 end 10 11 it "メールアドレスが不正なので検証エラーになる" do 12 # メールアドレスだけ不正な状態にして、パスワードは正常にする 13 user = User.new(email: "alice@@example.com", password: "111111", password_confirmation: "111111") 14 user.valid? 15 expect(user.errors[:email]).to include("is invalid") 16 end 17 18 it "パスワードが未入力なので検証エラーになる" do 19 # パスワードだけ未入力にして、メールアドレスは正常にする 20 user = User.new(email: "alice@example.com") 21 user.valid? 22 expect(user.errors[:password]).to include("can't be blank") 23 end 24end

このようなテストであれば大きな問題はないと思います。
(各テストのuserに関連があるかどうかは特に気になりません)

ちなみに、これは応用編になりますが、以下のようなポイントを押さえれば、よりRSpecらしいテストになります。

  • 検証したい処理(ここではvalidation)をdescribeブロックで囲む
  • 「もし〜の場合」に当たる部分をcontextで表現する
  • letを活用して、重複を無くす

具体的にはこんな感じです。

ruby

1require 'rails_helper' 2 3RSpec.describe User, type: :model do 4 describe 'validation' do 5 let(:user) do 6 User.new(email: email, password: password, password_confirmation: password) 7 end 8 9 context '全項目が正常な場合' do 10 let(:email) { "alice@example.com" } 11 let(:password) { "111111" } 12 it "検証エラーが起きない" do 13 expect(user).to be_valid 14 end 15 end 16 17 context 'メールアドレスが不正な場合' do 18 let(:email) { "alice@@example.com" } 19 let(:password) { "111111" } 20 it "検証エラーになる" do 21 user.valid? 22 expect(user.errors[:email]).to include("is invalid") 23 end 24 end 25 26 context 'パスワードが未入力の場合' do 27 let(:email) { "alice@example.com" } 28 let(:password) { nil } 29 it "検証エラーになる" do 30 user.valid? 31 expect(user.errors[:password]).to include("can't be blank") 32 end 33 end 34 end 35end

contextやletといった構文の説明はすべてEveryday Railsの中で説明しているはずですので、本書を参照してください。

とはいえ、「RSpecらしさ」を追求すると沼にはまってしまうというか、「RSpecのいろんな機能を使いまくりたい!!」という変な欲求に駆られがちになるので、個人的には一つ前のコードでも十分じゃないかなーと思います。

以上のような回答で参考になるでしょうか?
もし解決していない疑問点があればコメントしてください。

追記

first_name以外の入力がなされていないことに不満を持っています

たしかにEveryday Railsのコード例はfirst_name: nil以外の項目が未入力ですね。
それ以外の項目はちゃんと入力されていてfirst_nameだけが未入力、の方が「意図が伝わりやすい」という意味でベターです。
ですが、expect(user.errors[:first_name]).to_not include("can't be blank")という形で「first_nameが未入力なら検証エラーになること」を担保できているので、テストの機能的には問題ない気がします。

投稿2020/03/21 12:44

編集2020/03/21 22:16
jnchito

総合スコア357

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

annaPanda

2020/03/21 13:13

a@aについて あくまで私はdeviseがかけている制限、`config/initializers/devise.rb`に書いてある、`config.email_regexp = /\A[^@\s]+@[^@\s]+\z/`を元にそれを満たす最低限のものを考えてa@aとしています。実際a@aで問題なく登録できます。 "ng1"や "ng2"について こちらはすみません、完成した段階で他人でもわかるように整えるつもりでした。 そして、書いてくださった一つ目のテストは十分に納得がいきます。"メールアドレスが不正なので検証エラーになる"において、passwordも入力があるからです。 ただ、Everyday Rails - RSpecによるRailsテスト入門において、(ページ数がないので指定がしにくいですが) ``` # 名がなければ無効な状態であること 2 it "is invalid without a first name" do 3 user = User.new(first_name: nil) 4 user.valid? 5 expect(user.errors[:first_name]).to include("can't be blank") 6 end ``` の部分にfirst_name以外の入力がなされていないことに不満を持っています。
annaPanda

2020/03/22 06:01

私が感じたことは、first_name: nil以外の未収力項目があると、expect(user.errors[:first_name]).to_not include("can't be blank")で検証エラーが取れる理由が、first_nameが未入力であると特定できないということでした。(もちろん常識的に考えればそうですし、user.errors[:???]にfirst_nameを代入しているのもわかっています。でも論理的には特定できていません。) エラー原因を正確に特定するには、エラーのない状態から、一手一手変えていって、エラーを集めていくしかないと考えていたので、違和感を覚えました。 でもお話を聞いている限りだと、一般的にはコードテストにおいてそう言った思想はないようですね。。 このことを一文で示すと「コードテストにおいては、エラーの十分条件を示す必要はなく、必要条件のみを示せばよい」ということですかね。 初学者が生意気に失礼いたしました。
guest

0

"ng1"の中の(中略)と"ok"の中の(中略)は関連がなく

はい、それは意図したものです。各it間は、むしろ関連があってはいけません(テスト順に依存する、不適当なテストとなります)。

passwordはただただ入力がなされてないものであるので、"ng2"も通ります。

はい、パスワードが入力されていないので、エラーメッセージが生成されて、「パスワードがないエラーメッセージが存在する」というテストを通過します

これはテストとして正当なのでしょうか?

たしかに、必要以上にバリデーションに引っかかるのは気になるので、「ng1ではパスワードを指定する」「ng2では正しいメールアドレスを指定する」はしたほうがいいかもしれません。ただ、今のままでもテストとして動作に問題はありません。

投稿2020/03/21 03:07

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2020/03/21 03:12

もっとも、「特定のエラーメッセージが存在することをチェックする」というのはロケール変更などに弱いです。 チェックしたい内容によっては「当該列のエラーメッセージが存在するかどうかチェックする」あるいは「当該列以外はすべてvalidになるようにデータを埋めて、全体がinvalidになるかを見る」ほうが適当かもしれません。
annaPanda

2020/03/21 03:19

回答ありがとうございます。 例えば上掲のテキストでは、 last nameが入力必須になっていて、 ``` it "is invalid without a last name" do user = User.new(last_name: nil) user.valid? expect(user.errors[:last_name]).to include("can't be blank") end ``` このようにテストしているのですが、 基本的にテストは対照実験(項目一つだけを変更しての比較実験)でないと意味がないと思います。 つまり、上の私が書いたテストも含めて、ただエラーが出ないだけのテストとして価値がないもののように感じるのですが、いかがでしょうか?
maisumakun

2020/03/21 03:22

> 基本的にテストは対照実験(項目一つだけを変更しての比較実験)でないと意味がないと思います。 その考え方は、おそらく一般的ではありません。 > ただエラーが出ないだけのテストとして価値がないもののように感じるのですが ただテストを書くのではなく、「フェイルファースト」でやってみるとまた違う気はします。
annaPanda

2020/03/21 04:09 編集

> その考え方は、おそらく一般的ではありません。 なるほど、この前提に齟齬があったんですね。別意見も少し待ってみます。 > フェイルファースト まずバリデーションに引っかかるパタンを挙げていくって感じですか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問