Rspec-quick-guide

提供:Dev Guides
移動先:案内検索

RSpec-はじめに

RSpecは、Rubyプログラミング言語の単体テストフレームワークです。 RSpecはビヘイビア駆動型の開発ツールであるため、RSpecはJUnitのような従来のxUnitフレームワークとは異なります。 つまり、RSpecで記述されたテストは、テスト対象のアプリケーションの「動作」に焦点を当てています。 RSpecは、アプリケーションの動作方法ではなく、その動作、つまりアプリケーションの実際の動作に重点を置きません。

RSpec環境

まず、コンピューターにRubyをインストールする必要があります。 ただし、まだ以前に行っていない場合は、RubyのメインWebサイト(https://www.ruby-lang.org/en/documentation/installation[Ruby])からRubyをダウンロードしてインストールできます。

WindowsにRubyをインストールする場合は、WindowsのRubyインストーラーがhttp://rubyinstaller.org/[http://www.rubyinstaller.org]にあります。

このチュートリアルでは、メモ帳やコマンドラインコンソールなどのテキストエディターのみが必要です。 この例では、Windowsでcmd.exeを使用します。

cmd.exeを実行するには、[スタート]メニューをクリックして「cmd.exe」と入力し、Returnキーを押します。

cmd.exeウィンドウのコマンドプロンプトで、次のコマンドを入力して、使用しているRubyのバージョンを確認します-

ruby -v

これに似た以下の出力が表示されるはずです-

ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32]

このチュートリアルの例では、Ruby 2.2.3を使用しますが、2.0.0以降のRubyのバージョンであれば十分です。 次に、Rubyインストール用にRSpec gemをインストールする必要があります。 gemは、独自のコードで使用できるRubyライブラリです。 gemをインストールするには、 gem コマンドを使用する必要があります。

ここで、Rspec gemをインストールしましょう。 cmd.exeウィンドウに戻り、次を入力します-

gem install rspec

インストールされた依存gemのリストが必要です。これらは、rspec gemが正しく機能するために必要なgemです。 出力の最後に、次のようなものが表示されるはずです-

Done installing documentation for diff-lcs, rspec-support, rspec-mocks,
   rspec-expectations, rspec-core, rspec after 22 seconds
6 gems installed

出力がまったく同じに見えなくても心配しないでください。 また、MacまたはLinuxコンピューターを使用している場合は、 sudo を使用して gem install rspec コマンドを実行するか、HomeBrewやRVMなどのツールを使用してrspec gemをインストールする必要があります。

Hello World

はじめに、RSpecファイルを保存するディレクトリ(フォルダー)を作成しましょう。 あなたのcmd.exeウィンドウで、次を入力します-

cd \

次に、タイプ-

mkdir rspec_tutorial

そして最後に、タイプ-

cd rspec_tutorial

ここから、specという名前の別のディレクトリを作成し、次のように入力します-

mkdir spec

RSpecファイルをこのフォルダーに保存します。 RSpecファイルは「スペック」と呼ばれます。 これがわかりにくい場合は、スペックファイルをテストファイルと考えることができます。 RSpecでは、「仕様」の短縮形である「仕様」という用語を使用します。

RSpecはBDDテストツールであるため、目標はアプリケーションの動作と、仕様に準拠しているかどうかに焦点を当てることです。 行動駆動開発では、仕様は「ユーザーストーリー」の観点から説明されることがよくあります。 RSpecは、ターゲットコードが正しく動作しているかどうか、つまり仕様に従っているかどうかを明確にするように設計されています。

Hello Worldコードに戻りましょう。 テキストエディタを開き、次のコードを追加します-

class HelloWorld

   def say_hello
      "Hello World!"
   end

end

describe HelloWorld do
   context “When testing the HelloWorld class” do

      it "should say 'Hello World' when we call the say_hello method" do
         hw = HelloWorld.new
         message = hw.say_hello
         expect(message).to eq "Hello World!"
      end

   end
end

次に、これを上記で作成したspecフォルダーのhello_world_spec.rbという名前のファイルに保存します。 さて、cmd.exeウィンドウに戻って、このコマンドを実行します-

rspec spec spec\hello_world_spec.rb

コマンドが完了すると、次のような出力が表示されます-

Finished in 0.002 seconds (files took 0.11101 seconds to load)
1 example, 0 failures

おめでとう、最初のRSpec単体テストを作成して実行しただけです!

次のセクションでは、引き続きRSpecファイルの構文について説明します。

RSpec-基本的な構文

*HelloWorld* の例のコードを詳しく見てみましょう。 まず、明確でない場合、 *HelloWorld* クラスの機能をテストしています。 これはもちろん、* say_hello()*メソッドを1つだけ含む非常に単純なクラスです。

ここに再びRSpecコードがあります-

describe HelloWorld do
   context “When testing the HelloWorld class” do

      it "The say_hello method should return 'Hello World'" do
         hw = HelloWorld.new
         message = hw.say_hello
         expect(message).to eq "Hello World!"
      end

   end
end

describeキーワード

単語 describe はRSpecキーワードです。 「サンプルグループ」の定義に使用されます。 「サンプルグループ」はテストのコレクションと考えることができます。 describe キーワードは、クラス名および/または文字列引数を取ることができます。 また、ブロック引数を describe に渡す必要があります。これには、個々のテスト、またはRSpecで知られている「例」が含まれます。 ブロックは、Rubyの do/end キーワードで指定された単なるRubyブロックです。

コンテキストキーワード

*context* キーワードは *describe* に似ています。 クラス名や文字列引数を受け入れることもできます。 ブロックも *context* で使用する必要があります。 コンテキストの考え方は、特定のタイプのテストを囲むことです。

たとえば、このような異なるコンテキストを持つ例のグループを指定することができます-

context “When passing bad parameters to the foobar() method”
context “When passing valid parameters to the foobar() method”
context “When testing corner cases with the foobar() method”
*context* キーワードは必須ではありませんが、含まれている例に関する詳細を追加するのに役立ちます。

