LoginSignup
10
8

More than 5 years have passed since last update.

RSpec再履修(RSpec Core編)

Last updated at Posted at 2017-01-24

まえおき

改めてRSpecについてRelishを流し読みして学びのあったところをチェックしてみました。
使いやすそうだなと思った一部のみ抜粋しているため、網羅しているわけではないです。
また、量が多いのでとりあえずRspec Coreだけ見ました。
https://relishapp.com/rspec/rspec-core/v/3-5/docs

$ rspec -v
3.5.4

shared_examples

テスト項目を共有して再利用できます。

shared_examples "共有したいテスト項目" do
  it { expect(1).to eq(1) }
end

describe "テスト" do
  it_behaves_like "共有したいテスト項目"
end
$ bundle exec rspec spec/sample/shared_examples_spec.rb --format doc

テスト
  behaves like 共有したいテスト項目
    should eq 1

shared_examplesは引数をとることができます。


class Food; end
class Sushi < Food; end
class Drink; end
class Tea < Drink; end

shared_examples "親クラス確認" do |child, parent|
  it "#{child}の親クラスは#{parent}である" do
    expect(child.superclass).to eq(parent)
  end
end

describe "それぞれの親クラスを確認" do
  it_behaves_like "親クラス確認", Sushi, Food
  it_behaves_like "親クラス確認", Tea,   Drink
end
$ bundle exec rspec spec/sample/shared_examples_spec.rb --format doc

それぞれの親クラスを確認
  Sushiの親クラスはFoodである
  Teaの親クラスはDrinkである

Finished in 0.00301 seconds (files took 0.2289 seconds to load)
2 examples, 0 failures

shared_context

条件を共有することができます。

shared_context "共有する条件" do
  let(:shared_let) { "hello" }
  before do
    shared_let.concat("world")
  end
end

describe "shared_contextのテスト" do
  include_context "共有する条件"
  it { expect(shared_let).to eq("helloworld") }
end
$ bundle exec rspec spec/sample/shared_context_spec.rb --format doc

shared_contextのテスト
  should eq "helloworld"

configに宣言を定義しておくと、include_context を使わずに共有できます。
build_sushi という定義をしてみます。

spec/spec_helper_for_shared_context.rb
class Sushi
  attr_accessor :name
  def initialize(name=nil)
    @name = name
  end
end

shared_context "寿司を握る" do
  let(:sushi) { Sushi.new("トロ") }
end

RSpec.configure do |rspec|
  rspec.include_context "寿司を握る", build_sushi: true
end
spec/sample/shared_context.rb
require "spec_helper_for_shared_context.rb"

describe "宣言で共有する", build_sushi: true do
  it { expect(sushi.name).to eq("トロ") }
end

describe "個別に共有する" do
  let(:sushi) { Sushi.new("あなご") }
  it { expect(sushi.name).to eq("あなご") }

  it "トロ定義済み", build_sushi: true do
    expect(sushi.name).to eq("トロ")
  end

end

Command line

RSpec実行時にオプションを指定することで特定のテストを実行、除外などが可能です。

--example, --tag

spec/sample/command_line/command_line1_spec.rb
describe "Command Line テスト ひとつめ" do
  it "ログインに関するテスト その1" do; end
  it "ログインに関するテスト その2", required: false do; end
  it "ログインに関するテスト その3", required: true, type: 'important' do; end
  it "ログアウトに関するテスト" do; end
end
spec/sample/command_line/command_line2_spec.rb
describe "Command Line テスト ふたつめ" do
  it "レコード作成に関するテスト その1", type: 'heavy' do; end
  it "レコード作成に関するテスト その2" do; end
  it "レコード作成に関するテスト その3", required: true do; end
  it "レコード削除に関するテスト", type: 'heavy' do; end
end

describe "Command Line テスト みっつめ" do
  it "関係のない名前" do; end
end

--example 'テスト' を指定すると説明にテストを含むものを実行する。
itの命名に使われていなくても、describeに含めばそれも実行される。

$ bundle exec rspec spec/sample/command_line --example 'テスト' --format doc
Run options: include {:full_description=>/テスト/}

Command Line テスト ひとつめ
  ログインに関するテスト その1
  ログインに関するテスト その2
  ログインに関するテスト その3
  ログアウトに関するテスト

Command Line テスト ふたつめ
  レコード作成に関するテスト その1
  レコード作成に関するテスト その2
  レコード作成に関するテスト その3
  レコード削除に関するテスト

Command Line テスト みっつめ
  関係のない名前

Finished in 0.0036 seconds (files took 0.35518 seconds to load)
9 examples, 0 failures

--example 'その2' を指定すると説明にその2を含むものしか実行しない。

$ bundle exec rspec spec/sample/command_line --example 'その2' --format doc
Run options: include {:full_description=>/その2/}

Command Line テスト ひとつめ
  ログインに関するテスト その2

Command Line テスト ふたつめ
  レコード作成に関するテスト その2

Finished in 0.00192 seconds (files took 0.19095 seconds to load)
2 examples, 0 failures

