unoh.github.com

やってみようBDD on Sinatra

Wed Jul 22 19:26:14 -0700 2009

おはようございます。
うちだです。

みなさんテストコード書いてますか?
私はテストと言われると、どうもやる気がおこりません。
そこでBDD!
今回はBDD初心者の私が、やってみた過程を綴ります。ツッコミ大歓迎

BDDとは?

各言語のBDDフレームワーク

10ステップでやってみるBDD(RSpec編)

1. インストール

 $ sudo gem install rspec

2. スペックを書く

 # spec/hello_spec.rb
 require 'hello'
 
 describe Hello do
   before do
     @hello = Hello.new
   end
 
   it "should equal 'Hello, RSpec'" do
     @hello.say('RSpec').should == 'Hello, RSpec'
   end
 end

3. Helloクラスを書く

 # hello.rb
 class Hello
   def say(name)
     "Hello #{name}"
   end
 end

4. 動かす

 $ spec -fs -c spec/hello_spec.rb
 
 Hello
 - should equal 'Hello, RSpec' (FAILED - 1)
 
 1)
 'Hello should equal 'Hello, RSpec'' FAILED
 expected: "Hello, RSpec",
      got: "Hello RSpec" (using ==)
 ./spec/hello_spec.rb:9:
 
 Finished in 0.002776 seconds
 
 1 example, 1 failure

5. 失敗した!
expectedが仕様でgotが結果です。
「,」が無いので修正します

 class Hello
   def say(name)
     "Hello, #{name}"
   end
 end

6. 動かす

 $ spec -fs -c spec/hello_spec.rb
 
 Hello
 - should equal 'Hello, RSpec'
 
 Finished in 0.002051 seconds
 
 1 example, 0 failures

成功!

7. スペックを追加
日本語にしたり、ネストしたりすると見やすくなりますね
引数が空文字だったら例外にしよう

 require 'hello'
 
 describe Hello do
   describe '#say' do
     before :all do
       @hello = Hello.new
     end
 
     it "戻り値は'Hello, RSpec'であるべき" do
       @hello.say('RSpec').should == 'Hello, RSpec'
     end
 
     it "引数が空だと例外が発生するべき" do
       lambda { @hello.say('') }.should raise_error
     end
   end
 end

8. 動かす

 $ spec -fs -c spec/hello_spec.rb
 
 Hello#say
 - 戻り値は'Hello, RSpec'であるべき
 - 引数が空だと例外が発生するべき (FAILED - 1)
 
 1)
 'Hello#say 引数が空だと例外が発生するべき' FAILED
 expected Exception but nothing was raised
 ./spec/hello_spec.rb:14:
 
 Finished in 0.003019 seconds
 
 2 examples, 1 failure

9. もちろん失敗。Helloクラスを修正

 class Hello
   def say(name)
     raise StandardError, 'name is empty' if name.empty?
     "Hello, #{name}"
   end
 end

10. 動かす

 $ spec -fs -c spec/hello_spec.rb
  
 Hello#say
 - 戻り値は'Hello, RSpec'であるべき
 - 引数が空だと例外が発生するべき
 
 Finished in 0.002542 seconds
 
 2 examples, 0 failures

完成!!

BDD on Sinatra

1. 必要な物

 sudo gem install sinatra
 sudo gem install rspec
 sudo gem install rack-test

2. helperを用意
rack-testに合わせてhelperを用意します

 # spec/spec_helper.rb
 require 'rack/test'
 
 module MyTestMethods
   def app
     Sinatra::Application
   end
 end
 
 Spec::Runner.configure do |config|
   config.include Rack::Test::Methods
   config.include MyTestMethods
 end

3. さっそくスペックを書いてみる
レスポンスをチェックしてみましょう
Rack::Test::Methods#(get|last_response)が使えます

 # spec/app_spec.rb
 require 'app'
 require 'spec/spec_helper'
 
 describe 'GET /' do
   before :all do
     get '/'
   end
 
   it "statusコードは200であるべき" do
     last_response.ok?.should be_true
   end
 
   it "responseは'Hello, World\\n'であるべき" do
     last_response.body.should == "Hello, World\n"
   end
 end

4. アプリケーションの実装

 # app.rb
 require 'rubygems'
 require 'sinatra'
 
 get '/' do
   "Hello, World\n"
 end