itキーワード

*it* という単語は、「例」を定義するために使用される別のRSpecキーワードです。 例は、基本的にテストまたはテストケースです。 繰り返しますが、 *describe* や* contextのように、クラス名と文字列引数の両方を受け入れ、 *do/end* で指定されたブロック引数と共に使用する必要があります。 *it* の場合、文字列とブロック引数のみを渡すのが習慣です。 文字列引数は、多くの場合「should」という単語を使用し、* itブロック*内でどのような特定の動作が発生するかを説明するためのものです。 言い換えれば、予想される結果は例の結果であることを説明しています。

HelloWorldの例の* itブロック*に注意してください-

it "The say_hello method should return 'Hello World'" do

この文字列により、HelloWorldクラスのインスタンスでsay helloを呼び出すとどうなるかが明確になります。 RSpec哲学のこの部分、例は単なるテストではなく、仕様(仕様)でもあります。 言い換えれば、例では、Rubyコードの予想される動作を文書化してテストします。

expectキーワード

*expect* キーワードは、RSpecで「期待」を定義するために使用されます。 これは、特定の予想される条件が満たされていることを確認する検証手順です。

HelloWorldの例から、次のようになります-

expect(message).to eql "Hello World!"
*expect* ステートメントの考え方は、通常の英語のように読むことです。 これは「変数メッセージが文字列「Hello World」に等しいことを期待してください」と声を出して言うことができます。 プロジェクトマネージャーなどの非技術的な利害関係者にとっても、説明的で読みやすいという考え方です。
The to keyword
*to* キーワードは、 *expect* ステートメントの一部として使用されます。 Expectationをfalseにしたい場合は、 *not_to* キーワードを使用して反対を表すこともできます。 toはドット* expect(message).to、*とともに使用されていることがわかります。これは実際には通常のRubyメソッドであるためです。 実際、すべてのRSpecキーワードは実際には単なるRubyメソッドです。
The eql keyword
*eql* キーワードは、Matcherと呼ばれる特別なRSpecキーワードです。 マッチャーを使用して、テストする条件のタイプをtrue(またはfalse)に指定します。

HelloWorldの expect ステートメントでは、 eql が文字列の等価性を意味することは明らかです。 Rubyにはさまざまなタイプの等価演算子があり、その結果、RSpecには対応するMatcherが異なることに注意してください。 後のセクションで、さまざまな種類のマッチャーについて説明します。

RSpec-仕様の作成

この章では、新しいRubyクラスを作成し、独自のファイルに保存し、このクラスをテストするための個別の仕様ファイルを作成します。

まず、新しいクラスでは、 StringAnalyzer と呼ばれます。 単純なクラスで、ご想像のとおり、文字列を分析します。 このクラスには、* has_vowels?というメソッドが1つしかありません。このメソッドは、その名前が示すとおり、文字列に母音が含まれる場合はtrueを、含まれない場合はfalseを返します。 *StringAnalyzer の実装は次のとおりです-

class StringAnalyzer
   def has_vowels?(str)
      !!(str =~/[aeio]+/i)
   end
end

HelloWorldセクションに従っている場合、C:\ rspec_tutorial \ specというフォルダーを作成しました。

hello_world.rbファイルがある場合は削除し、上記のStringAnalyzerコードをC:\ rspec_tutorial \ specフォルダーのstring_analyzer.rbというファイルに保存します。

StringAnalyzerをテストするためのspecファイルのソースは次のとおりです-

require 'string_analyzer'

describe StringAnalyzer do
   context "With valid input" do

      it "should detect when a string contains vowels" do
         sa = StringAnalyzer.new
         test_string = 'uuu'
         expect(sa.has_vowels? test_string).to be true
      end

      it "should detect when a string doesn't contain vowels" do
         sa = StringAnalyzer.new
         test_string = 'bcdfg'
         expect(sa.has_vowels? test_string).to be false
      end

   end
end

これを同じspecディレクトリに保存し、string_analyzer_test.rbという名前を付けます。

cmd.exeウィンドウで、C:\ rspec_tutorialフォルダーに移動し、次のコマンドを実行します:dir spec

次が表示されるはずです-

C:\ rspec_tutorial \ specのディレクトリ

09/13/2015 08:22 AM  <DIR>    .
09/13/2015 08:22 AM  <DIR>    ..
09/12/2015 11:44 PM                 81 string_analyzer.rb
09/12/2015 11:46 PM              451 string_analyzer_test.rb

次に、テストを実行します。次のコマンドを実行します:rspec spec

フォルダーの名前を rspec に渡すと、フォルダー内のすべてのspecファイルが実行されます。 この結果が表示されるはずです-

No examples found.

Finished in 0 seconds (files took 0.068 seconds to load)
0 examples, 0 failures

これが発生した理由は、デフォルトでは、 rspec は名前が「_spec.rb」で終わるファイルのみを実行するためです。 string_analyzer_test.rbの名前をstring_analyzer_spec.rbに変更します。 あなたはこのコマンドを実行することで簡単にそれを行うことができます-

ren spec\string_analyzer_test.rb string_analyzer_spec.rb

さて、もう一度 rspec specを実行すると、次のような出力が表示されるはずです-

F.
Failures:

   1) StringAnalyzer With valid input should detect when a string contains vowels
      Failure/Error: expect(sa.has_vowels? test_string).to be true
         expected true
            got false
      # ./spec/string_analyzer_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.015 seconds (files took 0.12201 seconds to load)
2 examples, 1 failure

Failed examples:
rspec ./spec/string_analyzer_spec.rb:6 # StringAnalyzer With valid
   input should detect when a string contains vowels
Do you see what just happened? Our spec failed because we have a bug in
   StringAnalyzer. The bug is simple to fix, open up string_analyzer.rb
   in a text editor and change this line:
!!(str =~/[aeio]+/i)
to this:
!!(str =~/[aeiou]+/i)

さて、string_analyizer.rbで行った変更を保存し、rspec specコマンドを再度実行すると、次のような出力が表示されるはずです-

..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures

おめでとうございます、specファイルの例(テスト)は合格です。 母音メソッドを持つ正規表現のバグを修正しましたが、テストは完全ではありません。

has vowelsメソッドを使用してさまざまなタイプの入力文字列をテストする例をさらに追加することは理にかなっています。

次の表は、新しい例で追加できる置換の一部を示しています(ブロックします)。

Input string Description Expected result with has_vowels?
‘aaa’, ‘eee’, ‘iii’, ‘o’ Only one vowel and no other letters. true
‘abcefg’ ‘At least one vowel and some consonants’ true
‘mnklp’ Only consonants. false
‘’ Empty string (no letters) false
‘abcde55345&??’ Vowels, consonants, numbers and punctuation characters. true
‘423432%%%^&’ Numbers and punctuation characters only. false
‘AEIOU’ Upper case vowels only. true
‘AeiOuuuA’ Upper case and lower vowels only. true
‘AbCdEfghI’ Upper and lower case vowels and consonants. true
‘BCDFG’ Upper case consonants only. false
‘ ‘ Whitespace characters only. false

スペックファイルに追加するサンプルを決定するのはユーザー次第です。 テストする条件は多数ありますが、最も重要な条件のサブセットを特定し、コードを最適にテストする必要があります。

*rspec* コマンドにはさまざまなオプションがあり、それらをすべて表示するには、 *rspec* -helpと入力します。 次の表に、最も一般的なオプションとその機能の説明を示します。
Sr.No. Option/flag & Description
1

-I PATH

Rubyのソースファイルを探すときに rspec が使用するロード(必須)パスにPATHを追加します。

2

-r, --require PATH

仕様に必要な特定のソースファイルを追加します。 ファイル。

3

--fail-fast

このオプションを使用すると、rspecは最初の例が失敗した後に仕様の実行を停止します。 デフォルトでは、rspecは、失敗の数に関係なく、指定されたすべてのspecファイルを実行します。

4

-f, --format FORMATTER

このオプションを使用すると、さまざまな出力形式を指定できます。 出力形式の詳細については、フォーマッターのセクションを参照してください。

5

-o, --out FILE

このオプションは、テスト結果を標準出力ではなく出力ファイルFILEに書き込むようにrspecに指示します。

6

-c, --color

rspecの出力で色を有効にします。 成功例の結果は緑色のテキストで表示され、失敗は赤色のテキストで印刷されます。

7

-b, --backtrace

rspecの出力に完全なエラーバックトレースを表示します。

8

-w, --warnings

rspecの出力にRubyの警告を表示します。

9

-P, --pattern PATTERN

パターンPATTERNに一致する仕様ファイルをロードして実行します。 たとえば、-p“ *.rb”を渡すと、rspecは“ _spec.rb”で終わるファイルだけでなく、すべてのRubyファイルを実行します。

10
  • -e, --example STRING*

このオプションは、説明にテキストSTRINGを含むすべての例を実行するようにrspecに指示します。

11

-t, --tag TAG

このオプションを使用すると、rspecはタグTAGを含むサンプルのみを実行します。 TAGはRubyシンボルとして指定されていることに注意してください。 詳細については、RSpecタグのセクションを参照してください。

RSpec-マッチャー

元のHello Worldの例を思い出すと、次のような行が含まれていました-

expect(message).to eq "Hello World!"

キーワードeqlは RSpec 「マッチャー」です。 ここでは、RSpecの他のタイプのマッチャーを紹介します。

平等/同一性マッチャー

オブジェクトまたは値の等価性をテストするマッチャー。

Matcher Description Example
eq Passes when actual == expected expect(actual).to eq expected
eql Passes when actual.eql?(expected) expect(actual).to eql expected
be Passes when actual.equal?(expected) expect(actual).to be expected
equal Also passes when actual.equal?(expected) expect(actual).to equal expected

describe "An example of the equality Matchers" do

   it "should show how the equality Matchers work" do
      a = "test string"
      b = a

      # The following Expectations will all pass
      expect(a).to eq "test string"
      expect(a).to eql "test string"
      expect(a).to be b
      expect(a).to equal b
   end

end

上記のコードが実行されると、次の出力が生成されます。 秒数は、お使いのコンピューター上でわずかに異なる場合があります-

.
Finished in 0.036 seconds (files took 0.11901 seconds to load)
1 example, 0 failures

比較マッチャー

値と比較するためのマッチャー。

Matcher Description Example
> Passes when actual > expected expect(actual).to be > expected
>= Passes when actual >= expected expect(actual).to be >= expected
< Passes when actual < expected expect(actual).to be < expected
Passes when actual ⇐ expected expect(actual).to be ⇐ expected
be_between inclusive Passes when actual is ⇐ min and >= max expect(actual).to be_between(min, max).inclusive
be_between exclusive Passes when actual is < min and > max expect(actual).to be_between(min, max).exclusive
match Passes when actual matches a regular expression expect(actual).to match(/regex/)

describe "An example of the comparison Matchers" do

   it "should show how the comparison Matchers work" do
      a = 1
      b = 2
      c = 3
      d = 'test string'

      # The following Expectations will all pass
      expect(b).to be > a
      expect(a).to be >= a
      expect(a).to be < b
      expect(b).to be <= b
      expect(c).to be_between(1,3).inclusive
      expect(b).to be_between(1,3).exclusive
      expect(d).to match/TEST/i
   end

end

上記のコードが実行されると、次の出力が生成されます。 秒数は、お使いのコンピューター上でわずかに異なる場合があります-

.
Finished in 0.013 seconds (files took 0.11801 seconds to load)
1 example, 0 failures

クラス/タイプマッチャー

オブジェクトのタイプまたはクラスをテストするためのマッチャー。

