RSpecの概要を調べる

#はじめに
RSpecの基本的な勉強をしたので学習をまとめる

#RSpecとは
公式ドキュメントの翻訳
>RSpec は、Ruby プログラマー向けの[ビヘイビア駆動開発](https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%98%E3%82%A4%E3%83%93%E3%82%A2%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA)ツールです。BDD は、テスト駆動開発、ドメイン駆動設計、受け入れテスト駆動計画を組み合わせたソフトウェア開発手法です。

これだけでは分からなかったので、他を参考にさせてもらうとテストフレームワークの1つ、ということみたい。

#メリット
自然言語（英語等）を併記するコードの特徴から可読性が上がる
TDDの観点からだとコードの変更時に予期せぬ不具合を防げる

#導入手順(Ruby)
プロジェクトの下準備

```terminal
# ディレクトリの作成
mkdir project_ruby  # プロジェクトファイル
mkdir project_ruby/lib # テスト対象のファイルを入れるディレクトリ

# ディレクトリに移動
cd project_ruby

# bundleの初期化（Gemfile作成）
bundle init

# gemの追加とインストールを同時に行う
bundle add rspec

# RSpecの設定ファイルを作成（.rspec, spec/spec_helper.rb）
bundle exec rspec --init

```
次に読み込みの設定を行う。

```spec/spec_helper.rb
#lib内のファイルを全て読み込むように設定する時
Dir[File.join(File.dirname(__FILE__), "../lib/**/*.rb")].each { |f| require f }

#特定のファイルを読み込む設定にする時
require_relative "../lib/読み込みたいファイル名.rb"
```
読み込み自体はテストコードに直接書いても挙動は変わらないようなので、共通設定は`spec_helper.rb`に書く方が重複せず済む。

これで準備完了。
`lib` にテスト対象のコードを書く
`spec` にテストコードを書く

ファイル|説明
-|-
lib|テスト対象をまとめるライブラリ（ディレクトリ）
.rspec|RSpec用の設定ファイル
spec|RSpec用のディレクトリ
spec_helper.rb|RSpecの動きをカスタマイズするファイル

# 導入手順（Rails）
プロジェクトの下準備

```terminal
# プロジェクトの作成、postgresql指定とtestディレクトリを除外
rails new project_rails -d postgresql -T

# プロジェクトへ移動
cd project_rails

# DBの作成
rails db:create
```
Minitestを削除する明確な理由を見かけなかったが、そもそも格納するテストコードは別々のディレクトリ構造なので、必要ないから削除しているぽい。

続いて、Gemfileにgemを追加

```Gemfile
#Rails用のフレームワークを追加
group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails'
end
```

最後にgemのインストールと設定ファイルの作成

```terminal
# gemのインストール
bundle install

# RSpec用の設定ファイルの作成
rails g rspec:install
```
これで完了。
設定ファイルはRubyの時とは異なり、`rails_helper.rb`が追加されている。

ファイル|説明
-|-
.rspec |RSpec用の設定ファイル
spec |RSpec用のディレクトリ
spec/spec_helper.rb |RSpecの動きをカスタマイズするファイル
spec/rails_helper.rb |RSpecの動きをカスタマイズするファイル

#テストの実行

```terminal
# 全てのRSpecを実行
bundle exec rspec

# 一部のRSpecを実行
bundle exec rspec spec/実行したいファイル.rb

#実行結果
=> No examples found.
=> Finished in 0.00052 seconds (files took 0.33436  seconds to load)
=> 0 examples, 0 failures

```
実行結果は下記の通り。

実行結果|説明
-|-
No example |実行結果の内容（記述していないので今回は何も表示されいない）
Finished |テストにかかった時間を表す
0 examples |「0個のテストを実行した」を表す。itの数に比例。
0 failures |「0個のテストを失敗した」を表す。
0 pending |保留マークつけた箇所の警告を表す（上記にはない）

#失敗した実行結果
下記がテストの対象

```lib/student.rb
#テスト対象
class Student
  attr_reader :name, :age

  def initialize(name:, age:)
    @name = name
    @age = age
  end

  def introduce
    "私は#{@name}です。年齢は#{@age}才です。"
  end
end

```
下記がテストコードとした時

