Sinatra
RSpec

rspec + sinatra sample

More than 1 year has passed since last update.

簡単なsinatraアプリを例としたテスト駆動開発の紹介をします。今回作成するsinatraアプリの完成コードは以下です。

require 'sinatra'

get '/' do
  'Hello'
end

完成後のすべてのコードは、https://github.com/ppworks/rspec_sample にあります。

それでは、まずはレポジトリをcloneします。

git clone git://github.com/ppworks/rspec_sample.git

ディレクトリを移動して、必要なgemファイルをインストールします。

cd rspec_sample
bundle install --path .bundle

テストが通ることを確認します。

bundle exec rspec
App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること
      Helloと出力されること

Finished in 0.04865 seconds
2 examples, 0 failures

無事にテストが通りましたね。ではここまでに至る作業を見ていきます。各ステップはtagを作成してあるので以下のコマンドでtagが存在を確認して下さい。

git tag -n
tdd_001         テスト駆動開発例テストから書く
tdd_002         テスト駆動開発例テストに沿った実装を書く
tdd_003         テスト駆動開発例テストから書くその2
tdd_004         テスト駆動開発例テストに沿った実装を書くその2
tdd_005         specのリファクタリング

各tagに切り替えた際にコードを確認して見ることが大切です。実際にコードを見るようにして下さい。

テストから書く

まずは最初のtagをcheckoutします。

git checkout tdd_001

次にテストを実行します。

bundle exec rspec

以下のように、テストが失敗します。1 example, 1 failureは全部で1つのサンプルが存在し、1つが失敗したことを表しています。

App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること (FAILED - 1)

Failures:

  1) App レスポンスの精査 /へのアクセス 正常なレスポンスが返ること
     Failure/Error: last_response.should be_ok
       expected ok? to return true, got false
     # ./spec/app_spec.rb:14:in `block (4 levels) in <top (required)>'

Finished in 0.09747 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/app_spec.rb:13 # App レスポンスの精査 /へのアクセス 正常なレスポンスが返ること

このとき、app.rbを見てみると何も書いて有りませんね。このようにテスト駆動開発では、このように動いて欲しいという期待を先に記述します。ではこのテストコードを見てみましょう。git diff tdd_001..tdd_002の実行結果です。

spec/app.rb
# encoding: utf-8
require File.dirname(__FILE__) + '/spec_helper'

describe "App" do
  include Rack::Test::Methods
  def app
    @app ||= Sinatra::Application
  end

  describe "レスポンスの精査" do
    describe "/へのアクセス" do
      before { get '/' }
      it "正常なレスポンスが返ること" do
        last_response.should be_ok
      end
    end
  end
end

次はこのテストが期待するコードを実装していきます。

テストに沿った実装を書く

次のtagをcheckoutしましょう。

git checkout tdd_002

この状態でテストを実行すると

bundle exec rspec

今度は成功しましたね。

App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること

Finished in 0.10743 seconds
1 example, 0 failures

どのような処理を書いたのか見てみましょう。

diff --git a/app.rb b/app.rb
index e69de29..920ebe6 100644
--- a/app.rb
+++ b/app.rb
@@ -0,0 +1,4 @@
+require 'sinatra'
+
+get '/' do
+end

実にシンプルですね。このようにテスト駆動開発では、実装から書きたくなりがちなところをテストから書いていきます。

テストから書くその2

もう少しテストを足してみましょう。

git checkout tdd_003

今回足したテストは以下です。git diff tdd_002..tdd_003の実行結果です。

diff --git a/spec/app_spec.rb b/spec/app_spec.rb
index d849e1e..076f412 100644
--- a/spec/app_spec.rb
+++ b/spec/app_spec.rb
@@ -13,6 +13,9 @@ describe "App" do
       it "正常なレスポンスが返ること" do
         last_response.should be_ok
       end
+      it "Helloと出力されること" do
+        last_response.body.should == "Hello"
+      end
     end
   end
 end
bundle exec rspec
App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること
      Helloと出力されること (FAILED - 1)

Failures:

  1) App レスポンスの精査 /へのアクセス Helloと出力されること
     Failure/Error: last_response.body.should == "Hello"
       expected: "Hello"
            got: "" (using ==)
     # ./spec/app_spec.rb:17:in `block (4 levels) in <top (required)>'

Finished in 0.11694 seconds
2 examples, 1 failure

Failed examples:

rspec ./spec/app_spec.rb:16 # App レスポンスの精査 /へのアクセス Helloと出力されること

テストは通りませんね。では次はこのテストが通る実装をみてみましょう。

テストに沿った実装を書くその2

git checkout tdd_004

今回の差分は以下です。git diff tdd_003..tdd_004の実行結果です。"Hello"という文字列を返しています。

diff --git a/app.rb b/app.rb
index 920ebe6..0520e2e 100644
--- a/app.rb
+++ b/app.rb
@@ -1,4 +1,5 @@
 require 'sinatra'

 get '/' do
+  'Hello'
 end

ではテストを実行しています。

bundle exec rpec
App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること
      Helloと出力されること

Finished in 0.04242 seconds
2 examples, 0 failures

無事に通りましたね。次はテストコードのリファクタリングをしてみます。

specのリファクタリング

今回は振る舞いは同じまま、コードの修正を行なってみました。app.rbのコードが単純なためspec/app_specをリファクタリングしています。git diff tdd_004..tdd_005の実行結果です。

diff --git a/spec/app_spec.rb b/spec/app_spec.rb
index 076f412..1347ef9 100644
--- a/spec/app_spec.rb
+++ b/spec/app_spec.rb
@@ -10,11 +10,12 @@ describe "App" do
   describe "レスポンスの精査" do
     describe "/へのアクセス" do
       before { get '/' }
+      subject { last_response }
       it "正常なレスポンスが返ること" do
-        last_response.should be_ok
+        should be_ok
       end
       it "Helloと出力されること" do
-        last_response.body.should == "Hello"
+        subject.body.should == "Hello"
       end
     end
   end

現在テストしている主題(subject)を明確にしてみました。同じくテストを実行すると

bundle exec rpec
App
  レスポンスの精査
    /へのアクセス
      正常なレスポンスが返ること
      Helloと出力されること

Finished in 0.04653 seconds
2 examples, 0 failures

同じ結果が得られましたね。以上で、sinatraとrspecを使ったテスト駆動開発の体験が出来たと思います。どうでしたでしょうか?