Matcher Description Example
be_instance_of Passes when actual is an instance of the expected class. expect(actual).to be_instance_of(Expected)
be_kind_of Passes when actual is an instance of the expected class or any of its parent classes. expect(actual).to be_kind_of(Expected)
respond_to Passes when actual responds to the specified method. expect(actual).to respond_to(expected)

describe "An example of the type/class Matchers" do

   it "should show how the type/class Matchers work" do
      x = 1
      y = 3.14
      z = 'test string'

      # The following Expectations will all pass
      expect(x).to be_instance_of Fixnum
      expect(y).to be_kind_of Numeric
      expect(z).to respond_to(:length)
   end

end

上記のコードが実行されると、次の出力が生成されます。 秒数は、お使いのコンピューター上でわずかに異なる場合があります-

.
Finished in 0.002 seconds (files took 0.12201 seconds to load)
1 example, 0 failures

True/False/Nilマッチャー

値がtrue、false、またはnilかどうかをテストするためのマッチャー。

Matcher Description Example
be true Passes when actual == true expect(actual).to be true
be false Passes when actual == false expect(actual).to be false
be_truthy Passes when actual is not false or nil expect(actual).to be_truthy
be_falsey Passes when actual is false or nil expect(actual).to be_falsey
be_nil Passes when actual is nil expect(actual).to be_nil

describe "An example of the true/false/nil Matchers" do
   it "should show how the true/false/nil Matchers work" do
      x = true
      y = false
      z = nil
      a = "test string"

      # The following Expectations will all pass
      expect(x).to be true
      expect(y).to be false
      expect(a).to be_truthy
      expect(z).to be_falsey
      expect(z).to be_nil
   end
end

上記のコードが実行されると、次の出力が生成されます。 秒数は、お使いのコンピューター上でわずかに異なる場合があります-

.
Finished in 0.003 seconds (files took 0.12301 seconds to load)
1 example, 0 failures

エラーマッチャー

コードのブロックでエラーが発生した場合のテスト用のマッチャー。

Matcher Description Example
raise_error(ErrorClass) Passes when the block raises an error of type ErrorClass. expect {block}.to raise_error(ErrorClass)
raise_error("error message") Passes when the block raise an error with the message “error message”. expect {block}.to raise_error(“error message”)
raise_error(ErrorClass, "error message") Passes when the block raises an error of type ErrorClass with the message “error message” expect {block}.to raise_error(ErrorClass,“error message”)

次のコードを error_matcher_spec.rb という名前のファイルに保存し、このコマンドで実行します- rspec error_matcher_spec.rb

describe "An example of the error Matchers" do
   it "should show how the error Matchers work" do

      # The following Expectations will all pass
      expect { 1/0 }.to raise_error(ZeroDivisionError)
      expect { 1/0 }.to raise_error("divided by 0")
      expect { 1/0 }.to raise_error("divided by 0", ZeroDivisionError)
   end
end

上記のコードが実行されると、次の出力が生成されます。 秒数は、お使いのコンピューター上でわずかに異なる場合があります-

.
Finished in 0.002 seconds (files took 0.12101 seconds to load)
1 example, 0 failures

RSpec-テストダブル

この章では、RSpec Mocksとも呼ばれるRSpec Doublesについて説明します。 Doubleは、別のオブジェクトを「スタンドイン」できるオブジェクトです。 あなたはおそらく、それが正確に何を意味するのか、なぜそれが必要なのか疑問に思っているでしょう。

たとえば、学校用のアプリケーションを作成していて、生徒の教室を表すクラスと生徒用の別のクラス、つまり教室クラスと生徒クラスがあるとします。 最初にいずれかのクラスのコードを記述する必要があるため、クラスルームクラスから始めましょう。

class ClassRoom
   def initialize(students)
      @students = students
   end

   def list_student_names
      @students.map(&:name).join(',')
   end
end

これは単純なクラスであり、list_student_namesというメソッドが1つあり、学生名のカンマ区切り文字列を返します。 さて、このクラスのテストを作成したいのですが、Studentクラスをまだ作成していない場合はどうすればよいでしょうか? テストDoubleが必要です。

また、Studentオブジェクトのように動作する「ダミー」クラスがある場合、ClassRoomテストはStudentクラスに依存しません。 これをテスト分離と呼びます。

ClassRoomテストが他のクラスに依存しない場合、テストが失敗すると、ClassRoomクラスにバグがあり、他のクラスにはないことがすぐにわかります。 現実の世界では、他の誰かが書いた別のクラスと対話する必要があるクラスを構築している可能性があることに留意してください。

これは、RSpec Doubles(モック)が役立つ場所です。 list_student_namesメソッドは、@ studentsメンバー変数の各Studentオブジェクトでnameメソッドを呼び出します。 したがって、nameメソッドを実装するDoubleが必要です。

RSpecの例(テスト)とともにClassRoomのコードを示しますが、Studentクラスが定義されていないことに注意してください-

class ClassRoom
   def initialize(students)
      @students = students
   end

   def list_student_names
      @students.map(&:name).join(',')
   end
end

describe ClassRoom do
   it 'the list_student_names method should work correctly' do
      student1 = double('student')
      student2 = double('student')

      allow(student1).to receive(:name) { 'John Smith'}
      allow(student2).to receive(:name) { 'Jill Smith'}

      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith')
   end
end

上記のコードが実行されると、次の出力が生成されます。 経過時間は、コンピュータ上でわずかに異なる場合があります-

.
Finished in 0.01 seconds (files took 0.11201 seconds to load)
1 example, 0 failures

ご覧のとおり、 test double を使用すると、未定義または使用できないクラスに依存している場合でもコードをテストできます。 また、これは、テストに失敗した場合、クラスの問題であり、他の誰かが書いたクラスではないことがすぐにわかることを意味します。

RSpec-スタブ

