RSpecの基礎

### RSpecとは
▶ Ruby on Railsで用いられる、テストフレームワークです。RSpecをうまく活用するることで、簡潔で読みやすいテストコードを書くことができ、Railsアプリケーションの保守性を高めることができます。
▶ ちなみにRuby on RailsにはMinitestというテストフレームワークが標準で組み込まれているのですが、私はまだ学習していないので、今回はRSpecについて投稿したいと思います。

## 1. RSpecの初期設定
　GemファイルにRSpecのGemを追記

```ruby
# Gemfile
group :development, :test do      # 開発・テストのグループに記述してGem動作に制限をもたせる
       # 省略
  gem 'rspec-rails', '~> 4.0.0'   # ← gemの追記 
end
```
　ターミナルでbundle installを実行

```
アプリケーションのディレクトリに移動して以下のコマンドを実行
% bundle install
```
　引き続きRSpecをインストール

```
% rails g rspec:install
```
　インストール完了後、以下のディレクトリやファイルが生成されます。

```
ターミナルのログ
Running via Spring 
preloader in process 1087
   create .rspec
   create spec 
   create spec/spec_helper.rb
   create spec/rails_helper.rb
```

　また生成された.rspec.rbに以下を追記することでターミナルでテストコードの結果を可視化する事ができます。

```ruby
# spec/.rspec.rb
--format documentation
```
　以上で初期設定はおしまいです。次は実際にテストコードを書いていきたいと思います。

## 2. RSpecによる簡単なテストコードを書く
それではテストコードを書いていきたいと思います。
　specファイルはspecディレクトリのサブディレクトリに分類して配置します。どのサブディレクトリにどんなspecファイルを置くかは慣習的に決まっています。たとえば、<b>モデルクラス</b>に関するspecファイルは<b>spec/modelsディレクトリ</b>に、<b>API</b>に関するspecファイル<b>spec/requests</b>ディレクトリに置くのが一般的です。

　今回は説明のためにサブディレクトリとして<b>testsディレクトリ</b>を作成し説明していきます。
<b>specディレクトリ</b>に<b>testsディレクトリ</b>のサブディレクトリを作り、その直下に<b>string_spec.rb</b>のファイルを作ります。この時、ファイル名は特に気にしなくて良いのですが、ファイルの末尾は<b>_spec.rb</b>とする必要があります。

## 文字の追加が正しく行われているかテスト
```ruby
# spec/tests/string_spec.rb
require "spec_helper"

describe String do
  it "文字の追加" do
    str = "あいう"                 # 変数strを定義
    str << "え"                  # strに"え"を追加
    expect{str.size}.to eq(4)   # expectメソッドで変数strの状態を調べる。文字の追加後、文字数は４文字（"あいうえ"）
  end
end
```
### describeメソッド
　テストコードのグループ分けを行うメソッドです。「どの機能に対してのテストを行うか」をdescribeでグループ分けし、その中に各テストコードを記述します。また、describeとendで囲まれた部分を<b>エグザンプルグループ</b>といいます。describeメソッドの引数には、クラスまたは文字列を指定します。エグザンプルグループは入れ子構造にすることが可能です。
### itメソッド
　describeメソッド同様に、グループ分けを行うメソッドです。itの場合はより詳細に、「describeメソッドに記述した機能において、どのような状況のテストを行うか」を明記します。
またexampleという用法もあり、itで分けたグループのことを指します。また、itに記述した内容のことを指す場合もあります。
### expectation（エクスペクテーション）
　エクスペクテーションとは、検証で得られた挙動が想定通りなのかを確認する構文です。
<b>expect（T）.to M </b> を雛形に、テストの内容に応じて<b>T（引数）</b>や<b>M（マッチャ）</b>を変えて記述します。
### matcher（マッチャ）
　matcherは、「expectの引数」と「想定した挙動」が一致しているかどうかを判断します。
expectの引数には検証で得られた実際の挙動を指定し、マッチャには、どのような挙動を想定しているかを記述します。
今回はeqとういマッチャを使用しています。eqは、「expectの引数」と「eqの引数」が等しいことを確認するマッチャです。

　それでは、ターミナルでテストコードを実行します。

```
ターミナル
% bundle exec rspec spec/tests/string_spec.rb
```
ログは以下のようになります。テストは<b>成功</b>です。

```
Finished in 0.01423 seconds (files took 0.15324 seconds to load)
1 example, 0 failures
```
### 失敗する場合
　次に<b>失敗の場合</b>を見てみます。追加文字を"えお"の２文字にします。

```ruby
# spec/tests/string_spec.rb
require "spec_helper"

describe String do
  it "文字の追加" do
    str = "あいう"                 # 変数strを定義
    str << "えお"                  # strに"えお"を追加
    expect{str.size}.to eq(4)   # expectメソッドで変数strの状態を調べる。文字の追加後、文字数は５文字（"あいうえお"）
  end
end
```
　ログは以下のようになります。テストは<b>失敗</b>です。

