<% content_for :menu do %>
  • rspec ガイドラインの紹介
  • » メソッドの説明をする
  • » Contextsを使う
  • » 説明を短く
  • » 単一条件テスト
  • » 可能な限り全部をテスト
  • » Expect対Should
  • » Subjectを使う
  • » letとlet!を使う
  • » Mockを使うか使わないか
  • » 必要なデータだけ作る
  • » fixtureの代わりにfactoryを使う
  • » 読みやすいmatcherを使う
  • » Shared examples
  • » 見えるものをテスト
  • » shouldを使わない
  • » guardを使ったテスト自動化
  • » sporkを使ったテスト高速化
  • » HTTP requestをmockする
  • » 有用なformatter
  • Books
  • Presentations
  • Resources on the web
  • Libraries
  • Styleguide
  • Improving Better Specs
  • Credits
  • Help us
  • <% end %>

    RSpecとはBDDで使う素晴らしいツールです。(BDDとはbehavior-driven developmentの略で方向性を人の読める仕様に沿って開発を行う開発方法論です。)

    ウェブで既に沢山の使い方や _何_ ができるかを説明したRSpecの書き込みを探せますが、RSpecで良いテストの作り方を説明した書き込みはなかなか探せません。

    Better Specsは大抵のガイドラインの書いてない部分を集めようとしました。- これは開発者達の経験を通して学んだ方法です。

    メソッドの説明をする

    作成中のメソッドを明らかにしましょう。例えば、Rubyのコード規約ではクラスメソッドの名前には.(もしくは::)をインスタンスメソッドの名前には#を使っています。

    bad

    describe 'the authenticate method for User' do
    describe 'if the user is an admin' do
    

    good

    describe '.authenticate' do
    describe '#admin?' do
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | メソッドの説明する", url: "http://betterspecs.org/#describe" %>

    Contextsを使う

    Contextsはテストを明らかにし、まとめる素晴らしい方法です。 長い目で見ると、この方法はテストを読みやすくします。

    bad

    it 'has 200 status code if logged in' do
      expect(response).to respond_with 200
    end
    it 'has 401 status code if not logged in' do
      expect(response).to respond_with 401
    end
    

    good

    context 'when logged in' do
      it { is_expected.to respond_with 200 }
    end
    context 'when logged out' do
      it { is_expected.to respond_with 401 }
    end
    

    Contextsでは"when"もしくは"with"で説明し始めましょう。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | Contextsを使う", url: "http://betterspecs.org/#contexts" %>

    説明を短く

    specの説明は40文字を超えないようにしましょう。超えた場合はcontextを分けてください。

    bad

    it 'has 422 status code if an unexpected params will be added' do
    

    good

    context 'when not valid' do
      it { should respond_with 422 }
    end
    

    この例ではit { should respond_with 422 }と書いてステイタスの説明を削除しました。 rspec filenameでテストを実行すると相変わらず読める出力を取得できます。

    Formatted Output

    when not valid
      it should respond with 422
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 説明を短く", url: "http://betterspecs.org/#short" %>

    単一条件テスト

    '単一条件'は'各テストは一つだけ確認すべき'という表現でより広く知られています。 これはエラーを探しやすくし、失敗するテストをすぐ見つけるようにし、コードを読みやすくします。

    独立したユニットでは、各例はただ一つの振る舞いだけテストするのが望ましいです。例の中で多数のテストが有るのは幾つかの振る舞いに分離する必要があることを示しています。

    しかし、分離できないテストで(例えばDBや外部システムとの連動、前後があるテストの場合)分離するだけでは同じセットアップを何回も行い、テストが重くなる現象が現れます。こういった重いテストは分けなくても良いでしょう。

    good (isolated)

    it { should respond_with_content_type(:json) }
    it { should assign_to(:resource) }
    

    Good (not isolated)

    it 'creates a resource' do
      expect(response).to respond_with_content_type(:json)
      expect(response).to assign_to(:resource)
    end
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 単一条件テスト", url: "http://betterspecs.org/#single" %>

    可能な限り全部をテスト

    テストはやった方がいいですが、全ケースをテストしないと、有用とはいえません。有効な場合と無効な場合を全部テストしましょう。例えばこんなアクションが有るとしましょう。

    Destroy action

    before_action :find_owned_resources
    before_action :find_resource
    
    def destroy
      render 'show'
      @consumption.destroy
    end

    普段よく見るエラーで、モデルがうまく削除できたかだけテストしています。 が、少なくとも二つのエッジケースが存在します:モデルを探せなかった時と権限がない時です。 すべての一般的に可能な入力を考えてテストしましょう。

    bad

    it 'shows the resource'
    

    good

    describe '#destroy' do
    
      context 'when resource is found' do
        it 'responds with 200'
        it 'shows the resource'
      end
    
      context 'when resource is not found' do
        it 'responds with 404'
      end
    
      context 'when resource is not owned' do
        it 'responds with 404'
      end
    end
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 可能な限り全部テスト", url: "http://betterspecs.org/#all" %>

    Expect対Should

    新しいプロジェクトではexpectだけ使いましょう。

    bad

    it 'creates a resource' do
      response.should respond_with_content_type(:json)
    end

    good

    it 'creates a resource' do
      expect(response).to respond_with_content_type(:json)
    end

    全体で新しい文法だけ許容し、RSpec2の文法を使えなくなる様に設定できます。

    good

    # spec_helper.rb
    RSpec.configure do |config|
      # ...
      config.expect_with :rspec do |c|
        c.syntax = :expect
      end
    end

    RSpec expectation文法に対するもっと詳しい情報はここここをご覧ください。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | Expect対Should", url: "http://betterspecs.org/#expect" %>

    Subjectを使う

    もしも、同じsubjectに対して複数のテストをしていたら、subject{}を使ってDRYしましょう。

    bad

    it { expect(assigns('message')).to match /it was born in Belville/ }
    

    good

    subject { assigns('message') }
    it { should match /it was born in Billville/ }
    

    RSpecでは名前付きのsubjectも使えます。

    Good

    subject(:hero) { Hero.first }
    it "carries a sword" do
      expect(hero.equipment).to include "sword"
    end
    

    rspec subjectに関してもっと学ぶ。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | Subjectを使う", url: "http://betterspecs.org/#subject" %>

    letとlet!を使う

    変数に値を入れる必要がある時はbeforeブロックの代わりにletを使いましょう。 letを使えば変数が初めて使用された時だけlazy loadしてspecテストが終わるまでキャッシュとして使えます。 このstackoverflowの答letに関しての詳しい説明が有ります。

    bad

    describe '#type_id' do
      before { @resource = FactoryBot.create :device }
      before { @type     = Type.find @resource.type_id }
    
      it 'sets the type_id field' do
        expect(@resource.type_id).to equal(@type.id)
      end
    end
    

    good

    describe '#type_id' do
      let(:resource) { FactoryBot.create :device }
      let(:type)     { Type.find resource.type_id }
    
      it 'sets the type_id field' do
        expect(resource.type_id).to equal(type.id)
      end
    end
    

    letを初期化に使うとspecをテストする時にlazy loadされます。

    good

    context 'when updates a not existing property value' do
      let(:properties) { { id: Settings.resource_id, value: 'on' } }
    
      def update
        resource.properties = properties
      end
    
      it 'raises a not found error' do
        expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
      end
    end
    

    ブロックが定義した時に変数を定義したい時はlet!を使いましょう。 これはDBのクエリーやスコープのテストで有用です。

    これはletの実体の説明です。

    good

    # this:
    let(:foo) { Foo.new }
    
    # is very nearly equivalent to this:
    def foo
      @foo ||= Foo.new
    end
    

    rspec letに関してもっと学ぶ

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | letとlet!を使う", url: "http://betterspecs.org/#let" %>

    Mockを使うか使わないか

    まだ議論中のものです。出来るだけ(過度に)mockを使わずに実際の振る舞いをテストしましょう。 実際のケースをテストするのはロジックを変更する際に有用です。

    good

    # simulate a not found resource
    context "when not found" do
      before { allow(Resource).to receive(:where).with(created_from: params[:id]).and_return(false) }
      it { should respond_with 404 }
    end
    

    mockはspecを早くしますが正しく使うのが難しいです。mockをうまく使うにはもっとmockを理解する必要があります。この書き込みをご覧ください。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | Mockを使うか使わないか", url: "http://betterspecs.org/#mock" %>

    必要なデータだけ作る

    中小規模のプロジェクトの経験しかないと、テストスイートは重くなりがちです。必要以上のデータをロードしないようにしましょう。多数のデータが必要だと考えるならば、何かが間違っている可能性があります。

    good

    describe "User"
      describe ".top" do
        before { FactoryBot.create_list(:user, 3) }
        it { expect(User.top(2)).to have(2).item }
      end
    end
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 必要なテータだけ作る", url: "http://betterspecs.org/#data" %>

    fixtureの代わりにfactoryを使う

    これは古い話ですが、話しておく価値があります。fixtureは使わないでください。なぜならfixtureは操作が難しいからです。代わりにfactoryを使いましょう。factoryを使うと新しいデータを生成する際のコードの量を減らせます。

    bad

    user = User.create(
      name: 'Genoveffa',
      surname: 'Piccolina',
      city: 'Billyville',
      birth: '17 Agoust 1982',
      active: true
    )
    

    good

    user = FactoryBot.create :user
    

    注意!この文書でのユニットテストはfixtureもfactoryも使っておりません。 複雑なfixtureやfactoryを作る時間を節約すると、ライブラリにロジックを追加する時間を稼げます。 この書き込みを読んでみましょう。

    Factory Botをもっと学ぶ。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | fixtureの代わりにfactoryを使う", url: "http://betterspecs.org/#factories" %>

    読みやすいmatcherを使う

    読みやすいmatcherを使いましょう。 rspec matcherを二回以上確認しましょう。

    bad

    lambda { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
    

    good

    expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
    

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 読みやすいmatcherを使う", url: "http://betterspecs.org/#matchers" %>

    Shared Examples

    テストを作るのは素晴らしいです。毎日少しづつ自信がつきます。が、結局色んな所にコードの重複が発生します。shared exampleを使ってテストをDRYしましょう。

    bad

    describe 'GET /devices' do
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri) { '/devices' }
    
      context 'when shows all resources' do
        let!(:not_owned) { FactoryBot.create factory }
    
        it 'shows all owned resources' do
          page.driver.get uri
          expect(page.status_code).to be(200)
          contains_owned_resource resource
          does_not_contain_resource not_owned
        end
      end
    
      describe '?start=:uri' do
        it 'shows the next page' do
          page.driver.get uri, start: resource.uri
          expect(page.status_code).to be(200)
          contains_resource resources.first
          expect(page).to_not have_content resource.id.to_s
        end
      end
    end
    

    good

    describe 'GET /devices' do
    
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri)       { '/devices' }
    
      it_behaves_like 'a listable resource'
      it_behaves_like 'a paginable resource'
      it_behaves_like 'a searchable resource'
      it_behaves_like 'a filterable list'
    end
    

    経験によると、shared exampleは大体コントローラで使われます。モデルはお互いかなり異なるため、(普通は)多くのロジックを共有しません。

    rspec shared examplesに関してもっと学ぶ。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | shared examples", url: "http://betterspecs.org/#sharedexamples" %>

    見えるものをテスト

    モデルとアプリの振る舞いを深くテストしましょう(結合テスト)。 コントローラをテストする為にいらない複雑さを入れないようにしましょう。

    私は自分のアプリケーションをテストする時、最初はコントローラをテストしていましたが、今は行いません。 今はRSpecとCapybaraの統合テストしか作りません。なぜかと言うとほんとに目に見えるものをテストするべきだと信じているし、コントローラをテストするのは不要な段階だと思っているからです。そのうちテスト達はモデルと統合テストにされ、shared examplesに纏まって、きれいで読みやすいテストを作るようになります。

    これは未だ Ruby コミュニティーで議論中の話で、両方の側その考えを支える良い根拠が有ります。コントローラのテストを支持する側は統合テストは遅くてすべての機能をカバー出来ないと主張します。

    両方とも違います。大体簡単にすべての機能をカバーできるし(でしょ?)Guardみたいな自動化ツールを利用して一ファイルだけテストするのも可能です。こうすれば大きな流れを逆らわない範囲内で必要なspecだけテストできます。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 見えるものをテスト", url: "http://betterspecs.org/#integration" %>

    shouldを使わない

    テストの説明を書くときにshould使わないようにしましょう。現在時制に第三者観点から書きましょう。 新しいexpectationを使うともっと良いです。

    bad

    it 'should not change timings' do
      consumption.occur_at.should == valid.occur_at
    end
    

    good

    it 'does not change timings' do
      expect(consumption.occur_at).to equal(valid.occur_at)
    end
    

    shouldを使わないように制限できるshould_notをご覧ください。 そして既に作成されたrspecから"should."で始まる例文を消してくれるthe should_cleanもご覧ください。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | shouldを使わない", url: "http://betterspecs.org/#should" %>

    guardを使ったテスト自動化

    変更がある度に全テストをまわすのは厄介です。それは時間もかかりますし、流れをわずわらせます。Guardを使うと更新された使用、モデル、コントローラ、ファイルだけに対してテストをまわせるように自動化できます。

    good

    bundle exec guard
    
    この例は基本的なリロードルールがあるGuardfileです。

    good

    guard 'rspec', cli: '--drb --format Fuubar --color', version: 2 do
      # すべての更新されたspecを実行
      watch(%r{^spec/.+_spec\.rb$})
      # lib/フォルダーの中のファイルが変更された時libのspecを実行
      watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
      # モデルが変更された時関連モデルのspecを実行
      watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
      # ビューが変更された時関連ビューのspecを実行
      watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
      # コントローラが変更された時コントローラと関連ある統合テストのspecを実行
      watch(%r{^app/controllers/(.+)\.rb}) { |m| "spec/requests/#{m[1]}_spec.rb" }
      # application_controllerが変更された時すべての統合テストのspecを実行
      watch('app/controllers/application_controller.rb') { "spec/requests" }
    end
    

    Guardは良いツールですが、求めるすべてを満足させません。環境によってはTDDで働きながらキー設定で自分が望むタイミングで実行するのが良い時もあります。そしてpushの前だけ全体テストをまわしましょう。ここに参考になるvimの設定があります。

    guard-rspec

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | guardを使ったテスト自動化", url: "http://betterspecs.org/#guard" %>

    sporkを使ったテスト高速化

    Railsでテストを実行するには全体Railsのロードが必要です。これは時間がかかり仕事のじゃまになり得ます。 ZeusSpinSporkなどを利用すればこの問題は解決できます。Sporkは(普通)変更されないコントローラ、モデル、ビュー、 factoryと変更が頻繁なほぼすべてのコードをプリロードします。

    ここにSpork用のspec helperGuardfile設定があります。この設定を使用すればプリロードされるファイル(initializerとか)が更新されたときのみアプリ全体をリロードして、単一テストをとても早く実行できます。

    Sporkを使う際の欠点は、コードに積極的にモンキーパッチをあてているため、ファイルがリロードされない原因を理解するのに時間を無駄使いする可能性があることです。 もしSpinもしくは他の解決策があったら教えてください。.

    Zeusを使うためのGuardfile設定ファイルはここに有ります。 spec_helperを修正する必要はありませんが、テストを実行する前にコンソールで`zeus start`をする必要があります。

    全てに置いてZeusはSporkより比較的に安全な方法を使います。短所は使用要件がかなり厳しいことです。 Ruby 1.9.3以上(backported GCが使えるRuby 2.0以上を推奨) FSEventsもしくはinotifyが使えるOSを必要とします。

    多くの人達がSporkから他の解決策に移しました。この解決策達はもっと良い設計でもっと良い答えが出せますし、意識的に必要なものだけロードするようにしています。 詳しいことは関連議論をご覧ください。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | Railsをロードしといてテストを早くする", url: "http://betterspecs.org/#spork" %>

    HTTP requestをmockする

    テストする時、外部サービスに依存する必要がある場合がまれにあります。こんな場合実際サービスに依存させなくwebmockなどを使って stubするのが望ましいです。

    good

    context "with unauthorized access" do
      let(:uri) { 'http://api.lelylan.com/types' }
      before    { stub_request(:get, uri).to_return(status: 401, body: fixture('401.json')) }
      it "gets a not authorized notification" do
        page.driver.get uri
        expect(page).to have_content 'Access denied'
      end
    end
    

    webmockVCRに関してもっと学ぶ。 二つを一緒に使う方法を説明した 良いプレゼン もあります。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | HTTP requestをmockする", url: "http://betterspecs.org/#http" %>

    有用なformatter

    formatterを使うとテストに関する有用な情報を得られます。個人的にはfuubarが大変良かったです。実行するにはgemを設置しGuardfileにfuubarを基本formatterとして設定する必要があります。

    good

    # Gemfile
    group :development, :test do
      gem 'fuubar'
    

    good

    # Guardfile
    guard 'rspec' do
      # ...
    end
    

    good

    # .rspec
    --drb
    --format Fuubar
    --color
    

    fuubarに関してもっと学ぶ。

    このガイドに関して議論する →

    <%= render "partials/share", text: "betterspecs.org | 有用なformatter", url: "http://betterspecs.org/#formatter" %>

    Books

    <%= render "partials/books" %>

    Presentations

    RSpec 2 Best practices from Andrea Reginato

    Resources on the web

    1. Everyday Rails Spec
    2. Eggs on Bread Best Practices
    3. The Carbon Emitter Best Practices
    4. Andy Vanasse Best Practices
    5. Bitfluxx Best Practices
    6. Dmytro Shteflyuk Best Practices
    7. thoughtbot's RSpec Related Reading

    Screencasts

    1. RSpec on PeepCode
    2. Testing With RSpec Code School course
    3. Spork Railscast
    4. Using Zeus to speed up your tests
    5. Code TV Screencast on Guard and Spork
    6. Many of Destroy All Software screencasts

    Libraries (documentation)

    1. RSpec Documentation
    2. Capybara Documentation
    3. Factory Bot Documentation
    4. Webmock Documentation
    5. Timecop Documentation
    6. Shoulda Matchers
    7. Fuubar Release

    Styleguide

    We are seeking for the best guidelines to write "nice to read" specs. Right now a good starting point is for sure the Mongoid test suite. It uses a clean style and easy-to-read specs, following most of the guidelines described here.

    Improving Better Specs

    This is an open source project. If something is missing or incorrect just file an issue to discuss the topic. Also check the following issues:

    Credits

    The document was started by Andrea Reginato. A special thanks to the Lelylan Team. This document is licensed under MIT License.

    Help us

    If you have found those tips useful to improve your daily job think about making a $9 donation. Any donations will be used to make this site a more complete reference for better testing in Ruby.






    guard-rspecに関してもっと学ぶ。