こんにちは!システムエンジニアのオオイシです。
RSpec(アールスペック)をご存知でしょうか?
RSpecは、RubyやRuby on Railsの代表的なテストツールのことで、クラスやメソッド単位でテストするために利用します。
RSpecを使ったテスト手法を覚えておくと、コマンド1つで何度でもテストを実行できるので、プログラムのバグを減らしたり、コードの品質を上げることができます!
そんなRSpecはなんだか難しそうだと感じた人のために、今回は、
- RSpecとは
- RSpecのセットアップ
- Ruby on Rails をテストしてみよう
といった、基本的な解説から、
- 特に重要な構文の使い方
などの応用的な使い方に関しても解説していきます。
今回はRspecの基礎を理解していただくためにわかりやすく解説します!
RSpecとは
RSpecとは、RubyやRuby on Railsで作ったクラスやメソッドをテストするためのドメイン特化言語 (DSL)を使ったフレームワークです。
つまりは、テスト専用のプログラム言語とも言えます。
「別の言語をまた覚えるはツライ・・・」
と、感じたかもかもしれませんが、ベースはRubyから作られているので、本記事を読んでいただければ、簡単に始められます!
RSpecの特徴
RSpecにはテストのサポート機能がいろいろ用意されています。
例えば、
- Ruby on Railsのテストサポート
- モックやスタブ(擬似的なクラス)によるテスト支援
- テストデータの作成支援
- カバレッジツールとの連携(ソースコードが実行された割合を確認するツール)
- テスト結果のHTML出力機能
など、RSpecのコマンド一つでいろいろな機能を実行することが可能です。
このようにRSpecは豊富な機能を備えていますが、テストの実施方法の原則に変わりはありません。
次の項では、テストの進め方の基本について解説いたします。
テストの進め方の基本
クラスやメソッド単位のテストのことをユニットテストと言ったりしますがご存知でしょうか?
RSpecに限らずユニットテストの進め方には原則というものがあります。
- プログラムを書く前にテストコードを書く
- テスト実行して失敗することを確認する
- テストが成功するようにプログラムを書く
- 初めに戻る
を繰り返し行います。
このように、コーディングする前にテストコードを書くことを、テストファーストと言います。
開発手法でいえばTDD(テスト駆動開発)とか、BDD(振る舞い駆動開発)に分類される開発方法です。
ここでは「テストコードとプログラムコードを同時に書く」ことを意識いただければ問題ありません。
では、次章からRspecの使い方を説明していきます!
RSpecのセットアップ
はじめに、Ruby on RailsのBundlerを使ってRSpecをインストールするところから始めます。
インストールが終わったら、Rspecの実行方法と基本的な構文を説明していきます。
なお、Ruby on Rails のインストール方法については、こちらで詳しく説明していますので、ぜひ参考にしてください。
Gemfileに追加
Ruby on Rails にRSpecをインストールするためにはBundlerを使います。
Gemfileを開いて次のように追加してください。
group :development, :test do # 〜 省略 〜 gem 'rspec-rails' end
bundleコマンドを実行してインストールします。
bundle install
続いて、Ruby on RailsでRspecを利用できるように初期設定をします。
RSpecの初期設定
Ruby on RailsでRspecを使うためは、次のコマンドを実行して初期設定する必要があります。
bin/rails generate rspec:install
実行結果:
Running via Spring preloader in process 17316 create .rspec # 設定ファイル create spec # スペックファイルを格納するディレクトリ create spec/spec_helper.rb # RSpecのヘルパ(メソッド群) create spec/rails_helper.rb # Rails用のRSpecのヘルパ(メソッド群)
これで、RSpecを使うための準備ができました。
スペックの配置ルールと命名規則
RSpecでは、テストコードのことをスペック(仕様)と言います。
テストコードを書くことは、仕様(プログラムの振る舞い)を書くことと同じと考えると理解しやすいかもしれません。
そして、このスペックを実行するためにはファイルの配置ルールと命名規則に従わなければいけません。
Ruby on Railsの場合は、次のように配置します。
spec/ models/ モデルファイル名_spec.rb controllers/ コントローラファイル名_spec.rb views/ erbファイル名_spec.rb
このように、Ruby on Railsのディレクトリ構成と同じ階層構成でspecディレクトリ配下に配置します。
RSpecのセットアップが完了したので、次はテスト方法について紹介していきます!
Ruby on Rails をテストしてみよう
テスト準備
まずはじめに、テスト対象とするサンプルのモデルを作成します。
今回は、姓(last_name)と名(first_name)を属性に持つUserクラスを作成し、姓名を返却する「full_nameメソッド」を持つ仕様にします。
次のgenareteコマンドを実行して、モデルを作成してください。
rails g model user first_name:string last_name:string
実行結果:
Running via Spring preloader in process 17421 invoke active_record create db/migrate/20180407090831_create_users.rb create app/models/user.rb invoke rspec create spec/models/user_spec.rb
実行結果を見ると、Userモデルとともにスペックが自動作成されたのがわかるでしょうか。
もしも既存のクラスがある場合は、スペックファイルを手動で作成しても問題ありません。
最後に「db:migrate」コマンドを実行すれば、テスト対象のサンプルモデルは作成完了です。
rails db:migrate
期待する振る舞いを文章で書く
それでは、Userモデルのスペックを書いていきましょう。
テストファーストの考え方は、
- 期待するプログラムの振る舞いをスペックに記述する
- プログラムを実装する
を繰り返すことを解説しましたね。
その流れに沿って、はじめにRSpecでテストコードを書いていきます。
はじめに登場するit メソッドは、テスト対象のメソッドがどのような振る舞いをするかを文章で書きます。
- it(“プログラムの振る舞いを文章で記述”)
it メソッドは何かの処理をする訳ではなく、プログラムの仕様を書くイメージで、分かりやすさのために記述します。
次の例ではfull_nameメソッドに対するitの例となります。
spec/models/user_spec.rb
RSpec.describe User, type: :model do it "姓、名を登録すると、姓名が取得できること" end
RSpecを実行するには、次のコマンドを実行します。
bundle exec rspec
実行結果:
* Pending: (Failures listed here are expected and do not affect your suite's status) 1) User 姓、名を登録すると、姓名が取得できること # Not yet implemented # ./spec/models/user_spec.rb:4 Finished in 0.00512 seconds (files took 1.71 seconds to load) 1 example, 0 failures, 1 pending
実行結果の最後を見ると“1 pending”(保留中 1件)となりました。
保留中の理由は、itメソッド内にテストコードを書いていないからです。
次項は、itメソッド内にテストコードの書き方について説明していきます!
期待する振る舞いをコードで書く
expectメソッドはテスト対象を検証するためのメソッドです。
- expect(実行結果).to eq 期待する結果
文章にすると「実行結果は期待結果と等しいこと」となります。
eqは「等しいこと」を意味する構文でmatcher(マッチャー)と呼ばれます。
ここでは、full_nameメソッドが姓名を返すことを検証する、テストコードを確認してみましょう。
it "姓、名を登録すると、姓名が取得できること" do user = User.new( last_name: "侍", first_name: "太郎" ) expect(user.full_name).to eq "侍 太郎" end
実行結果:
$ bundle exec rspec F Failures: 1) User 姓、名を登録すると、姓名が取得できること Failure/Error: expect(user.full_name).to eq "侍 太郎" NoMethodError: undefined method `full_name' for #<User:0x00007fe445edbc10> # ./spec/models/user_spec.rb:9:in `block (2 levels) in <top (required)>' Finished in 0.00875 seconds (files took 1.68 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:4 # User 姓、名を登録すると、姓名が取得できること
残念ながら、”Failed examples“となってテストに失敗しました。
原因は「undefined method `full_name’」と表示されている通り、Userモデルにfull_nameメソッドが実装されていないことが原因です。
次の項では、この構文エラーを解消していきましょう!
テストに失敗する
構文エラーを解消するためにfull_nameメソッドを作成して再度実行してみます。
Userモデルに次のように実装しました。
class User < ApplicationRecord def full_name last_name + first_name end end
実行結果:
$ bin/bundle exec rspec F Failures: 1) User 姓、名を登録すると、姓名が取得できること Failure/Error: expect(user.full_name).to eq "侍 太郎" expected: "侍 太郎" got: "侍太郎" (compared using ==) # ./spec/models/user_spec.rb:9:in `block (2 levels) in <top (required)>' Finished in 0.02584 seconds (files took 1.78 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:4 # User 姓、名を登録すると、姓名が取得できること
Failed examplesと表示され、再びテストには失敗しました。
実行結果を見ると、
- expected: “侍 太郎” → 期待する結果
- got: “侍太郎” → 実際の値
となって、姓と名の間にスペースがないことが原因のようです。
次項で、このバグを解消してテストを成功させましょう!
テストに成功する
full_nameメソッドが、”侍 太郎”と結果を返すようにバグを修正して、再びRSpecを実行してみましょう。
class User < ApplicationRecord def full_name last_name + " " + first_name # 姓と名の間にスペースを追加 end end
実行結果:
bin/bundle exec rspec . Finished in 0.0122 seconds (files took 1.61 seconds to load) 1 example, 0 failures
このように、エラー表示がなくなりテストが成功したことが確認できました!
特に重要な構文の使い方
RSpecはとても簡単に始められることを解説しました。
しかし、RSpecはとても強力なテストツールでもあるので、たくさんある構文や機能のうちどの場面で何を使えばよいかがわからないという方も多くいらっしゃるようです。
そこで、今回は、特に重要なexpectとmatcherの2点について簡単に触れておきます。
expect
expectは検証を実行するためのメソッドです。
- expect(実行結果).to 期待する結果
- expect(実行結果).not_to 期待する結果
実行例は次のとおりです。
実行例 | 説明 |
---|---|
expect(1 + 2).to eq 3 | 1 + 2 の結果が 3と等しい場合はテスト成功 |
expect(1 + 2).not_to eq 4 | 1 + 2 の結果が 4と異なる場合はテスト成功 |
matcher
matcher(マッチャー)はテスト対象と期待する結果の比較条件を書きます。
- expect(実行結果).to eq 期待値
matcherは色々なバリエーションがあります。
matcher | 実行例 | 説明 |
---|---|---|
eq( 値 ) | expect(1 + 2).to eq 3 | 1+2 は 3 と等しいこと |
include( 値 ) | expect([1,2,3]).to include 2 | [1,2,3]の配列に2が含まれること |
be_truthy | expect(true).to be_truthy | 実行結果がtrueであること |
be_false | expect(false).to be_false | 実行結果がfalseであること |
match(正規表現) | expect("abc").to match /c$/ | “abc”は”c”で終わること |
be_valid | expect(user).to be_valid | RailsのUserモデルのvalidationに成功すること |
redirect_to(url) | expect(response).to redirect_to(“http://example.com”) | Railsのコントローラは“http://example.com”にリダイレクトすること |
render_template(url) | expect(response).to render_template(“new”) | Railsのコントローラはnewレンプレートをレンダリングすること |
まとめ
いかかでしたか?
今回は、RSpecの使い方を中心に基本的な構文について解説しました。
RSpecを使うとテストを何度も自動実行することができます。
また、プログラムのバグを減らしたり、品質を上げたりするのに役立つため、活用してみてください。
そして、RSpecの基本的な使い方を忘れてしまったらこの記事を確認してくださいね!