--tag required を指定すると required: true がついているものしか実行しない。


$ bundle exec rspec spec/sample/command_line --tag required --format doc
Run options: include {:required=>true}

Command Line テスト ひとつめ
  ログインに関するテスト その3

Command Line テスト ふたつめ
  レコード作成に関するテスト その3

Finished in 0.00132 seconds (files took 0.28193 seconds to load)
2 examples, 0 failures

--tag ~type:heavy を指定すると、type:heavyではないものを実行する。

$ bundle exec rspec spec/sample/command_line --tag ~type:heavy --format doc
Run options: exclude {:type=>"heavy"}

Command Line テスト ひとつめ
  ログインに関するテスト その1
  ログインに関するテスト その2
  ログインに関するテスト その3
  ログアウトに関するテスト

Command Line テスト ふたつめ
  レコード作成に関するテスト その2
  レコード作成に関するテスト その3

Command Line テスト みっつめ
  関係のない名前

Finished in 0.00321 seconds (files took 0.20338 seconds to load)
7 examples, 0 failures

行数指定

spec/sample/command_line/line_number_spec.rb
describe "行数指定の確認" do
  it "2行目" do; end

  it "4行目" do
    expect(1).to eq(1)
  end

  it "8行目" do; end
end

ファイル名のあとに:行番号で特定のテストを実行できます。


$ bundle exec rspec spec/sample/command_line/line_number_spec.rb:2 --format doc
Run options: include {:locations=>{"./spec/sample/command_line/line_number_spec.rb"=>[2]}}

行数指定の確認
  2行目

Finished in 0.00093 seconds (files took 0.30578 seconds to load)
1 example, 0 failures

--only-failures

設定をすると失敗したテストのみ再実行するようにできます。

spec/spec_helper_for_failure.rb
RSpec.configure do |config|
  config.example_status_persistence_file_path = "spec/examples.txt"
end
spec/sample/command_line/failure_spec.rb
require "spec_helper_for_failure"

describe "only failures 確認" do
  it "成功1" do
    expect("文字列".class).to eq(String)
  end

  it "失敗1" do
    expect(1.class).to eq(String)
  end

  it "成功2" do
    expect(1.class).to eq(Fixnum)
  end

  it "失敗2" do
    expect([1].class).to eq(Fixnum)
  end
end

これで $ bundle exec rspec spec/sample/command_line/failure_spec.rb を実行すると、2つテストが失敗し、txtファイルが生成される。

spec/examples.txt
example_id                                      | status | run_time        |
----------------------------------------------- | ------ | --------------- |
./spec/sample/command_line/failure_spec.rb[1:1] | passed | 0.00211 seconds |
./spec/sample/command_line/failure_spec.rb[1:2] | failed | 0.09153 seconds |
./spec/sample/command_line/failure_spec.rb[1:3] | passed | 0.00024 seconds |
./spec/sample/command_line/failure_spec.rb[1:4] | failed | 0.00239 seconds |

その後、 --only-failure オプションをつけて実行すると、失敗したテストのみ再実行できる。

$ bundle exec rspec spec/sample/command_line/failure_spec.rb --only-failure                               2.3.1
Run options: include {:last_run_status=>"failed"}
FF

Failures:

  1) only failures 確認 失敗1
     Failure/Error: expect(1.class).to eq(String)

       expected: String
            got: Fixnum

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -String
       +Fixnum

     # ./spec/sample/command_line/failure_spec.rb:9:in `block (2 levels) in <top (required)>'

  2) only failures 確認 失敗2
     Failure/Error: expect([1].class).to eq(Fixnum)

       expected: Fixnum
            got: Array

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -Fixnum
       +Array

     # ./spec/sample/command_line/failure_spec.rb:17:in `block (2 levels) in <top (required)>'

Finished in 0.10357 seconds (files took 0.64171 seconds to load)
2 examples, 2 failures

Failed examples:

rspec ./spec/sample/command_line/failure_spec.rb:8 # only failures 確認 失敗1
rspec ./spec/sample/command_line/failure_spec.rb:16 # only failures 確認 失敗2

また、 --next-failure オプションを活用することで少しずつ修正を進めていける様子。(まだ実際にこれはあまり活用したことがない...。)

詳細はRelishを参照してください!
https://relishapp.com/rspec/rspec-core/v/3-5/docs/command-line/only-failures

around hooks

aroundでテスト実行前後に処理することができます。
https://relishapp.com/rspec/rspec-core/v/3-5/docs/hooks/around-hooks

spec/sample/around_hooks.rb
describe "around hook" do
  around(:example) do |example|
    puts "実行前"
    example.run
    puts "実行後"
  end
  it { puts "テスト実行" }
end
$ bundle exec rspec spec/sample/around_hooks.rb 
実行前
テスト実行
実行後
.

Finished in 0.00125 seconds (files took 0.18901 seconds to load)
1 example, 0 failures

Metadata

Current example

