rspecの基本的な使い方 (ruby/sinatraを含む)
[履歴] [最終更新] (2014/10/06 20:18:18)
最近の投稿
注目の記事

概要

他の言語と同様にrubyにも様々なテストツールがありますが、その中でも特にrspecというテストツールについて使用方法を記載します。rspecのバージョンは3.1を想定しています。本ページの内容を越えるものは、以下の公式ページをご参照ください。

インストール

$ gem install rspec -v 3.1
$ gem list | grep rspec
rspec (3.1.0)
rspec-core (3.1.5)
rspec-expectations (3.1.2)
rspec-mocks (3.1.2)
rspec-support (3.1.1)

初期設定

GEMパッケージをインストールするとrspecコマンドが使用できるようになります。rspecコマンドを使用して、設定ファイルなどを自動生成しましょう。

$ rspec --init
  create   .rspec
  create   spec/spec_helper.rb

.rspec

rspecコマンドを実行する際に自動で付与される引数を記述できるファイルです。

--color
--format documentation
--require spec_helper

などとしておくとよいです。それぞれの意味については

$ rspec --help

を参照してください。

spec/spec_helper.rb

テスト時に自動で読み込まれるファイルです。各テストにおいて共通の処理があればこのファイルに記述します。

テストコードの例

sample.rb

class MyStack
  def initialize
    @arr = []
  end

  def push(val)
    raise if val.nil?
    @arr.push(val)
    return val
  end

  def pop
    @arr.pop
  end

  def size
    @arr.size
  end
end

spec/sample_spec.rb

require './sample'

RSpec.describe MyStack do

  subject { @stack }  # ← subject (和訳:テストされる者)
  before(:example) do   # ← それぞれの 'example' (itブロック) の前で実行される
    @stack = MyStack.new  # (「before(:each) do」としても同じ意味。エイリアス)
  end

  describe "#push" do
    context "in 正常系" do
      it { expect(subject.push(123)).to eq 123 }
      it { expect(subject.push('value1')).to eq 'value1' }
      it { expect(subject.push(false)).to eq false }
      it { expect(subject.push([])).to eq [] }
      it { expect(subject.push([])).to be_empty } # としても同じ
    end
    context "in 異常系" do
      it { expect { subject.push(nil) }.to raise_error }
    end
  end

  describe "#pop" do
    context "in 正常系" do
      context "スタックが空の場合" do
        it { expect(subject.pop).to be_nil }
      end
      context "スタックに値がある場合" do
        before do
          subject.push 'value1'
          subject.push 'value2'
        end
        it { expect(subject.pop).to eq 'value2' }
        it {
          subject.pop
          expect(subject.pop).to eq('value1')
        }
      end
    end
    context "in 異常系" do
      it { expect { subject.pop('arg') }.to raise_error ArgumentError }
    end
  end

  describe "#size" do
    context "in 正常系" do
      it { expect(subject.size).to eq 0 }
      it {
        subject.push('value')
        expect(subject.size).to eq(1)
      }
    end
    context "in 異常系" do
      it { expect { subject.size('arg') }.to raise_error ArgumentError }
    end
  end
end

実行方法