RSpec Doubles(別名Mocks)に関するセクションをすでに読んでいれば、RSpecスタブはすでに見ています。 RSpecでは、スタブはよく「メソッドスタブ」と呼ばれます。これは、既存のメソッド、またはまだ存在しないメソッドを「代用する」特別なタイプのメソッドです。

ここにRSpec Doublesに関するセクションからのコードがあります-

class ClassRoom
   def initialize(students)
      @students = students
   End

   def list_student_names
      @students.map(&:name).join(',')
   end
end

describe ClassRoom do
   it 'the list_student_names method should work correctly' do
      student1 = double('student')
      student2 = double('student')

      allow(student1).to receive(:name) { 'John Smith'}
      allow(student2).to receive(:name) { 'Jill Smith'}

      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith')
   end
end

この例では、allow()メソッドはClassRoomクラスをテストするために必要なメソッドスタブを提供します。 この場合、Studentクラスのインスタンスのように動作するオブジェクトが必要ですが、そのクラスは実際には存在しません(まだ)。 Studentクラスはname()メソッドを提供する必要があることを知っており、allow()を使用してname()のメソッドスタブを作成します。

注意すべきことの1つは、RSpecの構文が長年にわたって少し変わったことです。 RSpecの古いバージョンでは、上記のメソッドスタブはこのように定義されます-

student1.stub(:name).and_return('John Smith')
student2.stub(:name).and_return('Jill Smith')

上記のコードを使用して、2つの* allow()*行を古いRSpec構文に置き換えてみましょう-

class ClassRoom
   def initialize(students)
      @students = students
   end

   def list_student_names
      @students.map(&:name).join(',')
   end

end

describe ClassRoom do
   it 'the list_student_names method should work correctly' do
      student1 = double('student')
      student2 = double('student')

      student1.stub(:name).and_return('John Smith')
      student2.stub(:name).and_return('Jill Smith')

      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith')
   end
end

上記のコードを実行すると、この出力が表示されます-

.
Deprecation Warnings:

Using `stub` from rspec-mocks' old `:should` syntax without explicitly
   enabling the syntax is deprec

ated. Use the new `:expect` syntax or explicitly enable `:should` instead.
   Called from C:/rspec_tuto