5. 動かす。そしてOK

 $ spec -fs -c spec/app_spec.rb
 
 GET /
 - statusコードは200であるべき
 - responseは'Hello, World\n'であるべき
 
 Finished in 0.021507 seconds
 
 2 examples, 0 failures

6. 実行が面倒なのでRakefileを書く

 # Rakefile
 require 'rubygems'
 require 'spec/rake/spectask'
 
 task :default => :spec
 
 desc "Run all specs in spec directory"
 Spec::Rake::SpecTask.new(:spec) do |t|
   t.spec_opts = ['--format specdoc', '--color']
   t.spec_files = FileList['spec/**/*_spec.rb']
 end
 $ rake

7. コントローラのスペック
コントローラの役割は

参考
Rubyist Magazine – スはスペックのス 【第 2 回】 RSpec on Rails

しかし、このままではRSpec on Railsのようにコントローラの状態を知ることができないので、少々Sinatraのコードを変更します。
何か良い手段はないものか。。

 # spec/spec_helper.rb
 require 'singleton'
 class SinatraSpecHelper
   include Singleton
   attr_accessor :last_app
 end
 
 module Sinatra
   class Base
     def call(env)
       _dup = dup
       SinatraSpecHelper.instance.last_app = _dup
       _dup.call!(env)
     end
 
     def assigns(sym)
       instance_variables.include?("@#{sym}")
     end
   end
 end
 
 module MyTestMethods
   def app
     Sinatra::Application
   end
 
   def last_app
     SinatraSpecHelper.instance.last_app
   end
 end
 ...
 

これで#last_appで実行されたインンスタンスを取得できます、また、#assignsでインスタンス変数のチェックができます

 # spec/app_spec.rb
 it "@msgをもつべき" do
   last_app.assigns(:msg).should be_true
 end
 
 it "viewはindex.erbをつかうこと" do
   last_response.body.should == last_app.erb(:index)
 end

8. rake。失敗。実装。

 # app.rb
 require 'rubygems'
 require 'sinatra'
 
 get '/' do
   @msg = "Hello, World"
   erb :index
 end
 # views/index.erb
 <%= @msg %>

9. rake。成功!

 GET /
 - statusコードは200であるべき
 - responseは'Hello, World\n'であるべき
 - @msgをもつべき
 - viewはindex.erbをつかうこと
 
 Finished in 0.015934 seconds
 
 4 examples, 0 failures

10. viewのスペック

RSpec on Railsでは#have_tagのような仕組みが提供されています。
これをそのまま使えないだろうか。
調査中

11. Extensions(拡張)のスペック

まずは拡張をロードするアプリケーションを用意します。
これは通常のSinatraアプリのようにTOPレベルに書いても良いのですが、ここではmockを作り、そこでロードしましょう
全コードを載せます

# lib/hello.rb
module Sinatra
  module Hello
    module Helpers
      def hello(name)
        "#{options.prefix}#{name}"
      end
    end
    def self.registered(app)
      app.helpers Hello::Helpers
      app.set :prefix, "Hello, "
    end
  end
  register Hello
end
# spec/hello_spec
require 'rubygems'
require 'sinatra'
require 'lib/hello'
require 'rack/test'

module MyTestMethods
  def app
    @app
  end
  def mock_app(base=Sinatra::Base, &block)
    @app = Sinatra.new(base, &block)
  end
end

Spec::Runner.configure do |config|
  config.include Rack::Test::Methods
  config.include MyTestMethods
end

describe Sinatra::Hello do
  it '「Hello, Unoh」であるべき' do
    mock_app {
      register Sinatra::Hello
      get('/') { hello('Unoh') }
    }
    get '/'
    last_response.body.should == 'Hello, Unoh'
  end

  it '「Good morning, Unoh」であるべき' do
    mock_app {
      register Sinatra::Hello
      get('/') { hello('Unoh') }
      configure { set :prefix, "Good morning, " }
    }
    get '/'
    last_response.body.should == 'Good morning, Unoh'
  end
end
$ spec -fs -c spec/hello_spec.rb

Sinatra::Hello
- 「Hello, Unoh」であるべき
- 「Good morning, Unoh」であるべき

Finished in 0.046408 seconds

2 examples, 0 failures

課題

参考

HT-03Aを買ってAndroidアプリに夢中なうちだでした。
おしまい