概要

 BDD を実現するための Ruby 用テストフレームワークになります。

 公式サイトは、 http://rspec.info/ になります。また、 http://www.relishapp.com/rspec こちらのサイトもいろいろな仕様が詳しくのっているので参考になるかと思います。


TDD・BDD

 まずは、 TDD(Test Driven Development)BDD(Behavior Driven Development) とはなんぞやということなのですが…。そこらへんは、こちらの記事 を参考にするとよいかと思います。

 より、機能要件(仕様)のテストに特化した開発という感じでしょうか…。


開発サイクル


  1. 失敗するテストを書く。 (Red)

  2. 最小限のコードを書いて、テストをパスさせる。 (Green)

  3. リファクタリングをする。 (Refactoring)


開発環境

OS: CentOS Linux release 7.5.1804 (Core)

Ruby: ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

Rspec: RSpec 3.8


開発の準備

 まずはじめに $ rspec --init コマンドを実行して、設定ファイル等を作成します。実行すると、 spec/spec_helper.rb.rspec ファイルが作成されているかと思います。ここでは、.rspec だけ編集しておきましょう。



  • spec_helper.rb: 色々な設定を行えるファイル。


  • .rspec: rspec コマンドにデフォルトで渡すコマンドオプション。

# .rspec

--color
--warnings
--require spec_helper

 では、ここからテスト対象となるファイルと、テストファイルを作成します。テスト対象となるファイルは、 class/calculation.rb とし、 テストファイルは、 spec/calculation_spec.rb としましょう。


失敗するテストの記述

 はじめに、BDD の最初のサイクルである 失敗するテストの記述 を行なっていきます。今回は、以下のようなテストを実行します。コメント文を読んで、プログラムの意味は理解してください…。

# spec/calculation_spec.rb

# describe -> テストのグループ化
# '計算' に関するテストを行う!
RSpec.describe '計算' do
# it -> テストの一例(example という単位になります。)
# いくつかの 'expect' を持つ
it '1 + 1 = 2 ?' do
calculation = Calculation.new
# expect -> テストの最小粒度
# test: 1 + 1 = 2 ?
expect(calculation.add(1, 1)).to eq(2)
end
end


テストの実行

 テストを実行するには、 $ rspec コマンドを使います。(特にオプションをつけずに実行すると、適当に spec フォルダ を探してテストを実行してくれます。)実際に、コマンドを実行すると以下のような記述が出てくるでしょうか…。なんとなくテストに失敗しているのがわかるかと思います。何が失敗しているかというのは、 uninitialized constant Calculation を見ればわかりますね。

 ちなみに、 1 example, 1 failure という部分は先のプログラムにちょっとだけ書いてありますが、 it ブロック を 1つのexample と数えるため 1つの example に対し、 1つの失敗があるということがわかります。

Screen Shot 2018-08-14 at 18.34.14.png


仮実装

 それでは、今回のテストをパスするための 最小限のコード を書いていきたいと思います。このような最小限のコードを書くことを 仮実装 のようにも言います。下記のような感じですかね…。

# class/calculation.rb

class Calculation
def add(a, b)
# 仮実装
2
end
end

 また、先ほどCalc クラスを読み込めていなかったので、読み込む設定を追記しておきましょう。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# describe -> テストのグループ化
# '計算' に関するテストを行う!
RSpec.describe '計算' do
# it -> テストの一例
# いくつかの 'expect' を持つ
it '1 + 1 = 2 ?' do
calculation = Calculation.new
# expect -> テストの最小粒度
# test: 1 + 1 = 2 ?
expect(calculation.add(1, 1)).to eq(2)
end
end

 再度、 $ rspec を実行すると、以下のように出てきますね。しっかりと?テストに成功していることがわかります。

Screen Shot 2018-08-14 at 18.35.11.png

 これにてサイクルのうち、2つ目 最小限のコードを書いて、テストをパスさせる。 まで、終わりましたね。今回は、 リファクタリング するほど複雑なコードではないので割愛します。

 まあしかし、これではうまく実装できていないのは明らかです。このような際には、再度BDD サイクルを回していきます。


BDD サイクルの進行


  • Red

 新しく before で共通の前準備をするという操作が加えられている点に注意してください。また、このように違った角度からテストを加えることを 三角測量 とよんだりもします。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# describe -> テストのグループ化
# '計算' に関するテストを行う!
RSpec.describe '計算' do
# example の実行前に毎回呼ばれる処理
before do
@calculation = Calculation.new
end
# it -> テストの一例
# いくつかの 'expect' を持つ
it '1 + 1 = 2 ?' do
# expect -> テストの最小粒度
# test: 1 + 1 = 2 ?
expect(@calculation.add(1, 1)).to eq(2)
end

# 三角測量
it '2 + 5 = 7 ?' do
expect(@calculation.add(2, 5)).to eq(7)
end
end

Screen Shot 2018-08-14 at 18.44.59.png


  • Green

 それでは、テストを通過するようなコードを書いていきましょう。このような実装のことを 明らかな実装 と呼びます。(今回のような簡単な場合は、最初から明らかな実装を行なってもよかったかもしれません。)

# class/calculation.rb

class Calculation
def add(a, b)
# 仮実装
# 2
# 明らかな実装
a + b
end
end

Screen Shot 2018-08-14 at 18.45.26.png


describe / context

 describe は入れ子構造にすることもできます。例えば、以下のような書き方ができます。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# 最上位のdescribe は "Rspec." をつける