itの説明の内容を検証に含むことができる。
shared_exampleとかと組み合わせたらもっと有用な感じに書けそう?
https://relishapp.com/rspec/rspec-core/v/3-5/docs/metadata/current-example

spec/sample/metadata_description_spec.rb
class Sushi
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end

describe "description取得の確認" do
  let(:sushi) {|example| Sushi.new(example.description) }
  it "甘エビ" do
    expect(sushi.name).to eq("甘エビ")
  end
  it "いくら" do
    expect(sushi.name).to eq("いくら")
  end
end

described_class

describeにセットされている値を扱うことができる。
これが存在を知ってから一番活用しているやつです。便利。
https://relishapp.com/rspec/rspec-core/v/3-5/docs/metadata/described-class

spec/sample/metadata_described_class_spec.rb
class Food; end
class Sushi < Food; end
class Sweet < Food; end

shared_examples "Foodを継承している" do
  it { expect(described_class.superclass).to eq(Food) }
end

describe Sushi do
  it_behaves_like "Foodを継承している"
end

describe Sweet do
  it_behaves_like "Foodを継承している"
end

Conditional Filters

各項目にif: false, もしくはunless: trueをつけると実行時に無視されます。
https://relishapp.com/rspec/rspec-core/v/3-5/docs/filtering/conditional-filters

spec/sample/conditional_filters_spec.rb
describe "if: true の describe", if: true do
  it("if: true なので含まれる", if: true) {}
  it("if: false なので除外", if: false) {}
  it("unless: true なので除外", unless: true) {}
  it("unless: false なので含まれる", unless: false) {}
end

describe "if: false の describe", if: false do
  it("if: true なので含まれる", if: true) {}
  it("if: false なので除外", if: false) {}
  it("unless: true なので除外", unless: true) {}
  it("unless: false なので含まれる", unless: false) {}
end
$ bundle exec rspec spec/sample/conditional_filters_spec.rb --format doc

if: true の describe
  if: true なので含まれる
  unless: false なので含まれる

if: false の describe
  if: true なので含まれる

Finished in 0.00162 seconds (files took 0.23367 seconds to load)
3 examples, 0 failures

ちなみに --tag でわざと指定しても filterd out と言われてしまいました。

$ bundle exec rspec spec/sample/conditional_filters_spec.rb --tag if:false --format doc
Run options: include {:if=>false}

All examples were filtered out

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

Pending and Skipped

pending(未決定、保留中)とskip(とばす)を設定しておくことができます。
pendingは現時点で失敗してしまうテスト項目に付与するものなので、成功するテストがpendingしていると失敗扱いとなります。
一方skipは単純にskipしたいテスト項目に対して付与します。
pendingskipともに理由をつけておくと実行時に表示してくれます。

英語力が足りなかったので下記の記事を参考にしました!
Rspec の pending と skip って何が違うの? - Qiita

spec/sample/pending_and_skipped_spec.rb
# pending specs
describe "Pendingなテスト" do
  it "pendingです" do
    pending "まだです"
    fail
  end
  it "エラーが無いけどpending" do
    pending "エラーが無いときはpendingはFailed"
    expect(1).to eq(1) # エラーが無いpendingは不適とみなされてFAILED
  end
end

# skip specs
describe "テストをskipする" do
  it("skipします その1") { skip("skipする理由") }
  it("skipします その2", skip: "skipする理由") {}
  xit("skipします その3") { } # 一時的なskipという意味があるので理由不要
end
$ bundle exec rspec spec/sample/pending_and_skipped_spec.rb --format doc

Pendingなテスト
  pendingです (PENDING: まだです)
  エラーが無いけどpending (FAILED - 1)

テストをskipする
  skipします その1 (PENDING: skipする理由)
  skipします その2 (PENDING: skipする理由)
  skipします その3 (PENDING: Temporarily skipped with xit)

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

  1) Pendingなテスト pendingです
     # まだです
     Failure/Error: fail
     RuntimeError:
     # ./spec/sample/pending_and_skipped_spec.rb:5:in `block (2 levels) in <top (required)>'

  2) テストをskipする skipします その1
     # skipする理由
     # ./spec/sample/pending_and_skipped_spec.rb:15

  3) テストをskipする skipします その2
     # skipする理由
     # ./spec/sample/pending_and_skipped_spec.rb:16

  4) テストをskipする skipします その3
     # Temporarily skipped with xit
     # ./spec/sample/pending_and_skipped_spec.rb:17


Failures:

  1) Pendingなテスト エラーが無いけどpending FIXED
     Expected pending 'エラーが無いときはpendingはFailed' to fail. No Error was raised.
     # ./spec/sample/pending_and_skipped_spec.rb:7

Finished in 0.00531 seconds (files took 0.27895 seconds to load)
5 examples, 1 failure, 4 pending

Failed examples:

rspec ./spec/sample/pending_and_skipped_spec.rb:7 # Pendingなテスト エラーが無いけどpending

テストリポジトリ

ごにょごにょしたファイル群を置いておきます。
https://github.com/betachelsea/sample_rspec

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8