hiyoko-programingの日記

プログラミングを勉強したてのひよっ子。   エンジニア目指して勉強中。

単体テストの基礎〜RSpecを利用した基礎的なテストコード〜

RSpecの準備をする

Pictweetのテストコードを書いていく。
まずは「rspec-rails」というGemをインストールし、RSpecを利用できるようにする。続いて、RSpecの設定を行う。その後、実際にRSpecのコードを書きながらRSpecの基礎文法を学ぶ。

Gemをインストール

RSpecを利用するためのGem「rspec-rails」をインストールする。

テキストエディタで、Pictweetのディレクトリを開く

テキストエディタを起動し「command + o」を実行。

https://tech-master.s3.amazonaws.com/uploads/curriculums//13bdbb8dd366de44ae0d81eea7e39eab.png
続いてGemfileを編集する。

PictweetのGemfileの下部を以下のように編集

`gem 'rspec-rails'という部分を追記。

また web_console というgemはtest環境で動かすと不具合が起きる可能性があるgemなのでdevelopment環境でのみ動くようにする。

group :development do ~ endの記述が無ければ下記のように作成し、既にある方はその記述の間にweb_consoleを移動させる。

 web_console は既に記述がある場合は、記述場所を移動するだけ。追記してしまうと重複してしまう可能性があるため注意。

Gemfile
 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
$cd ~/projects/pictweet
#pictweetのディレクトリに移動
$pwd
~/projects/pictweet
#pictweetのディレクトリに居ることを確認
$ bundle install
#bundle installを実行

これでGem「rspec-rails」をインストールすることができた。

RSpecの設定

続いてRSpecの基本設定をしていく。
まずはRSpec用の設定ファイルを作成する必要がある。

ターミナル
1
2
$ rails g rspec:install
#RSpec用設定ファイルの作成

下記のようにファイルが作成されれば成功。

ターミナル
1
2
3
4
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

上記の作業によって作成された2つのファイル「spec_helper.rb」と「rails_helper.rb」に関して簡単な説明をしておく。

 rails_helper.rb

RailsにおいてRSpecを利用する際に、共通の設定を書いておくファイル。各テスト用ファイルでこちらのファイルを読み込むことで、共通の設定や、メソッドを適用する。

 spec_helper.rb

rails_helper.rbと同じくRSpec用の共通の設定を書いておくファイルだが、こちらはRSpecRails無しで利用する際に利用する。

 .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という記述を削除

app/views/devise/registrations/new.html.erb
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, ~の記述を追加する。

user.rb
 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ファイル

specファイルの命名規則

specファイルは対応するクラス名_spec.rbという名前になる。今回はまず「user.rb」に関するspecファイルを作成するので、その場合の名前は「user_spec.rb」になる。

テストコードの基本

続いて、「1 + 1が2になることを確かめる」という簡単なテストコードを例として、以下に挙げる基本的なテストコードの文法について。

【例】1 + 1が2になることを確かめるテストコード

sample_spec.rb
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行目のitexampleと呼ばれる実際に動作するテストコードのまとまりを表す。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」にポインタを合わせ、左上部にあるアイコンをクリックし、新しいディレクトリを作成する。

https://tech-master.s3.amazonaws.com/uploads/curriculums//17e80873fb1398ba607b24024be78682.png

同様に、テキストエディタを利用してspec/models/user_spec.rbを作成する。

 Pictweetのディレクトリに、spec/models/user_spec.rbを作成する

先ほどと同じように、テキストエディタから作成することができる。

 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メソッドをテストするまとまり」であることを示している。このように、describedoの間にメソッド名を書く際は#をつけるのが慣習である。

nicknameが空の場合登録できないことを確かめるテストコードを書く

ではここから「nicknameが空の場合登録できないことを確かめる」exampleを書いていく。先ほどと同様、先にコードを示し解説する形で進める。

 user_spec.rbを以下のように編集

user_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
require 'rails_helper'
describe User do
  describe '#create' do
    it "is invalid without a nickname" do
     user = User.new(nickname: "", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000")
     user.valid?
     expect(user.errors[:nickname]).to include("can't be blank")
    end
  end
end

上記のコードで追記した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行目は、以下のようなコードになっている。

user_spec.rb
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コマンドでテストがパスすることを確かめる。