# また、引数はテストするクラス名とする
RSpec.describe Calculation do
describe '正常処理' do
it '1 + 1 = 2 ?' do
calculation = Calculation.new
expect(calculation.add(1, 1)).to eq(2)
end
end
describe '異常処理' do
it '' do
end
end
end

 ここで、 describe とよく似ているもので context というものがあります。例えば、以下のような書き方ができます。テスト対象によって使い分けられるようです。


  • テスト対象が 物: describe

  • テスト対象が 状況: context

# spec/calculation_spec.rb

require_relative '../class/calculation'

RSpec.describe Calculation do
context '正常処理' do
it '1 + 1 = 2 ?' do
calculation = Calculation.new
expect(calculation.add(1, 1)).to eq(2)
end
end
context '異常処理' do
it '' do
end
end
end

 テストを実行してみましょう。今回は、よりみやすくするために -fdオプション をつけて実行します。

$ rspec -fd


it / example / specify

 今度は、 it について言及していきます。実はこの it は、 examplespecify で書き換えることができます。(使い分けは、英語の意味からフィーリングでやるといいっぽいです?)

 今回は、 it で統一していきますが、 rails 等で運用する際には少しばかり気を使った方がよいのかもしれませんね…。次の2点について言及します。


  • 引数の省略

  • pending(テストの保留)

# spec/calculation_spec.rb

require_relative '../class/calculation'

RSpec.describe Calculation do
# 引数を省略すると、自動的に rspec がドキュメントを生成してくれる
it do
calculation = Calculation.new
expect(calculation.add(1, 1)).to eq(2)
end
# pending: 保留
# ブロックを記述しないと pending という状態になる
it '1 - 1 = 0 ?'
end

 以下の実行結果から、引数を省略すると、なんか shouled eq 2 みたいな文が自動的に生成されているのがわかります。また、ブロックを記述していないと黄色く pending と記述されていることがわかります。

Screen Shot 2018-08-14 at 19.11.03.png


matcher

 次に、 matcher というものを説明します。 mathcer という用語は初めて使いましたが、実はもうすでに使っています。以下の部分 (.to eq) です。 期待される振る舞いを指定している部分を matcher と呼びます。この場合は、 2と等しい という感じになっているのがなんとなく読み取れますね。

expect(calculation.add(1, 1)).to eq(2)

 様々な matcher があるので、全ては網羅できませんが、以下にいくつか例をあげておきます。

# .to eq CONSTANT -> CONSTANT と等しい

expect(calc.add(2, 3)).to eq(5)

# .not_to eq CONSTANT -> CONSTANT と等しくない
expect(calc.add(2, 3)).not_to eq(5)

# .to be true / false -> true / false である
expect(calc.add(2, 3)).to be true
expect(calc.add(2, 3)).to be false

# .to be (比較演算子) CONSTANT -> (比較演算子) CONSTANT が true
expect(calc.add(2, 3)).to be < 10

# .to be_between(a, b).inclusive -> a から b の間
expect(calc.add(2, 3)).to be_between(1, 10).inclusive

# .to respond_to(:METHOD) -> METHOD を持っているか
expect(calc).to respond_to(:add)

# 整数値チェック
expect(calc.add(2, 3).integer?).to be true

# 整数値チェックの省略化(Ruby の default method(integer? empty? nil? etc...) で扱える。)
expect(calc.add(2, 3)).to be_integer
expect(calc.add(2, 3)).to be_empty
expect(calc.add(2, 3)).to be_nil


subject

 次に、 subject というものを説明します。これは、 先ほど説明した場合での before の代替となるものです。実は、 example 内では、インスタンス変数は使うべきでない ということが言われており、それを解決するために以下のように用います。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# 最上位の'describe' でクラスを指定 -> subject(該当クラスのインスタンス) が使える
RSpec.describe Calculation do
# caluculation (各 'example' におけるローカル変数)の設定
subject(:calculation) { Calculation.new }
it do
expect(calculation.add(1, 1)).to eq(2)
end
end


let / let!

 操作の対象となるものを subject を使うことにより、うまく操作することができました。 let とは、操作の主な対象ではないが、変数を作成したい場合に使用されます。例えば、以下のようなコードになります。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# 最上位の'describe' でクラスを指定 -> subject(該当クラスのインスタンス) が使える
RSpec.describe Calculation do
subject(:calculation) { Calculation.new }
context '5%' do
# rate 変数の作成
let(:rate) { 0.05 }
it do
expect(calculation.proportion(100, rate).to eq(105)
end
end
context '8%' do
let(:rate) { 0.08 }
it do
expect(calculation.proportion(100, rate).to eq(108)
end
end
end

 letlet! での違いとしては、簡単にいえば、 各 example ごとに実行されない or される というものがあげられます。 各 example ごとに必ず実行したい処理なのであれば、以下のように let! を使用しましょう。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# 最上位の'describe' でクラスを指定 -> subject(該当クラスのインスタンス) が使える
RSpec.describe Calculation do
subject(:calculation) { Calculation.new }
context '5%' do
# rate 変数の作成
let!(:rate) { 0.05 }
it do
expect(calculation.proportion(100, rate).to eq(105)
end
it do
# ~~~~~
end
end
end


shared examples

 いくつかの context を作成した際に、共通する exapmle が出てくることがあります。その共通部分を切り出す方法が shared examples です。例えば、以下のようなコードを記述し、実現します。

# spec/calculation_spec.rb

require_relative '../class/calculation'

# 共通部分を切り出す
RSpec.shared_examples "basics" do
it "1."
it "2."
it "3."
end

RSpec.describe Calculation do
context "無料分" do
include_examples "basic functions"
end
context "課金分" do
include_examples "basic functions"
it "4."
it "5."
end
end