rial/spec/double_spec.rb:15:in `block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations
   to identify where to make the necessary changes, you can configure

`config.raise_errors_for_deprecations!`, and it will turn the
   deprecation warnings into errors, giving you the full backtrace.

1 deprecation warning total

Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

RSpecの例でメソッドスタブを作成する必要がある場合は、新しいallow()構文を使用することをお勧めしますが、ここでは古いスタイルを提供しているので、見たときにそれを認識できます。

RSpec-フック

単体テストを作成する場合、テストの前後にセットアップコードとティアダウンコードを実行すると便利なことがよくあります。 セットアップコードは、テストの条件を構成または「セットアップ」するコードです。 分解コードはクリーンアップを行い、後続のテストで環境が一貫した状態になるようにします。

一般的に、テストは互いに独立している必要があります。 テストのスイート全体を実行し、そのうちの1つが失敗した場合、テスト中のコードにバグがあり、前のテストが環境を一貫性のない状態にしたためではなく、失敗したことを確信する必要があります。

RSpecで使用される最も一般的なフックは、フックの前後です。 これらは、上記で説明したセットアップおよび分解コードを定義および実行する方法を提供します。 このサンプルコードを考えてみましょう-

class SimpleClass
   attr_accessor :message

   def initialize()
      puts "\nCreating a new instance of the SimpleClass class"
      @message = 'howdy'
   end

   def update_message(new_message)
      @message = new_message
   end
end

describe SimpleClass do
   before(:each) do
      @simple_class = SimpleClass.new
   end

   it 'should have an initial message' do
      expect(@simple_class).to_not be_nil
      @simple_class.message = 'Something else. . .'
   end

   it 'should be able to change its message' do
      @simple_class.update_message('a new message')
      expect(@simple_class.message).to_not be 'howdy'
   end
end

このコードを実行すると、次の出力が得られます-

Creating a new instance of the SimpleClass class
.
Creating a new instance of the SimpleClass class
.
Finished in 0.003 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures

何が起こっているかを詳しく見てみましょう。 before(:each)メソッドは、セットアップコードを定義する場所です。 :each引数を渡すと、サンプルメソッドグループの各サンプルの前にbeforeメソッドを実行するように指示しています。 上記のコードの記述ブロック内の2つのitブロック。

@simple_class = SimpleClass.newという行では、SimpleClassクラスの新しいインスタンスを作成し、それをオブジェクトのインスタンス変数に割り当てています。 あなたが疑問に思うかもしれないオブジェクトは何ですか? RSpecは、describeブロックのスコープ内でバックグラウンドで特別なクラスを作成します。 これにより、このクラスのインスタンス変数に値を割り当てることができ、例のitブロック内でアクセスできます。 これにより、テストでよりクリーンなコードを簡単に記述できます。 各テスト(例)にSimpleClassのインスタンスが必要な場合、そのコードをbeforeフックに入れることができ、各例に追加する必要はありません。

「Creating a new instance of the SimpleClass class」という行がコンソールに2回書き込まれていることに注意してください。これは、各* itブロック*でフックが呼び出される前を示しています。

前述したように、RSpecにはafterフックもあり、beforeフックとafterフックの両方が引数として使用できます。 afterフックは、指定されたターゲットの後に実行されます。 すべてのターゲットは、すべての例の前/後にフックが実行されることを意味します。 各フックがいつ呼び出されるかを示す簡単な例を次に示します。

describe "Before and after hooks" do
   before(:each) do
      puts "Runs before each Example"
   end

   after(:each) do
      puts "Runs after each Example"
   end

   before(:all) do
      puts "Runs before all Examples"
   end

   after(:all) do
      puts "Runs after all Examples"
   end

   it 'is the first Example in this spec file' do
      puts 'Running the first Example'
   end

   it 'is the second Example in this spec file' do
      puts 'Running the second Example'
   end
end

上記のコードを実行すると、この出力が表示されます-

Runs before all Examples
Runs before each Example
Running the first Example
Runs after each Example
.Runs before each Example
Running the second Example
Runs after each Example
.Runs after all Examples

RSpec-タグ

RSpecタグは、specファイルで特定のテストを実行する簡単な方法を提供します。 デフォルトでは、RSpecは実行するspecファイル内のすべてのテストを実行しますが、実行する必要があるのはそれらのサブセットのみです。 非常に高速に実行されるテストがいくつかあり、アプリケーションコードに変更を加えたばかりで、クイックテストを実行したい場合、このコードはRSpecタグでそれを行う方法を示します。

describe "How to run specific Examples with Tags" do
   it 'is a slow test', :slow = > true do
      sleep 10
      puts 'This test is slow!'
   end

   it 'is a fast test', :fast = > true do
      puts 'This test is fast!'
   end
end

次に、上記のコードをtag_spec.rbという新しいファイルに保存します。 コマンドラインから次のコマンドを実行します。rspec --tag slow tag_spec.rb

この出力が表示されます-

実行オプション:\ {:slow ⇒ true}を含める

This test is slow!
.
Finished in 10 seconds (files took 0.11601 seconds to load)
1 example, 0 failures

次に、次のコマンドを実行します:rspec --tag fast tag_spec.rb

この出力が表示されます-

Run options: include {:fast = >true}
This test is fast!
.
Finished in 0.001 seconds (files took 0.11201 seconds to load)
1 example, 0 failures

ご覧のとおり、RSpecタグを使用すると、テストのサブセットを非常に簡単に作成できます。

RSpec-サブジェクト

RSpecの長所の1つは、テストを作成し、テストをクリーンにする多くの方法を提供することです。 テストが短く整理されていると、テストの記述方法の詳細ではなく、予想される動作に焦点を当てやすくなります。 RSpecのサブジェクトは、簡単な簡単なテストを作成できるもう1つのショートカットです。

このコードを検討してください-

class Person
   attr_reader :first_name, :last_name

   def initialize(first_name, last_name)
      @first_name = first_name
      @last_name = last_name
   end
end

describe Person do
   it 'create a new person with a first and last name' do
      person = Person.new 'John', 'Smith'

      expect(person).to have_attributes(first_name: 'John')
      expect(person).to have_attributes(last_name: 'Smith')
   end
end

実際はかなり明確ですが、例のコードの量を減らすためにRSpecのサブジェクト機能を使用できます。 これを行うには、personオブジェクトのインスタンス化を記述行に移動します。

class Person
   attr_reader :first_name, :last_name

   def initialize(first_name, last_name)
      @first_name = first_name
      @last_name = last_name
   end

end

describe Person.new 'John', 'Smith' do
   it { is_expected.to have_attributes(first_name: 'John') }
   it { is_expected.to have_attributes(last_name: 'Smith') }
end

このコードを実行すると、この出力が表示されます-

..
Finished in 0.003 seconds (files took 0.11201 seconds to load)
2 examples, 0 failures

2番目のコードサンプルがどれほど単純であるかに注意してください。 最初の例で1つの* itブロック*を取得し、それを2つの* itブロック*に置き換えました。これにより、必要なコードが少なくなり、同様に明確になります。

RSpec-ヘルパー

RSpecの例には、再利用可能なコードを簡単に共有する方法が必要な場合があります。 これを達成する最良の方法は、ヘルパーを使用することです。 ヘルパーは基本的に通常のRubyメソッドであり、サンプル間で共有します。 ヘルパーを使用する利点を説明するために、このコードを考えてみましょう-

class Dog
   attr_reader :good_dog, :has_been_walked

   def initialize(good_or_not)
      @good_dog = good_or_not
      @has_been_walked = false
   end

   def walk_dog
      @has_been_walked = true
   end
end

describe Dog do
   it 'should be able to create and walk a good dog' do
      dog = Dog.new(true)
      dog.walk_dog

      expect(dog.good_dog).to be true
      expect(dog.has_been_walked).to be true
   end

   it 'should be able to create and walk a bad dog' do
      dog = Dog.new(false)
      dog.walk_dog

      expect(dog.good_dog).to be false
      expect(dog.has_been_walked).to be true

   end
end

このコードは明確ですが、可能な限り繰り返しコードを減らすことを常にお勧めします。 create_and_walk_dog()と呼ばれるヘルパーメソッドを使用して上記のコードを取得し、この繰り返しの一部を減らすことができます。

class Dog
   attr_reader :good_dog, :has_been_walked

   def initialize(good_or_not)
      @good_dog = good_or_not
      @has_been_walked = false
   end

   def walk_dog
      @has_been_walked = true
   end
end

describe Dog do
   def create_and_walk_dog(good_or_bad)
      dog = Dog.new(good_or_bad)
      dog.walk_dog
      return dog
   end

   it 'should be able to create and walk a good dog' do
      dog = create_and_walk_dog(true)

      expect(dog.good_dog).to be true
      expect(dog.has_been_walked).to be true
   end

   it 'should be able to create and walk a bad dog' do
      dog = create_and_walk_dog(false)

      expect(dog.good_dog).to be false
      expect(dog.has_been_walked).to be true
   end
end

上記のコードを実行すると、この出力が表示されます-

..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures

ご覧のとおり、犬のオブジェクトを作成して、ヘルパーを作成するためのロジックをプッシュすることができました。これにより、例がより簡潔になります。

RSpec-メタデータ

RSpecは柔軟で強力なツールです。 RSpecのメタデータ機能も例外ではありません。 メタデータは一般に「データに関するデータ」を指します。 RSpecでは、これは describecontext および* itブロック*に関するデータを意味します。

例を見てみましょう-

RSpec.describe "An Example Group with a metadata variable", :foo => 17 do
   context 'and a context with another variable', :bar => 12 do

      it 'can access the metadata variable of the outer Example Group' do |example|
         expect(example.metadata[:foo]).to eq(17)
      end

      it 'can access the metadata variable in the context block' do |example|
         expect(example.metadata[:bar]).to eq(12)
      end

   end
end

上記のコードを実行すると、この出力が表示されます-

..
Finished in 0.002 seconds (files took 0.11301 seconds to load)
2 examples, 0 failures

メタデータは、RSpecファイル内のさまざまなスコープで変数を割り当てる方法を提供します。 example.metadata変数は、例および例グループに関する他の情報を含むRubyハッシュです。

たとえば、上記のコードを次のように書き換えてみましょう-

RSpec.describe "An Example Group with a metadata variable", :foo => 17 do
   context 'and a context with another variable', :bar => 12 do

      it 'can access the metadata variable in the context block' do |example|
         expect(example.metadata[:foo]).to eq(17)
         expect(example.metadata[:bar]).to eq(12)
         example.metadata.each do |k,v|
         puts "#{k}: #{v}"
      end

   end
end

このコードを実行すると、example.metadataハッシュ内のすべての値が表示されます-

.execution_result: #<RSpec::Core::Example::ExecutionResult:0x00000002befd50>
block: #<Proc:0x00000002bf81a8@C:/rspec_tutorial/spec/metadata_spec.rb:7>
description_args: ["can access the metadata variable in the context block"]
description: can access the metadata variable in the context block
full_description: An Example Group with a metadata variable and a context
   with another variable can access the metadata variable in the context block
described_class:
file_path: ./metadata_spec.rb
line_number: 7
location: ./metadata_spec.rb:7
absolute_file_path: C:/rspec_tutorial/spec/metadata_spec.rb
rerun_file_path: ./metadata_spec.rb
scoped_id: 1:1:2
foo: 17
bar: 12
example_group:
{:execution_result=>#<RSpec::Core::Example::ExecutionResult:
   0x00000002bfa0e8>, :block=>#<
   Proc:0x00000002bfac00@C:/rspec_tutorial/spec/metadata_spec.rb:2>,
   :description_args=>["and a context with another variable"],

   :description=>"and a context with another variable",
   :full_description=>"An Example Group with a metadata variable
   and a context with another variable", :described_class=>nil,
      :file_path=>"./metadata_spec.rb",

   :line_number=>2, :location=>"./metadata_spec.rb:2",
      :absolute_file_path=>"C:/rspec_tutorial/spec/metadata_spec.rb",
      :rerun_file_path=>"./metadata_spec.rb",

   :scoped_id=>"1:1", :foo=>17, :parent_example_group=>
      {:execution_result=>#<
      RSpec::Core::Example::ExecutionResult:0x00000002c1f690>,
      :block=>#<Proc:0x00000002baff70@C:/rspec_tutorial/spec/metadata_spec.rb:1>
      , :description_args=>["An Example Group with a metadata variable"],

   :description=>"An Example Group with a metadata variable",
   :full_description=>"An Example Group with a metadata variable",
    :described_class=>nil, :file_path=>"./metadata_spec.rb",
   :line_number=>1, :location=>"./metadata_spec.rb:1",
   :absolute_file_path=>

   "C:/rspec_tutorial/spec/metadata_spec.rb",
   :rerun_file_path=>"./metadata_spec.rb",
   :scoped_id=>"1", :foo=>17},
   :bar=>12}shared_group_inclusion_backtrace: []

last_run_status: unknown .
.
Finished in 0.004 seconds (files took 0.11101 seconds to load)
2 examples, 0 failures

ほとんどの場合、このメタデータのすべてを使用する必要はありませんが、完全な説明値を見てください-

メタデータ変数と別の変数を持つコンテキストを持つサンプルグループは、コンテキストブロック内のメタデータ変数にアクセスできます。

これは、describeブロックの説明+含まれるコンテキストブロックの説明+ * itブロック*の説明から作成された文です。

ここで興味深いのは、これら3つの文字列が一緒になって通常の英語の文のように読めることです。 . . これはRSpecの背後にある考え方の1つであり、動作の英語の説明のように聞こえるテストを持っています。

RSpec-フィルタリング

RSpecフィルタリングはRSpecメタデータに基づいているため、このセクションを読む前にRSpecメタデータのセクションを読むことをお勧めします。

スペックファイルがあり、2つのタイプのテスト(例)が含まれていると想像してください:ポジティブ機能テストとネガティブ(エラー)テスト。 このように定義しましょう-

RSpec.describe "An Example Group with positive and negative Examples" do
   context 'when testing Ruby\'s build-in math library' do

      it 'can do normal numeric operations' do
         expect(1 + 1).to eq(2)
      end

      it 'generates an error when expected' do
         expect{1/0}.to raise_error(ZeroDivisionError)
      end

   end
end

さて、上記のテキストを「filter_spec.rb」というファイルとして保存し、このコマンドで実行します-

rspec filter_spec.rb

次のような出力が表示されます-

..
Finished in 0.003 seconds (files took 0.11201 seconds to load)
2 examples, 0 failures

ここで、このファイルの肯定的なテストのみを再実行したい場合はどうでしょうか? または、負のテストのみですか? これは、RSpec Filtersで簡単に行えます。 上記のコードをこれに変更します-

RSpec.describe "An Example Group with positive and negative Examples" do
   context 'when testing Ruby\'s build-in math library' do

      it 'can do normal numeric operations', positive: true do
         expect(1 + 1).to eq(2)
      end

      it 'generates an error when expected', negative: true do
         expect{1/0}.to raise_error(ZeroDivisionError)
      end

   end
end

変更をfilter_spec.rbに保存し、このわずかに異なるコマンドを実行します-

rspec --tag positive filter_spec.rb

これで、次のような出力が表示されます-

Run options: include {:positive=>true}
.
Finished in 0.001 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

--tag positiveを指定することで、RSpecに、ポジティブメタデータ変数が定義されたサンプルのみを実行するように指示しています。 このようなコマンドを実行することで、負のテストでも同じことができます-

rspec --tag negative filter_spec.rb

これらは単なる例であり、任意の名前でフィルターを指定できることに注意してください。

RSpecフォーマッター

フォーマッタにより、RSpecはさまざまな方法でテストからの出力を表示できます。 このコードを含む新しいRSpecファイルを作成しましょう-

RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do
   context 'when running some tests' do

      it 'the test usually calls the expect() method at least once' do
         expect(1 + 1).to eq(2)
      end

   end
end

さて、これをformatter_spec.rbというファイルに保存し、このRSpecコマンドを実行します-

rspec formatter_spec.rb

次のような出力が表示されるはずです-

.
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

今、同じコマンドを実行しますが、今回はこのようにフォーマッタを指定します-

rspec --format progress formatter_spec.rb

今回は同じ出力が表示されるはずです-

.
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

その理由は、「進行中」フォーマッターがデフォルトのフォーマッターだからです。 次に別のフォーマッタを試して、このコマンドを実行してみてください-

rspec --format doc formatter_spec.rb

今、あなたはこの出力が表示されるはずです-

A spec file to demonstrate how RSpec Formatters work
   when running some tests
      the test usually calls the expect() method at least once
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

ご覧のとおり、出力は「doc」フォーマッタとは大きく異なります。 このフォーマッタは、ドキュメントのようなスタイルで出力を提示します。 テストに失敗したときに、これらのオプションがどのように見えるか疑問に思われるかもしれません(例)。 formatter_spec.rb のコードを次のように変更してみましょう-

RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do
   context 'when running some tests' do

      it 'the test usually calls the expect() method at least once' do
         expect(1 + 1).to eq(1)
      end

   end
end

期待値* expect(1 + 1).to eq(1)*は失敗します。 変更を保存し、上記のコマンドを再実行します-

*rspec --format progress formatter_spec.rb* を覚えておいてください。「プログレス」フォーマッターがデフォルトであるため、実行できるのは *rspec formatter_spec.rb* です。 この出力が表示されるはずです-
F
Failures:
1) A spec file to demonstrate how RSpec Formatters work when running some tests
the test usually calls the expect() method at least once
   Failure/Error: expect(1 + 1).to eq(1)

      expected: 1
         got: 2

      (compared using ==)
   # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'

Finished in 0.016 seconds (files took 0.11201 seconds to load)
1 example, 1 failure
Failed examples:

rspec ./formatter_spec.rb:3 # A spec file to demonstrate how RSpec
   Formatters work when running some tests the test usually calls
   the expect() method at least once

さて、ドキュメントフォーマッタを試して、このコマンドを実行してください-

rspec --format doc formatter_spec.rb

さて、失敗したテストでは、この出力が表示されるはずです-

A spec file to demonstrate how RSpec Formatters work
   when running some tests
      the test usually calls the expect() method at least once (FAILED - 1)

Failures:

1) A spec file to demonstrate how RSpec Formatters work when running some
   tests the test usually calls the expect() method at least once
   Failure/Error: expect(1 + 1).to eq(1)

   expected: 1
        got: 2

   (compared using ==)
   # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'

Finished in 0.015 seconds (files took 0.11401 seconds to load)
1 example, 1 failure

失敗した例

rspec ./formatter_spec.rb:3#いくつかのテストを実行するときにRSpec Formatterがどのように機能するかを示すための仕様ファイル。テストは通常​​、expect()メソッドを少なくとも1回呼び出します。

RSpec Formatterは、テスト結果の表示方法を変更する機能を提供します。独自のカスタムFormatterを作成することも可能ですが、それはより高度なトピックです。

RSpec-期待

RSpecを学ぶとき、期待について多くを読むかもしれませんが、最初は少し混乱するかもしれません。 「期待」という用語が表示される場合、留意すべき主な2つの詳細があります-

  • Expectationは、* expect()メソッドを使用する itブロック*内のステートメントです。 それでおしまい。 それ以上に複雑ではありません。 次のようなコードがある場合:* expect(1 + 1).to eq(2)、あなたの例には期待があります。 式 *1 + 12 に評価されることを期待しています。 ただし、RSpecはBDDテストフレームワークであるため、文言は重要です。 このステートメントをExpectationと呼ぶことで、RSpecコードがテスト対象のコードの「動作」を記述していることが明確になります。 アイデアは、ドキュメントのように読み取る方法で、コードの動作方法を表現しているということです。 期待構文は比較的新しいものです。 expect()メソッドが導入される前(2012年)、RSpecは should()メソッドに基づく別の構文を使用していました。 上記のExpectationは、古い構文の(1 + 1).should eq(2)*のように記述されています。

古いコードベースまたは古いバージョンのRSpecで作業する場合、期待の古いRSpec構文が発生する場合があります。 RSpecの新しいバージョンで古い構文を使用すると、警告が表示されます。

たとえば、このコードで-

RSpec.describe "An RSpec file that uses the old syntax" do
   it 'you should see a warning when you run this Example' do
      (1 + 1).should eq(2)
   end
end

実行すると、次のような出力が得られます-

. Deprecation Warnings:

Using `should` from rspec-expectations' old `:should`
   syntax without explicitly enabling the syntax is deprecated.
   Use the new `:expect` syntax or explicitly enable

`:should` with `config.expect_with( :rspec) { |c| c.syntax = :should }`
   instead. Called from C:/rspec_tutorial/spec/old_expectation.rb:3 :in
   `block (2 levels) in <top (required)>'.

If you need more of the backtrace for any of these deprecations to
   identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the deprecation
   warnings into errors, giving you the full backtrace.

1 deprecation warning total
Finished in 0.001 seconds (files took 0.11201 seconds to load)
1 example, 0 failures

古い構文を使用する必要がない限り、should()ではなくexpect()を使用することを強くお勧めします。