```spec/student_spec.rb
#テストコード
RSpec.describe Student do
  describe "initialize" do
    context "インスタンスが生成された時" do
      it "名前の値が正しいこと" do
        student = Student.new(name: "suzuki", age: 20)
        expect(student.name).to eq "suzuki"
      end

      it "年齢の値が正しいこと" do
        student = Student.new(name: "suzuki", age: 20)
        expect(student.age).to eq 10
      end
    end
  end

  describe "introduce" do
    it "正しい名前と年齢を含む値が返ること" do
      student = Student.new(name: "suzuki", age: 10)
      expect(student.introduce).to eq "私はsuzukiです。年齢は20才です。"
    end
  end
end
```
下記のような実行結果となる。

```terminal
Student
  initialize
    インスタンスが生成された時
      名前の値が正しいこと
      年齢の値が正しいこと (FAILED - 1)
  introduce
    正しい名前と年齢を含む値が返ること (FAILED - 2)

Failures:

  1) Student initialize インスタンスが生成された時 年齢の値が正しいこと
     Failure/Error: expect(student.age).to eq 10

       expected: 10
            got: 20

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

  2) Student introduce 正しい名前と年齢を含む値が返ること
     Failure/Error: expect(student.introduce).to eq "私はsuzukiです。年齢は20才です。"

       expected: "私はsuzukiです。年齢は20才です。"
            got: "私はsuzukiです。年齢は10才です。"

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

Finished in 0.01333 seconds (files took 0.48402 seconds to load)
3 examples, 2 failures

Failed examples:

rspec ./spec/student_spec.rb:9 # Student initialize インスタンスが生成された時 年齢の値が正しいこと
rspec ./spec/student_spec.rb:17 # Student introduce 正しい名前と年齢を含む値が返ること
```
実行結果を見てみると、`3 examples, 2 failures`「3つのテストを実行して、2つ失敗した」とある。

`expected: 10`には、「期待していた値」が表示されている。
`got: 20`には、「実際の値」が表示されている。

つまり、ここの差異が失敗の原因を示している。

上記では`1) Student initialize`と`2) Student introduce`の2つが表示されているので、2つの失敗が表示されていることになる。

また問題のある行数も具体的に記述されているので探しやすい。


#設定ファイルをカスタマイズ(.rspec)
```.rspec
# rails_helperを読み込む
--require rails_helper

# 出力結果をドキュメント風に見やすくする
--format documentation

# 出力結果を色分けする
--color
```
必要に応じて`.rspec`に設定項目を追加。不要なものは削除する。

#テストコードの見方
```.rb
#テスト対象を読み込むための記述
require_relative '../lib/ファイル名'

# 基本的な見方
RSpec.describe クラス名 do
  describe "対象(例：バリデーション)" do
    context "条件(例：データがA条件を満たす時)" do
      before do
      end
      it "期待する結果(例：保存できる)" do
        example(式).to eq 期待値
      end
    end
  end
end
```

コード|説明
-|-
RSpec.describe *** do ... end|テストの宣言。これがないと正しく動作しない。
describe "***" do ... end|テストの対象。
context "***" do ... end|条件別にグループ化（describeと機能的には同じ）
before do ... end|事前に処理を行うグループ。
it "***" do ... end|期待する処理と結果をコメント
example(X).to eq Y|テストコード。
コメントの部分は `.` `#` `''` `""` など書き方が色々あるようだが挙動は変わらないため統一されていれば、どれでもいいみたい。

#マッチャ
引用
>マッチャ（matcher）は「期待値と実際の値を比較して、一致した（もしくは一致しなかった）という結果を返すオブジェクト」のことです。
引用元：https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5

コードで言うと、exampleの行にある、`ep`の部分のところみたい。

```.rb
# 「Xの結果とYは等しい」を検証
expect(X).to eq Y

# 「Xの結果とYは異なる」を検証
expect(X).not_to eq Y

# be_falsey/be_truthyもマッチャ（言語仕様に合わせた真偽を判定）
expect(X).to be_truthy
expect(X).to be_falsey

# changeもマッチャ（XするとYが[Z~W]or[±Z]に変わることを期待する）
expect{X}.to change{Y}.form(Z).to(W)
expect{X}.to change{Y}.by(±Z)

# includeもマッチャ（「配列XにYが含まれている」を検証）
X = [Y, Z, W]
expect(X).to include Y
```

参考元の詳細が大変参考になった。
書籍も買ってみたので具体的な部分についてはまた記事にまとめたい。

参考元
[RSpec/公式ドキュメント](https://relishapp.com/rspec/)
[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)
[使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
[RailsじゃないRspec3環境を構築する方法](https://qiita.com/yusabana/items/db44b81bdddf6ed0e9f5)