$ rspec   ← spec/* のすべてのテストを実行
(or $ rspec spec/sample_spec.rb)   ← 特定のspecファイルのみを実行

実行結果例

MyStack
  #push
    in 正常系
      should eq 123
      should eq "value1"
      should eq false
      should eq []
      should be empty
    in 異常系
      should raise Exception
  #pop
    in 正常系
      スタックが空の場合
        should be nil
      スタックに値がある場合
        should eq "value2"
        should eq "value1"
    in 異常系
      should raise ArgumentError
  #size
    in 正常系
      should eq 0
      should eq 1
    in 異常系
      should raise ArgumentError

Finished in 0.006 seconds (files took 0.24701 seconds to load)
13 examples, 0 failures

Mockとは何か

"Mock" という英単語には "物まね" や "偽物の" といった意味があります。rspecにおけるMockという仕組みを使用すると、あたかもあるクラスのオブジェクトであるかのような「もの」を用意できます。その「もの」に未実装のクラスの物まねを覚えさせることによって、未実装のクラスの存在を前提としたテストコードが記述できます。これによって、ある未実装のクラスに依存した実装済みのクラスのテストを、未実装のクラスの実装を待たずに行うといったことが可能になります。ここで記載する内容を越えるものは公式ページをご参照ください。

用語

rspecのMockという仕組みの中では以下のような用語が登場します。

  • double : この英単語には実は「代役、影武者」といった意味があります。物まねを覚える「もの」です。何らかのクラスの代役を務めます
  • stub : 仕様書に記載された名前と型を持つ。実際には何もしない関数など。rspec v3.1 においては以前ほど明示的には登場せず、ここに記載のあるように Old syntax 扱いのものが存在します

テストコードの例

全くの未実装の場合

mymock1_spec.rb

RSpec.describe "Unimplemented Class" do

  subject { @unimplemented }
  before(:example) do
    @unimplemented = double("unimplemented")
  end

  describe "#unimplemented_method" do
    it {
      allow(subject).to receive(:unimplemented_method).and_return('value')
      expect(subject.unimplemented_method).to eq('value')
    }
  end
end

実行例

$ rspec spec/mymock1_spec.rb

Unimplemented Class
  #unimplemented_method
    should eq "value"

Finished in 0.012 seconds (files took 0.24701 seconds to load)
1 example, 0 failures

一部実装済みの場合

mymock2_spec.rb

class User
  def initialize(name)
    @name = name
  end
  attr_accessor :name
end

RSpec.describe User do
  subject { @user }
  before(:example) do
    @user = User.new('sample name')
  end

  describe "#unimplemented_method" do
    it {
      allow(subject).to receive(:unimplemented_method).and_return('value')
      expect(subject.unimplemented_method).to eq('value')
    }
  end
end

実行例

$ rspec spec/mymock2_spec.rb

User
  #unimplemented_method
    should eq "value"

Finished in 0.012 seconds (files took 0.23501 seconds to load)
1 example, 0 failures

(参考) Sinatraでrspecを使用する場合

rubyの軽量WebフレームワークであるSinatraにおいて、その本体アプリケーションファイルをrspecでテストする場合に必要となる事項について記載します。

初期設定

Sinatraのための初期設定として "Rack::Test::Methods" のincludeが追加で必要になります。この処理は複数のテストで必要になることが多いため、共通の処理として "spec/spec_helper.rb" に記述しておきましょう。

spec/spec_helper.rb

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

テストコード

「Hello World」と表示するだけのsinatraの本体アプリケーション "helloworld.rb" があるとします。これをテストするためには、ファイル "spec/helloworld_spec.rb" を作成し内容を以下のようにします。

spec/helloworld_spec.rb

ENV['RACK_ENV'] = 'production'

# Sinatra本体アプリケーションを読み込む
require './helloworld'

describe 'HelloWorld Application' do

  # Rack::Testのために必要な設定
  def app
    Sinatra::Application
  end

  it "トップページにアクセスすると 'Hello World' と表示される" do
    get '/'
    expect(last_response).to be_ok
    expect(last_response.body).to eq('Hello World')
  end
end

HTTPメソッド

get, post, put, delete, head が使用できます。各メソッドには引数が指定できます。例えばgetの場合は以下のようになります。

$ get '/path', params={}, rack_env={}
  • /path : リクエストパスです。いわゆるクエリストリングでパラメータを指定することもできます
  • params={} : postやgetで指定するパラメータをハッシュ形式またはクエリストリング形式で記述できます
  • rack_env={} : rackの環境変数をハッシュ形式で指定できます。指定できる項目はこちらです。「rack.session」などが指定できます

レスポンス

HTTPメソッドを実行したあとは

  • last_request : 実際に実行されたHTTPリクエスト
  • last_response : HTTPレスポンス

が使用できます。例えば

  • last_response.headers['Content-Length']
  • last_response.body

といった値をテストで利用できるようになります。

関連ページ
    概要 よく使う python ライブラリのサンプルコード集です。 JSON #!/usr/bin/python # -*- coding: utf-8 -*- import json arr = [1, 2, {'xxx': 3}] # オブジェクト ←→ JSON 文字列 jsonStr = json.dumps(arr) arr2 = json.loads(jsonStr) # オ