おはようございます。
うちだです。
みなさんテストコード書いてますか?
私はテストと言われると、どうもやる気がおこりません。
そこでBDD!
今回はBDD初心者の私が、やってみた過程を綴ります。ツッコミ大歓迎
BDDとは?
- Behavior Driven Development
- 振舞駆動開発
- テスト駆動開発ではテストのためにコードを書く
- 振舞駆動開発では振舞(仕様)のためにコードを書く
- 結果的にやる事はほぼ一緒
- 言い方でモチベーションがかなり変わる
- スペック!スペック!
各言語の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
課題
- viewのスペック
- ストーリーテスト
参考
- RSpec on Railsのとても参考になる記事
- よく分かるまとめ
- Rack-Test
HT-03Aを買ってAndroidアプリに夢中なうちだでした。
おしまい