【保存版】RSpecチュートリアル:実例で学ぶRubyテストの書き方
RubyアプリケーションにRSpecを使ってテストを書く方法を学びたいと思っていませんか?
それならここがまさにうってつけの場所です!
このチュートリアルでは、RSpecによるテストの書き方をステップバイステップで解説していきます。
目次
- なぜテストを書くべきなのか?
- RSpecを始めよう
- RSpecテストの実例
- letメソッドの使い方
- subjectメソッドの使い方
- テスト実行前にコードを実行する方法
- テストのサブグループを作る方法
- テストを一時的に無効化する方法
- 名前を指定してテストを実行する方法
- RSpecの期待値とマッチャー
- RSpecのフォーマッター
- 遅いテストを見つける方法
- まとめ
なぜテストを書くべきなのか?
その理由をひとつずつ見ていきましょう。
エラーに対するセーフティネットになる(リファクタリング時に特に有効)
テストスイートがなければ、「何か壊してしまうかもしれない」という不安から、既存のコードに手を加えたくなくなってしまいます。
しかし、テストがあれば自信を持ってコードを変更できます!
コードのドキュメントとして機能する
テストは「アプリケーションが何をすべきか」を記述するものです。つまり、テスト自体が生きたドキュメントになります。
フィードバックループを得られる
TDD(テスト駆動開発)を行うと、次に何に集中すべきかを教えてくれるフィードバックループが得られます。集中力が続きにくい人にとっては特に心強い仕組みです。
コードが期待通りの結果を出していることを確認できる
これは非常に重要なポイントです。
複雑なロジックを書く場合、思いついた1つの例だけでなく、さまざまな入力に対して正しく動作することを確認したいはずです。
テストがあれば、エッジケース(境界値)を発見し、それを記録として残すことにも役立ちます。
Rubyエンジニアの就職・転職に有利になる
多くの求人ではテストスキルが高く評価されます。テストを書けることは、採用される可能性を高める大きな武器になります。
RSpecを始めよう
RSpecの仕組みを理解するために、具体例を一歩ずつ見ていきましょう。
ここでは、階乗を計算するシンプルなアプリケーションを作成します。
最初のステップはこちらです:
require 'rspec/autorun'
describe Factorial do
# ...
end
これが最初のRSpecテストを書くための雛形となるコードです。
まずrspecのgemをrequireする必要があります。
次に、describeブロックを作成します。これはすべてのテストをグループ化するとともに、RSpecに「どのクラスをテストしているのか」を伝える役割を持ちます。
続いて登場するのがitブロックです:
describe Factorial do
it "does something" do
# ...
end
end
itブロックはテスト名を表すと同時に、テスト本体の各コンポーネントをまとめる役割も果たします。
テストの構成要素は以下の3つです:
- セットアップ(準備)
- 実行(Exercise)
- 検証(Verify)
セットアップでは、必要なオブジェクトを作成します。いわば準備フェーズです。
次に、テスト対象のメソッドを呼び出して戻り値を取得します。
最後に、期待値(expectation)を使って結果を検証します(Minitestではアサーションと呼ばれます)。
RSpecテストの実例
階乗を計算するメソッドを書きたい場合、まず有効な値をオンラインや手計算で調べます。
そして、その値をテストに使用します。
こんな感じです:
describe Factorial do
it "finds the factorial of 5" do
calculator = Factorial.new
expect(calculator.factorial_of(5)).to eq(120)
end
end
このコードを(通常のRubyプログラムのように)実行すると、次のような結果になります:
uninitialized constant Factorial (NameError)
これは正常な動作です。Factorialクラスがまだ存在しないからですね。
では、クラスを作成しましょう:
class Factorial
end
次に出てくるエラーはこちら:
undefined method 'factorial_of'
factorial_ofメソッドを作成して修正します:
class Factorial
def factorial_of
end
end
再びコードを実行すると:
wrong number of arguments (given 1, expected 0)
またエラーです!でも、安心してください。
これはむしろ良いことなんです 🙂
エラーはイライラすべきものではありません。
エラーとはフィードバックなのです。
さて、続けましょう。
factorial_ofメソッドに引数を1つ追加します:
class Factorial
def factorial_of(n)
end
end
今度はテスト失敗(test failure)という結果が得られます:
expected: 120
got: nil
この段階では、まさにこの状態であることが理想です!
次のタスクはメソッドの中身を実装することです:
class Factorial
def factorial_of(n)
(1..n).inject(:*)
end
end
すると、ついに初めてのパスするテストが完成します:
.
Finished in 0.00315 seconds (files took 0.09083 seconds to load)
1 example, 0 failures
これこそが、テスト駆動開発(TDD)と呼ばれる手法です。
まずテストを書き、そのテストに導かれながら「次に何をすべきか」を決めていくのです。
letメソッドの使い方
多数のテストを書いていて、同じオブジェクトを再利用したい場合は、letステートメントでオブジェクトを定義できます。
次のような形です:
describe Factorial do
let(:calculator) { Factorial.new }
it "finds the factorial of 5" do
expect(calculator.factorial_of(5)).to eq(120)
end
end
こうすることで、同じdescribeブロック内のすべてのテストでcalculatorを再利用できます。
letについて知っておくべき重要な特徴があります。それは「遅延評価(lazy)」だということです。
どういう意味でしょうか?
オブジェクトは、最初に使用される瞬間まで生成されないのです。
オブジェクトの生成に副作用がある場合(データベースへのレコード作成やファイルへの書き込みなど)、この挙動は重要な違いを生みます。
本来なら副作用は避けるのがベストですが、どうしても避けられない場合はlet!メソッドを使用しましょう。
例:
let!(:user) { User.create("rspec@rubyguides.com") }
let!メソッドは遅延評価ではないため、オブジェクトはテストが実行される前に必ず生成されます。
subjectメソッドの使い方
letの別バージョンとしてsubjectがあります。
唯一の違いは、subjectは1つしか定義できない点と、テスト対象となるメインオブジェクトのインスタンスを表すために使われるという点です。
実は、RSpecはデフォルトで次のようなsubjectを自動的に生成しています:
subject { Factorial.new }
これは「暗黙的subject(implicit subject)」と呼ばれます。
次のように使えます:
describe Factorial do
it "finds the factorial of 5" do
expect(subject.factorial_of(5)).to eq(120)
end
end
subjectに名前を付けることも可能です:
subject(:calculator) { Factorial.new }
これはletを使う場合と同じように動作しますが、さらに1行で書ける期待式(one-line expectation)が使えるようになります:
it { should be_empty }
テスト実行前にコードを実行する方法
RSpecにはフック(hook)機能があり、すべてのテストの前後、あるいはテストグループ全体の前後に任意のコードを実行できます。
例えば:
describe Shop do
before(:all) { Shop.prepare_database }
after(:all) { Shop.cleanup_database }
end
各テスト(example = RSpecにおけるテストの単位)ごとにコードを実行したい場合は、:allの代わりに:eachを使用します。
テストのサブグループを作る方法
アプリケーションのさまざまなシナリオをテストする場合、関連するテストをグループ化しておくと便利です。
RSpecではcontextブロックを使ってこれを実現できます。
以下がその例です:
describe Course do
context "when user is logged in" do
it "displays the course lessons" do
end
it "displays the course description" do
end
end
context "when user is NOT logged in" do
it "redirects to login page" do
end
it "shows a message" do
end
end
end
このようにcontextを使うことで、「ログイン済みの場合」「未ログインの場合」といった条件ごとにテストを整理でき、可読性が大きく向上します。
テストを一時的に無効化する方法
デバッグ目的で、特定のテストを一時的に無効化したいことがあります。
その方法はとても簡単で、無効化したいテストのitをxitに変更するだけです。
例:
xit "eats lots of bacon" do
end
作業が終わったら、忘れずにxを削除しましょう!
名前を指定してテストを実行する方法
テストを無効化する代わりに、-eフラグを使って実行したいテストだけを絞り込むこともできます。
例:
> ruby person.rb -e bacon
このフィルタリングはテスト名に基づいているため、上記の例では「bacon」という単語を含むすべてのテストが実行されます。
RSpecの期待値とマッチャー
このチュートリアルで使ってきた次の例を覚えていますか?
expect(calculator.factorial_of(5)).to eq(120)
ところで、このeq(120)の部分は何なのでしょうか?
120は私たちが期待する値です。
そしてeqは、いわゆる「マッチャー(matcher)」と呼ばれるものです。
マッチャーとは、RSpecがメソッドの出力と期待値を比較するための仕組みです。
eqの場合、RSpecは==演算子を使用します(Rubyの演算子について詳しく知りたい方は、関連記事をご覧ください)。
しかし、他にもさまざまなマッチャーが用意されています。
例えば、be_○○形式のマッチャーです:
expect(nil).to be_nil
ここでの○○の部分には、述語メソッド(empty?など)が入り、テスト結果のオブジェクトに対して呼び出されます。
その他の便利なマッチャー一覧:
- include(配列やハッシュに含まれるか)
- start_with(指定した文字列・要素で始まるか)
- end_with(指定した文字列・要素で終わるか)
- match(正規表現とのマッチング)
- be_between(範囲内に収まっているか)
- have_key / have_value(ハッシュのキー・値の存在チェック)
- be_instance_of(指定クラスのインスタンスか)
- respond_to(指定メソッドに応答できるか)
- have_attributes(インスタンス変数のテスト)
中でも特に注意が必要なのがraise_errorマッチャーです。
このマッチャーを使う際は、期待式をブロックで囲む必要があるからです。
こんな感じです:
expect{ :x.count }.to raise_error(NoMethodError)
changeマッチャーも同様にブロック形式で使います:
expect{ stock.increment }.to change(stock, :value).by(100)
RSpecのフォーマッター
RSpecのデフォルト出力は「progress」形式です。
この形式では、パスしたテスト1件につきドット(.)が1つ表示され、失敗したテスト(期待値と実際の値が一致しない場合)はF、エラーが発生した場合はEが表示されます。
しかし、他にも選択できるフォーマットがあります。
以下がその一覧です:
- progress(デフォルト)
- documentation
- json
- html
-fフラグで切り替えられます:
> ruby factorial.rb -f d
Person
eats lots of healthy food
writes many articles
Finished in 0.00154 seconds (files took 0.09898 seconds to load)
2 examples, 0 failures
documentation形式では、テストの説明文(description)が出力にそのまま使われます。
遅いテストを見つける方法
RSpecには、テストの実行時間を計測(プロファイリング)できる非常に便利なオプションが標準で備わっています。
--profileフラグを渡すだけで、各テストの実行時間を確認でき、特に遅いテストを特定して改善できます。
実行例:
> ruby factorial.rb --profile
Factorial finds the factorial of 5
0.00043 seconds
まとめ
この記事では、RSpecテストフレームワークを使ったテストの書き方を学びました。
describe、it、expectといった基本構造から、letやsubjectによるオブジェクトの再利用、contextによるグループ化、便利なマッチャーやフラグまで、実践的な内容を網羅しました。
さあ、今度はあなた自身の手で、最初のテストを書いてみましょう!
-
Bashシェル関数入門:実用的な6つのサンプル例で学ぶチュートリアル
Bashシェル関数とは、複数のUNIX/Linuxコマンドをひとつの名前でグループ化し、後からまとめて実行できるようにする仕組みです。Bashシェル関数は通常のUnixコマンドと同じように呼び出すことができ、現在のシェルコンテキスト内で実行されるため、解釈用の新しいプロセスを生成する必要がありません。bashのエイリアス(alias)と関数はどちらも、長いコマンドや複雑なコマンドに対するショートカットを定義するのに役立ちます。しかし、エイリアスでは制御フローや引数の扱いなど、関数ならではの高度な処理を行うことはできません。本記事では、その違いも含めて詳しく解説していきます。この記事は、進行中の
-
実用的な5つの例で学ぶBashスクリプト入門チュートリアル
Unix SedやUnix Awkシリーズと同様に、本シリーズではBashスクリプティングに関する記事を複数回にわたって公開します。実用的なサンプルを交えながら、Bashスクリプティングのテクニックを幅広く解説していきます。 シェルとは、ユーザーが入力したコマンドを解釈して実行するプログラムです。コマンドはユーザーが直接入力するか、「シェルスクリプト」と呼ばれるファイルから読み込まれます。 ユーザーからの入力を直接読み取る場合、そのシェルは対話型(インタラクティブ)シェルと呼ばれます。 一方、ファイルからコマンドを読み込んで実行する場合は非対話型(ノンインタラクティブ)シェルと呼ばれます。この