```
String
  文字の追加 (FAILED - 1)

Failures:

  1) String 文字の追加
     Failure/Error: expect(s.size).to eq(4)
     
       expected: 4
            got: 5
     
       (compared using ==)
     # ./spec/tests/string_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.03041 seconds (files took 0.13182 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/tests/string_spec.rb:4 # String 文字の追加
```
この時、 「Failure/Error: expect(s.size).to eq(4)」の後に「expected: 4　got: 5」
という記述があり、想定は４文字なのに実際は５文字であるというエラーメッセージがあります。このようにテストが失敗した場合、ログに失敗の原因のヒントがあります。

## 3. 保留中（pendingメソッド）
　基本的には、すべてのテストが成功するまでソースコードの修正とテストの実行を繰り返すことになります。しかし、原因が分からないとか時間が足りないといった理由ですぐには直せない場合もあります。その場合は、<b>pendingメソッド</b>を使ってエグザンプルに「保留中（pending）」の印を付けることができます。

```ruby
require "spec_helper"

describe String do
  it "文字の追加" do
      pending("原因調査中")    # pendingメソッドには理由を示す引数を与える。この場合"原因調査中"
      s = "あいう"
      s << "えお"
      expect(s.size).to eq(4)
  end
end
```
　この状態でテストを実行すると次のようになります。

```
String
  文字の追加 (PENDING: 原因調査中)

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) String 文字の追加
     # 原因調査中
     Failure/Error: expect(s.size).to eq(4)
     
       expected: 4
            got: 5
     
       (compared using ==)
     # ./spec/tests/string_spec.rb:8:in `block (2 levels) in <top (required)>'

Finished in 0.0292 seconds (files took 0.13358 seconds to load)
1 example, 0 failures, 1 pending
```
　また、pendingメソッドの代わりに<b>xexamplメソッド</b>があります。今回exampleを使用していないのですが、単純に対象のexampleをxexampleに書き換えるだけです。
　これを使うと、ターミナルのログに「xexampleにより一時的に無効化されている」という意味である「Temporarily disabled with xexample」が出力されます。

## 4. エグザンプルの絞り込み
### 複数のエグザンプルグループを持つテストコードの例
```ruby
# spec/tests/test_spec.rb
require "spec_helper"

describe String do                   # 行番号3
  it "文字の追加" do
      s = "あいう"
      s << "え"
      expect(s.size).to eq(4)
  end
end

describe Integer do                  # 行番号11
  it "数字の足し算" do
      i = 1
      i += 1
      expect(i).to eq(2)
  end
end
```
　複数のエグザンプルグループを持つテストコードを実行させる場合、処理に時間がかかることもあり、特定のエグザンプルグルームのみをテストしたい場合があります。
### 行番号による絞り込み
　次のようにパスの後ろにコロン（:）と行番号を指定する。今回は行番号11のエグザンプルグループのみを実行します。

```
ターミナル
 % bundle exec rspec spec/tests/test_spec.rb:11
```
```
ターミナルのログ
Run options: include {:locations=>{"./spec/tests/string_spec.rb"=>[11]}}

Integer
  数字の足し算

Finished in 0.0039 seconds (files took 0.13787 seconds to load)
1 example, 0 failures
```
### タグによる絞り込み
　test_spec.rbを次のように修正します。

```ruby
# spec/tests/test_spec.rb
require "spec_helper"
    #　途中省略 #
describe Integer do                  # 行番号11
  it "数字の足し算" , :exception do    # [, :exception]の追記
      i = 1
      i += 1
      expect(i).to eq(2)
  end
end
```
　exampleメソッドの第2引数に加えた:exceptionというシンボルがタグです。こうしておくと、次のコマンドで:exceptionタグの付いたエグザンプルだけをまとめて実行できます。

```
ターミナル
 % bundle exec rspec spec/tests/test_spec.rb --tag=exception
```
```
ターミナルのログ
Run options: include {:exception=>true}

Integer
  数字の足し算

Finished in 0.004 seconds (files took 0.15853 seconds to load)
1 example, 0 failures
```


　２つあるエグザンプルグループのうち片方のみを実行できました。今回は２つだけですが、実際にアプリケーション開発で多数のエグザンプルグループに分けてテストコードを書くことがあります。１つのエグザンプルグループのコードを修正して、再度テストするときに、他のエグザンプルグループをテストすると非効率です。そのためエグザンプルの絞り込みは重要だと思います。


## 最後に
　今回はRSpecの基礎について投稿させていただきました。紹介したコードは実際に実行して、間違いがないか試していますが、もし誤り等あればご指摘のほうよろしくお願いします。