単体テストの基礎〜RSpecを利用した基礎的なテストコード〜
RSpecの準備をする
Pictweetのテストコードを書いていく。
まずは「rspec-rails」というGemをインストールし、RSpecを利用できるようにする。続いて、RSpecの設定を行う。その後、実際にRSpecのコードを書きながらRSpecの基礎文法を学ぶ。
Gemをインストール
RSpecを利用するためのGem「rspec-rails」をインストールする。
テキストエディタで、Pictweetのディレクトリを開く
テキストエディタを起動し「command + o」を実行。
続いてGemfileを編集する。
PictweetのGemfileの下部を以下のように編集
また web_console
というgemはtest環境で動かすと不具合が起きる可能性があるgemなのでdevelopment環境でのみ動くようにする。
group :development do ~ endの記述が無ければ下記のように作成し、既にある方はその記述の間にweb_consoleを移動させる。
web_console
は既に記述がある場合は、記述場所を移動するだけ。追記してしまうと重複してしまう可能性があるため注意。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'pry-rails'
gem 'compass-rails'
gem 'sprockets'
gem 'kaminari'
gem 'devise'
gem 'rspec-rails'
end
group :development do
gem 'web-console'
end
|
Gemfileを編集したら、忘れずにbundle install
を。
1 2 3 4 5 6 7 |
これでGem「rspec-rails」をインストールすることができた。
RSpecの設定
続いてRSpecの基本設定をしていく。
まずはRSpec用の設定ファイルを作成する必要がある。
下記のようにファイルが作成されれば成功。
上記の作業によって作成された2つのファイル「spec_helper.rb」と「rails_helper.rb」に関して簡単な説明をしておく。
rails_helper.rb
RailsにおいてRSpecを利用する際に、共通の設定を書いておくファイル。各テスト用ファイルでこちらのファイルを読み込むことで、共通の設定や、メソッドを適用する。
spec_helper.rb
rails_helper.rbと同じくRSpec用の共通の設定を書いておくファイルだが、こちらはRSpecをRails無しで利用する際に利用する。
.rspecに以下を追加
1 |
--format documentation
|
以上で準備は完了。
RSpecが正常に利用できるか確かめる
RSpecを利用して書いたテストコードは、ターミナルからコマンドを打つことで全て自動的に実行される。今はまだテストコードを作成していないが、こちらのコマンドを試すことは可能なので、やってみる。
1 |
$ bundle exec rspec
|
以下のような結果になれば、RSpecを利用する準備はできている。
1 2 3 4 5 |
No examples found.
Finished in 0.00031 seconds (files took 0.19956 seconds to load)
0 examples, 0 failures
|
Pictweetの元のコードを編集
テストコードを書く際に、バリデーションが正常に働くようにコードを修正する。
app/views/devise/registrations/new.html.erbを以下のように編集する
nicknameを入力するフォームを出力しているtext_field
タグに付いているmaxlength: 6
という記述を削除
1 2 3 4 5 6 7 8 9 |
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :nickname %> <em>(6 characters maximum)</em><br>
<%= f.text_field :nickname, autofocus: true %>
</div>
#省略
|
続いてuser.rbを編集する。
app/models/user.rbを以下のように編集
Userクラス内に、validates :nickname, ~
の記述を追加する。
1 2 3 4 5 6 7 8 9 10 11 |
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :tweets
has_many :comments
validates :nickname, presence: true, length: { maximum: 6 }
end
|
これで準備は完了。
モデルクラスのテストコードを書く
Railsにおいて基礎となる単体テストであるモデルのバリデーションに関するテストコードを書く。Pictweetには「userモデル」が存在するので、こちらに対応するテストコードを書いていく。
事前に知っておくべきこと
RSpec用ディレクトリの構造
RSpecによるテストコードが書かれたファイルのことを、specファイルと呼ぶ。全てのspecファイルは、先ほどのrails g rspec:install
コマンドで生成された「specディレクトリ」の中に格納しておく。
モデルに関するテスト用ファイルであればspec/models/
以下に、コントローラーに関するテスト用ファイルであればspec/controllers/
以下に格納される。app
ディレクトリ以下にあるテストの対象となるコードの在り処と対応させる。
【例】specファイルを配置する場所
- pictweet
- app
- models
- user.rb
- controllers
- tweets_controller.rb
省略・・・
- spec
- models
- specファイル
- controllers
- specファイル
- models
specファイルの命名規則
specファイルは対応するクラス名_spec.rb
という名前になる。今回はまず「user.rb」に関するspecファイルを作成するので、その場合の名前は「user_spec.rb」になる。
テストコードの基本
続いて、「1 + 1が2になることを確かめる」という簡単なテストコードを例として、以下に挙げる基本的なテストコードの文法について。
【例】1 + 1が2になることを確かめるテストコード
1 2 3 4 5 |
describe "hogehoge" do
it "1 + 1は2になること" do
expect(1 + 1).to eq 2
end
end
|
上記のコードの概要を説明する。
まず、describe
というキーワードでテストをグループ化します。
続いて、テスト1つ(example)として評価されるit do ~ end
のブロックの中に、expect(X).to eq Y
という形式の式を書いていく。これが、実際にテストが成功するかどうかチェックされる式(エクスペクテーション)になる。
・describe
・itとexample
・エクスペクテーション
・expectメソッド
・マッチャ
describe
1行目のdescribe
は、直後のdo ~ end
までのテストのまとまりを作る。describe
の後に続く""
の中にはそのまとまりの説明を書く。
itとexample
2行目のit
はexampleと呼ばれる実際に動作するテストコードのまとまりを表す。it
の後に続く""
の中にはそのexampleの説明を書く。
エクスペクテーション
実際に評価される式のこと。it do ~ end
の間に書く。上記の式ではexpect(1 + 1).to eq 2
の部分がエクスペクテーション。
expect(X).to eq Y
エクスペクテーションの文法。xの部分に入れた式の値がYの部分の値と等しければ、テストが成功する。eq
の部分を、マッチャと言う。
マッチャ
エクスペクテーションの中で、テストが成功する条件を示す。例えばeq
は「等しければ」という意味になる。他にもinclude
(含んでいれば)、valid
(バリデーションされれば)など複数のマッチャが存在する。これらに関しては利用する時に再度説明する。
試しにバリデーションのテストを書く
では、実際にテストコードを書いていく。今回はuser_spec.rbを作成し、ユーザーの新規作成時に設定されているバリデーションが正常に機能するかどうかを調べるテストコードを書く。
Pictweetのディレクトリに、spec/modelsディレクトリを作成しましょう
ディレクトリはテキストエディタから作成することができる。左部メニューの「spec」にポインタを合わせ、左上部にあるアイコンをクリックし、新しいディレクトリを作成する。
同様に、テキストエディタを利用してspec/models/user_spec.rbを作成する。
Pictweetのディレクトリに、spec/models/user_spec.rbを作成する
先ほどと同じように、テキストエディタから作成することができる。
user_spec.rbを以下のように編集する
先に基本となるコードを記述。
1 2 3 4 5 6 7 |
require 'rails_helper'
describe User do
describe '#create' do
it "is invalid without a nickname" do
end
end
end
|
it ~ do
の間はそのexampleの説明を書くが、ここは日本語でも問題ない。今回は、英語で書いてみた。
では、この状態でテストを実行。テストを実行するためには、ターミナルでbundle exec rspec
というコマンドを実行する。
現状、user_spec.rbには中身のないテストコードが書かれている状態。
bundle exec rspecコマンド
RSpecのテストコードを利用したテストを実行するためのコマンド。
1 |
$ bundle exec rspec
|
ターミナルに以下のように表示されれば、テストを正常に実行できている。
1 2 3 4 5 6 |
User
#create
is invalid without a nickname
Finished in 0.1379 seconds (files took 3.28 seconds to load)
1 example, 0 failures
|
現在はまだテストとして評価される式を書いていないので、テストは無条件でパスする。
1行目のrequire 'rails_helper'
は、rails_helper.rb内の記述を読み込むことで共通の設定を有効にしている。この1行目の記述は、全てのspecファイルに書き込む。
2,3行目に連続してdescribe
が登場している。describe
は、このようにネスト(入れ子状)にすることができる。ここでは「Userクラスにあるcreateメソッドをテストするまとまり」であることを示している。このように、describe
とdo
の間にメソッド名を書く際は#
をつけるのが慣習である。
nicknameが空の場合登録できないことを確かめるテストコードを書く
ではここから「nicknameが空の場合登録できないことを確かめる」exampleを書いていく。先ほどと同様、先にコードを示し解説する形で進める。
user_spec.rbを以下のように編集
1 2 3 4 5 6 7 8 9 10 |
上記のコードで追記した5,6,7行目の流れは以下です。
【5行目】テストしたいプロパティを持ったuserクラスのインスタンスを新規作成する
スペックファイルの中では、そのRailsプロジェクトで作成しているモデルクラスを利用することができる。今回は「nicknameが空である場合登録できないこと」を確かめるテストコードを作成したいのでnicknameの値を空にし、それ以外は適当な値をセットした状態でuserクラスのインスタンスを作成している。
【6行目】作成したインスタンスがバリデーションによって保存ができない状態かチェックする
続いて、新規作成したuserクラスのインスタンスがバリデーションに引っかかるかどうかを確かめるvalid?
メソッドを利用する。
valid?メソッド
valid?
メソッドを利用すると、ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができない状態であるか」を確かめることができる。
errorsメソッド
valid?
メソッドの返り値はtrue/falseだが、valid?
メソッドを利用したインスタンス対してerrors
メソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができる。
実際に確かめてみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#コンソールを立ち上げる
$ rails c
#nicknameの値が空であるuserクラスのインスタンスを作成する
>user = User.new(nickname: "", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000")
#valid?メソッドを利用する
>user.valid?
#errorsメソッドを利用する
>user.errors
=> #<ActiveModel::Errors:0x007ffa6ce07ef0
@base=
#<User:0x007ffa6d3430b8
#中略
@messages={:nickname=>["can't be blank"]}>
|
下部の@messages=~
の部分が、valid?
によって追加された、そのインスタンスが保存できない理由である。上記の場合、「nicknameカラムに値が入っていなければ保存できない」と言われている。
ここで記述されている"can’t be blank”
は、元々RailsのGemで用意されているエラーメッセージ。このエラーメッセージは、RailsのGem本体に記述されている。
【7行目】チェックした結果インスタンスが持つエラー文が期待したものであるか確かめる
7行目は、以下のようなコードになっている。
7 |
expect(user.errors[:nickname]).to include("can't be blank")
|
expectの引数に関して、user.errors
に対してハッシュのバリューの取り出し方でカラム名を指定すると、そのカラムが原因のエラー文が入った配列を取り出すことができる。こちらに対して、include
というマッチャを利用してエクスペクテーションを作っている。
includeマッチャ
includeマッチャは、引数にとった値がexpectの引数である配列に含まれているかをチェックすることができるマッチャである。
今回の場合、「nicknameが空の場合はcan't be blankというエラーが出るはずだ」ということがわかっているため、include("can't be blank")
のように書くことができる。実際にその通りになればこちらのエクスペクテーションはパスし、このコードは意図した動作をすると保証できる。
ターミナルから以下のようにテストを実行
1 |
$ bundle exec rspec
|
以下のような結果になれば成功。
1 2 3 4 5 6 7 8 |
$ bundle exec rspec
User
#create
is invalid without a nickname
Finished in 0.53132 seconds (files took 3.64 seconds to load)
1 example, 0 failures
|
メールアドレスが存在しなければ登録できないことを確かめるテストコードを書く
バリデーションをかけることで、メールアドレスが入力されていないと登録できない実装がしてある。テストコードを書くことで、メールアドレスが存在しなければ登録できないことを確かめる。
上記の問題を解くことができたら、もう一度bundle exec rspec
コマンドでテストがパスすることを確かめる。