2010-02-28
RSpec の入門とその一歩先へ
event, ruby, rspec, tokyorubykaigi03, TDD, BDD |
![]()
東京 Ruby 会議 03 の RSpec ワークショップの資料です。このワークショップでは参加者の方に「写経」(コードを書き写すこと)をして貰い、TDD/BDD と RSpec を同時に学べるように都度説明を入れるかたちで行いました。
第2イテレーションも書きました。続きに興味ある方はご覧下さい
(更新) 第3イテレーションも書きました。続きに興味ある方はご覧下さい
1st iteration
favotter の <censored> みたいな NG ワードのフィルタリング機能を RSpec で作りましょう。まずは NG ワードの検出機能を作成します。
このイテレーションでは最初ベタな形のテストコードと実装を書き、だんだんとそのコードを洗練させてゆきます。
message_filter_spec.rb を作成
まずはこれから育てていく spec ファイルを作成します。
なお、このエントリのコードは diff 風の記法で書かれています。 "+" が追加された行、 "-" が削除された行です。
message_filter_spec.rb
+require 'rubygems' +require 'spec' + +describe MessageFilter do +end
git に登録
git がある人は、これから書くコードを git で管理しましょう。各ステップ毎に commit を行いつつ進めましょう。
(私は emacs から git を使うので明示的にコマンドラインでコミットを行っていませんが、このエントリの所々に出てくる sha1 は私が資料作成した時の sha1 です)
$ ls message_filter_spec.rb $ git init Initialized empty Git repository in /home/takuto/work/git-sandbox/event/tork03/.git/ $ git add . $ git commit -m 'initial' Created initial commit 2bc2345: initial 1 files changed, 5 insertions(+), 0 deletions(-) create mode 100644 message_filter_spec.rb $ git checkout -b 1st Switched to a new branch "1st"
実行してみましょう
$ spec message_filter_spec.rb ./message_filter_spec.rb:4: uninitialized constant MessageFilter (NameError) from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load_files' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `each' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `load_files' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/options.rb:133:in `run_examples' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/lib/spec/runner/command_line.rb:9:in `run' from /home/takuto/app/gems/1.8/gems/rspec-1.3.0/bin/spec:5 from /home/takuto/app/gems/1.8/bin/spec:19:in `load' from /home/takuto/app/gems/1.8/bin/spec:19 $
RSpec が泣いているので、 message_filter.rb を作成しましょう
commit 54876c311b65dee84e530aec8add4212ce0e5f85
message_filter.rb
+class MessageFilter +end
message_filter_spec.rb
require 'rubygems' require 'spec' +require 'message_filter' describe MessageFilter do end
実行してみましょう
$ spec message_filter_spec.rb Finished in 0.003614 seconds 0 examples, 0 failures $
最初のテスト (code example) を書きましょう
commit d67ccc04a2991b14dae66e4cecab9de5b0dc351c
非常にベタな書き方ですが、最初のテスト (code example) を書きます。このイテレーションの後半で、テストの書き方も洗練させていきます。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do + it 'should detect message with NG word' do + filter = MessageFilter.new('foo') + filter.detect?('hello from foo').should == true + end end
実行してみましょう
$ spec message_filter_spec.rb F 1) ArgumentError in 'MessageFilter should detect message with NG word' wrong number of arguments (1 for 0) ./message_filter_spec.rb:7:in `initialize' ./message_filter_spec.rb:7:in `new' ./message_filter_spec.rb:7: Finished in 0.003438 seconds 1 example, 1 failure $
コンストラクタの引数の数が不正であると言われました。それはそうですね、コンストラクタを作成します。
コンストラクタ作成
commit 5a8d1dc52e190d6e50c5df9d56818611d8b08c44
message_filter.rb
class MessageFilter + def initialize(word) + @word = word + end end
実行してみましょう
$ spec message_filter_spec.rb F 1) NoMethodError in 'MessageFilter should detect message with NG word' undefined method `detect?' for #<MessageFilter:0xb7ab05d8 @word="foo"> ./message_filter_spec.rb:8: Finished in 0.003369 seconds 1 example, 1 failure $
テストはまだ落ちています。 detect? というメソッドが無いよと言われました。無いですね。作りましょう。
detect? メソッド作成
commit 57e84d6a9f26a45c3cec25a520100e25e3d119b8
空で良いので、メソッドを作成します。
message_filter.rb
class MessageFilter def initialize(word) @word = word end + def detect?(text) + end end
実行してみましょう
$ spec message_filter_spec.rb
F
1)
'MessageFilter should detect message with NG word' FAILED
expected: true,
got: nil (using ==)
./message_filter_spec.rb:8:
Finished in 0.003675 seconds
1 example, 1 failure
$
true が返ってきてほしいところに nil が返ってきました。メソッドの中身が空だからですね。ではこのテストを通すもっとも簡単な実装はどうなるでしょうか。ここに TDD のトリックがあります。それが、「仮実装 (fake it)」です。
仮実装 (fake it)
commit e53b370ee40947870c152e4dd43bbc6ee4b4e252
先ほどのテストを通すためのもっとも単純な実装はどうなるでしょうか? 書いてみましょう。
message_filter.rb
class MessageFilter @word = word end def detect?(text) + true end end
こ れ は ひ ど い !! しかし、これが TDD の「仮実装」です。
実行してみましょう
$ spec message_filter_spec.rb . Finished in 0.002809 seconds 1 example, 0 failures $
メソッドから true を返すベタな実装を行ったのでテストが通るのは当たり前ですね。こんな行為に何か意味があるのでしょうか?
仮実装とは、テストのテスト、と考えることが出来ます。例えば今回の例で、 true を返すという絶対テストが通るだろうという実装コードを書いても、テストが失敗したらどうでしょうか? それは、テストコードの方にこそバグが潜んでいることを示唆しています。仮実装で成功しないテストは、本実装でも成功しないでしょう。本実装でもテストが通らなかったときに、なぜテストが通らないのか本実装を長い時間デバッグした結果、テストコードが間違っていたのでは目も当てられません。
三角測量
commit c2e9fc3aba93ab49e6138b7402c4772cb0a08674
しかし、このままでは実装はいつまでも安易過ぎるものになってしまうので、別のデータを使ったテストを足しましょう。これを TDD では「三角測量(triangulation)」といいます。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do it 'should detect message with NG word' do filter = MessageFilter.new('foo') filter.detect?('hello from foo').should == true end + it 'should not detect message without NG word' do + filter = MessageFilter.new('foo') + filter.detect?('hello, world!').should == false + end end
実行してみましょう
$ spec message_filter_spec.rb
.F
1)
'MessageFilter should not detect message without NG word' FAILED
expected: false,
got: true (using ==)
./message_filter_spec.rb:12:
Finished in 0.00399 seconds
2 examples, 1 failure
$
「仮実装」で書いたコードはあっという間に破綻しました。そろそろきちんと実装しないといけないですね。
明白な実装
commit ecd31f317c758aad94b6dae6d2bf1453e067c758
message_filter.rb
class MessageFilter def initialize(word) @word = word end def detect?(text) - true + text.include?(@word) end end
実行してみましょう
$ spec message_filter_spec.rb .. Finished in 0.002986 seconds 2 examples, 0 failures $
実装が見えているときは、三角測量を介さずにそのまま仮実装を変更して実装してもかまいません。これを「明白な実装 (obvious implementation)」といいます。
大事なのは自分の不安をコントロールすることです。 TDD では、テストと実装両方に自信がある時は「明白な実装」、一歩一歩進めたい、つまりテストをテストして、そのあとで実装をテストしたい時は「仮実装」と「三角測量」の組み合わせを使います。
タグを打つ
きりのいいところまできたので、一度タグを打ちます。
$ git tag -a -m '1st iteration spec refactoring point' iter1_spec_refactoring $ git tag iter1_spec_refactoring $
テストのリファクタリング
さて、テストが全部通っているので、実装クラスのリファクタリングを行いましょう。リファクタリングとは、コードに重複があったり、無駄がある場合に、テストが通ったままで実装を綺麗にしていくことです。実装にコードの重複や無駄はあるでしょうか? 現時点ではリファクタリングの余地がないほどシンプルですね。ではテストコードはどうでしょうか? …かなり重複が見られますね。テストを書いたすぐ後のタイミングで、テストコードの重複も積極的に排除していこう、というのが最近の考え方です。テストの「リファクタリング」というと厳密にはもっと難しく、タイミングが遅れるほど困難なものですが、テスト実装直後では自分の頭にもテスト設計が残っているでしょうし、このタイミングでは大胆に行動できます。では重複を排除していきましょう。
before メソッドの抽出
commit 4a3a7c895965e7f719a7b7b7a07bf8cd23fe1b2b
before メソッドを作成し、filter のインスタンス作成部分をそこに移動します。 before とは、 xUnit でいうと setUp に相当します。before メソッドは各テストの実行前に毎回実行されますので、重複部を before に書くことでテストコードの重複を排除することができます。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do + before(:each) do + @filter = MessageFilter.new('foo') + end it 'should detect message with NG word' do - filter = MessageFilter.new('foo') - filter.detect?('hello from foo').should == true + @filter.detect?('hello from foo').should == true end it 'should not detect message without NG word' do - filter = MessageFilter.new('foo') - filter.detect?('hello, world!').should == false + @filter.detect?('hello, world!').should == false end end
実行してみましょう
$ spec message_filter_spec.rb .. Finished in 0.003297 seconds 2 examples, 0 failures $
大丈夫そうですね、本当でしょうか?
(テストのリファクタリング、 mutation testing について、あとでかく?)
:each は不要
commit 2e71d40efc324671f18bc7940d9e9696507b2384
ちなみに、 :each はデフォルト扱いなので、明示的に書く必要はありません。(before(:all) について、あとで書く?)
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do - before(:each) do + before do @filter = MessageFilter.new('foo') end it 'should detect message with NG word' do @filter.detect?('hello from foo').should == true end it 'should not detect message without NG word' do @filter.detect?('hello, world!').should == false end end
実行してみましょう
$ spec message_filter_spec.rb .. Finished in 0.003297 seconds 2 examples, 0 failures $
be_[predicate] マッチャー
commit fac3a62ae1a25a3abe33514383156f9673485e5e
"XXX?.should == true" や "XXX.should be_true" という記述は、 be_[predicate] マッチャーという書き方に変換することができます。こういう書き方をすることでドキュメントとしてのテストコードの意味を高め、かつ記述自体も簡潔にすることが出来ます。
"hoge.fuga?.should == true" は "hoge.should be_fuga" と書き直すことができます。 be_fuga というメソッドは当然存在しませんが、 RSpec が method_missing を使い、テスト用のメソッドであると解釈してくれます。これもメタプログラミングの例と言うことも出来ます。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do before do @filter = MessageFilter.new('foo') end it 'should detect message with NG word' do - @filter.detect?('hello from foo').should == true + @filter.should be_detect('hello from foo') end it 'should not detect message without NG word' do - @filter.detect?('hello, world!').should == false + @filter.should_not be_detect('hello, world!') end end
実行してみましょう
$ spec message_filter_spec.rb .. Finished in 0.00763 seconds 2 examples, 0 failures $
-fs オプション
さて、ここ以降は RSpec のドキュメント出力機能にも着目します。
spec コマンドの実行時に -fs というオプションをつけると、仕様記述を出力することが出来ます。現状ではどうなっているでしょうか。
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter - should detect message with NG word - should not detect message without NG word Finished in 0.003184 seconds 2 examples, 0 failures $
RSpec に仕様記述を組み立てさせる
まだ重複しているのは、 it の引数とブロックの中身です。文字列で書かれた内容と、ブロック内のコード自身がかなり重複していますね。 RSpec は it の説明用の文字列引数を省略した時に自分で判断できる範囲で仕様記述を組み立てます。 it の文字列引数を削除してしまいましょう。(日本語の使用についてあとで書く)
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter do before do @filter = MessageFilter.new('foo') end - it 'should detect message with NG word' do + it { @filter.should be_detect('hello from foo') - end + } - it 'should not detect message without NG word' do + it { @filter.should_not be_detect('hello, world!') - end + } end
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter - should be detect "hello from foo" - should not be detect "hello, world!" Finished in 0.003087 seconds 2 examples, 0 failures $
"should be detect" では英語的に微妙ですが、ここでは目をつぶります。どうでしょう。案外読めるのではないでしょうか?
違和感がある場合、それは設計がまだ不完全であることを示唆していると考えることができますし、カスタムマッチャーへの道を進むこともできます。
ここで論じているようなポイントも RSpec とのうまい付き合いかた、といえます。
describe にコンテクスト情報を追加する
commit f3c46f7030a77ccd6b137d858a50a021775ccbef
先ほどの出力ですが、ちょっと情報が足りないですね
MessageFilter - should be detect "hello from foo"
「MessageFilter should be detect "hello from foo"」と読めますが、全ての MessageFilter がこう振る舞うわけではないですよね、状況の説明が足りていません。状況説明をテストコードに加えましょう。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' -describe MessageFilter do +describe MessageFilter, 'with argument "foo"' do before do @filter = MessageFilter.new('foo') end it { @filter.should be_detect('hello from foo') } it { @filter.should_not be_detect('hello, world!') } end
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter with argument "foo" - should be detect "hello from foo" - should not be detect "hello, world!" Finished in 0.00303 seconds 2 examples, 0 failures $
ドキュメントを文字列で書かずとも、情報量がかなり保たれるようになってきましたね。これも RSpec Way です。
まだまだ重複がある!
@filter も重複していませんか? これも取り去ることができます。RSpec の subject という機能を使います。subject を使うと、 subject ブロックの評価結果が it 内の should のレシーバになります。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter, 'with argument "foo"' do before do @filter = MessageFilter.new('foo') end + subject { @filter } it { - @filter.should be_detect('hello from foo') + should be_detect('hello from foo') } it { - @filter.should_not be_detect('hello, world!') + should_not be_detect('hello, world!') } end
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter with argument "foo" - should be detect "hello from foo" - should not be detect "hello, world!" Finished in 0.003051 seconds 2 examples, 0 failures $
まだまだまだ重複がある!!
commit 8cdf08ca0052a43780458522248e3746b60c9b7b
今回の例では before ブロックはほとんど仕事していないですね、 subject ブロックの中にインライン化してしまいましょう
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter, 'with argument "foo"' do - before do - @filter = MessageFilter.new('foo') - end - subject { @filter } + subject { MessageFilter.new('foo') } it { should be_detect('hello from foo') } it { should_not be_detect('hello, world!') } end
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter with argument "foo" - should be detect "hello from foo" - should not be detect "hello, world!" Finished in 0.003051 seconds 2 examples, 0 failures $
簡潔さは力
かなりテストコードが簡潔になってました。コードをすっきりさせたいので it の改行を廃し、最終的にテストコードはこういう形になりました。重複が少なく、かつ情報量自体はかなり保たれています。
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter, 'with argument "foo"' do subject { MessageFilter.new('foo') } it { should be_detect('hello from foo') } it { should_not be_detect('hello, world!') } end
実行してみましょう
$ spec -fs message_filter_spec.rb MessageFilter with argument "foo" - should be detect "hello from foo" - should not be detect "hello, world!" Finished in 0.003051 seconds 2 examples, 0 failures $
第一イテレーション終了!
さて、このイテレーションでは最終的にコードは以下のようになりました。
message_filter.rb
class MessageFilter def initialize(word) @word = word end def detect?(text) text.include?(@word) end end
message_filter_spec.rb
require 'rubygems' require 'spec' require 'message_filter' describe MessageFilter, 'with argument "foo"' do subject { MessageFilter.new('foo') } it { should be_detect('hello from foo') } it { should_not be_detect('hello, world!') } end
タグを打ってイテレーションの終了としましょう。
$ git tag -a -m 'end of 1st iteration' end_of_iter1 $
この後のログ
未完なのでとりあえず git log を貼っておきます。(長いので別エントリにしたほうがよさそうですね)
(更新) 第2イテレーションも書きました。続きに興味ある方はご覧下さい
commit f004a89c996c962d87a70aed407cd476574bddc5
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:13:17 2010 +0900
add example group for MessageFilter with argument "foo","bar"
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 23a90ca..bc1c7b7 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -7,3 +7,8 @@ describe MessageFilter, 'with argument "foo"' do
it { should be_detect('hello from foo') }
it { should_not be_detect('hello, world!') }
end
+
+describe MessageFilter, 'with argument "foo","bar"' do
+ subject { MessageFilter.new('foo', 'bar') }
+ it { should be_detect('hello from bar') }
+end
commit c2ee1bd03d4ad2be03537fe0b71e670db75046e0
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:19:36 2010 +0900
naive implementation using varargs
diff --git a/message_filter.rb b/message_filter.rb
index 1f2c0a1..d032457 100644
--- a/message_filter.rb
+++ b/message_filter.rb
@@ -1,8 +1,11 @@
class MessageFilter
- def initialize(word)
- @word = word
+ def initialize(*words)
+ @words = words
end
def detect?(text)
- text.include?(@word)
+ @words.each do |w|
+ return true if text.include?(w)
+ end
+ false
end
end
commit c33b54b72507989cbf323bfd433aa2e8eb7410b7
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:23:25 2010 +0900
MessageFilter with argument "foo","bar" should satisfy specs for 'foo' only.
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index bc1c7b7..3c1e80c 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,4 +11,6 @@ end
describe MessageFilter, 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
it { should be_detect('hello from bar') }
+ it { should be_detect('hello from foo') }
+ it { should_not be_detect('hello, world!') }
end
commit 18c8e8ae5a037c156632b45db5263015ee668a07
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:26:34 2010 +0900
share_examples_for 'MessageFilter with argument "foo"'
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 3c1e80c..91415d9 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -2,15 +2,18 @@ require 'rubygems'
require 'spec'
require 'message_filter'
-describe MessageFilter, 'with argument "foo"' do
- subject { MessageFilter.new('foo') }
+share_examples_for 'MessageFilter with argument "foo"' do
it { should be_detect('hello from foo') }
it { should_not be_detect('hello, world!') }
end
+describe MessageFilter, 'with argument "foo"' do
+ subject { MessageFilter.new('foo') }
+ it_should_behave_like 'MessageFilter with argument "foo"'
+end
+
describe MessageFilter, 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
it { should be_detect('hello from bar') }
- it { should be_detect('hello from foo') }
- it { should_not be_detect('hello, world!') }
+ it_should_behave_like 'MessageFilter with argument "foo"'
end
commit a50c1a402f1b5dfa4a609efba58324590bb938cd
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:32:27 2010 +0900
nest example groups to make them more DRY
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 91415d9..fca16fb 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -2,18 +2,18 @@ require 'rubygems'
require 'spec'
require 'message_filter'
-share_examples_for 'MessageFilter with argument "foo"' do
- it { should be_detect('hello from foo') }
- it { should_not be_detect('hello, world!') }
-end
-
-describe MessageFilter, 'with argument "foo"' do
- subject { MessageFilter.new('foo') }
- it_should_behave_like 'MessageFilter with argument "foo"'
-end
-
-describe MessageFilter, 'with argument "foo","bar"' do
- subject { MessageFilter.new('foo', 'bar') }
- it { should be_detect('hello from bar') }
- it_should_behave_like 'MessageFilter with argument "foo"'
+describe MessageFilter do
+ share_examples_for 'MessageFilter with argument "foo"' do
+ it { should be_detect('hello from foo') }
+ it { should_not be_detect('hello, world!') }
+ end
+ describe 'with argument "foo"' do
+ subject { MessageFilter.new('foo') }
+ it_should_behave_like 'MessageFilter with argument "foo"'
+ end
+ describe 'with argument "foo","bar"' do
+ subject { MessageFilter.new('foo', 'bar') }
+ it { should be_detect('hello from bar') }
+ it_should_behave_like 'MessageFilter with argument "foo"'
+ end
end
commit ad7819f5d9d244d81b830217fcad22be62e3de42
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:34:19 2010 +0900
describe() for things, context() for context
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index fca16fb..244f5c0 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -7,11 +7,11 @@ describe MessageFilter do
it { should be_detect('hello from foo') }
it { should_not be_detect('hello, world!') }
end
- describe 'with argument "foo"' do
+ context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
end
- describe 'with argument "foo","bar"' do
+ context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
it { should be_detect('hello from bar') }
it_should_behave_like 'MessageFilter with argument "foo"'
commit 289edb2b695e8ff0606ce84df882874ae6998361
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:36:43 2010 +0900
now it is time to refactoring product code. use Enumerable#any? for simplicity
diff --git a/message_filter.rb b/message_filter.rb
index d032457..b8492ec 100644
--- a/message_filter.rb
+++ b/message_filter.rb
@@ -3,9 +3,6 @@ class MessageFilter
@words = words
end
def detect?(text)
- @words.each do |w|
- return true if text.include?(w)
- end
- false
+ @words.any?{|w| text.include?(w) }
end
end
commit 5b810bcb67232df32256e5509b046512f5ee55dd
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:47:03 2010 +0900
new example : 'ng_words should not be empty' in naive style
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 244f5c0..1b41200 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -10,6 +10,9 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
+ it 'ng_words should not be empty' do
+ subject.ng_words.empty?.should == false
+ end
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 5cb3d978533e8002ab8b6da227ec6521bffcac42
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:48:40 2010 +0900
obvious implementation using attr_reader :ng_words
diff --git a/message_filter.rb b/message_filter.rb
index b8492ec..502eb18 100644
--- a/message_filter.rb
+++ b/message_filter.rb
@@ -1,8 +1,10 @@
class MessageFilter
def initialize(*words)
- @words = words
+ @ng_words = words
end
+ attr_reader :ng_words
+
def detect?(text)
- @words.any?{|w| text.include?(w) }
+ @ng_words.any?{|w| text.include?(w) }
end
end
commit 5974880a2865962de553349e630abc00467edad6
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:50:46 2010 +0900
using be_empty
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 1b41200..5ec83f8 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,7 +11,7 @@ describe MessageFilter do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
it 'ng_words should not be empty' do
- subject.ng_words.empty?.should == false
+ subject.ng_words.should_not be_empty
end
end
context 'with argument "foo","bar"' do
commit 19ac7de474f422effb49cba0b00ab48b2225cf26
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:52:41 2010 +0900
remove example description argument, but information is wrong
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 5ec83f8..0c3ccbf 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -10,9 +10,7 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- it 'ng_words should not be empty' do
- subject.ng_words.should_not be_empty
- end
+ it { subject.ng_words.should_not be_empty }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 8a971b9e5c5f7eb1dc2e0cb19984ebad459825d4
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:54:08 2010 +0900
using its(:ng_words) to get right amount of information
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 0c3ccbf..a7dbb92 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -10,7 +10,7 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- it { subject.ng_words.should_not be_empty }
+ its(:ng_words) { should_not be_empty }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 94ee574c81ab8d70882519c3099e0f520df59619
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:55:03 2010 +0900
move to shared examples
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index a7dbb92..cd28e79 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -6,11 +6,11 @@ describe MessageFilter do
share_examples_for 'MessageFilter with argument "foo"' do
it { should be_detect('hello from foo') }
it { should_not be_detect('hello, world!') }
+ its(:ng_words) { should_not be_empty }
end
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- its(:ng_words) { should_not be_empty }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit b4a15fa8c971daec40c9c6e0362582bc9e6467c6
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 05:57:53 2010 +0900
add example: ng_words size is 1
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index cd28e79..ef00f74 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,6 +11,9 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
+ it 'ng_words size is 1' do
+ subject.ng_words.size.should == 1
+ end
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 393aaaf73e4f93efeab26648dbffd738faa74645
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 06:01:36 2010 +0900
introduce have(count).items matcher for size expectation
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index ef00f74..7542f74 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -12,7 +12,7 @@ describe MessageFilter do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
it 'ng_words size is 1' do
- subject.ng_words.size.should == 1
+ subject.ng_words.should have(1).items
end
end
context 'with argument "foo","bar"' do
commit 77ff82504d0e490525190b08a9b1868bb6cee77e
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 06:02:45 2010 +0900
remove example description, but information is wrong
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 7542f74..1f7593d 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,9 +11,9 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- it 'ng_words size is 1' do
+ it {
subject.ng_words.should have(1).items
- end
+ }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 3d0226cae2093deb27f1211cf547e689042d96ba
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 06:03:51 2010 +0900
okay, introduce its(:ng_words) again.
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 1f7593d..3d54706 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,9 +11,7 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- it {
- subject.ng_words.should have(1).items
- }
+ its(:ng_words) { should have(1).items }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit 8b0ca415de0bfd6af512d304fb475335cec6cae3
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 06:06:24 2010 +0900
using have(n).named_collection to gain simplicity
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 3d54706..85f106a 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -11,7 +11,7 @@ describe MessageFilter do
context 'with argument "foo"' do
subject { MessageFilter.new('foo') }
it_should_behave_like 'MessageFilter with argument "foo"'
- its(:ng_words) { should have(1).items }
+ it { should have(1).ng_words }
end
context 'with argument "foo","bar"' do
subject { MessageFilter.new('foo', 'bar') }
commit a75ba296ca465eb21a23661aa6c8c0ae0f3794a1
Author: Takuto Wada <takuto.wada@gmail.com>
Date: Sun Feb 28 06:07:23 2010 +0900
add similar example for other context
diff --git a/message_filter_spec.rb b/message_filter_spec.rb
index 85f106a..e738ea8 100644
--- a/message_filter_spec.rb
+++ b/message_filter_spec.rb
@@ -17,5 +17,6 @@ describe MessageFilter do
subject { MessageFilter.new('foo', 'bar') }
it { should be_detect('hello from bar') }
it_should_behave_like 'MessageFilter with argument "foo"'
+ it { should have(2).ng_words }
end
end
yosukehara
2010/03/01 18:53
すばらしい!! 社内のRubyな方々に共有しました.
nobyuki
2010/05/11 21:36
社内勉強会の題材とさせて頂きました。ありがとうございます。
t-wada
2010/05/12 20:50
ありがとうございます!
- 未来のいつか/hyoshiokの日記 - 東京Ruby会議03に行ってきた〜 [to...
- maeshimaの日記 - Rspec入門メモ
- acceleration.rb - coffee.rb (Twitter hash tag #coffeerb)に参加...
- うんたらかんたらRuby - RSpec入門
- Rubyとか Illustratorとか SFとか折紙とか - RSpecとかCucumberとか...
- changeworldの日記 - Unit::Test -> RSpec? Shoulda?
- changeworldの日記 - Shoulda と Object Daddy が楽しい!
- 初学者の箸置
- 初学者の箸置 - 第42回 Ruby/Rails勉強会@関西にいってきた
- miauの避難所 - [Git]プロジェクトで Git を使ってみた感想とか
- 日記 - Rspecのpredicateマッチャ
- zonolog - これからrspecを勉強するときに参考にする資料
- @hamaknの日記 - Rails勉強会@東京第58回
- Umeyashikiの日記 - 2010年の振り返り
- watawata日記 - 技術書の写経
- Naoki_Rinの学習 - はじめてのRuby&RSpec
- WapBox - RspecのSubjectについて
- pgmgontaの日記 - Rails勉強会 2011/9/17(土) 前半まとめ
- Rubyの魔神 - rspec
- hp12c - RubyのRSpecでリボルバーを作ってロシアンルーレットしよう...
- 621 http://reader.livedoor.com/reader/
- 351 http://pipes.yahoo.com/pipes/pipe.info?_id=faa858a20082ef6d25ad27557e37e011
- 341 http://twitter.com/
- 330 http://www.223soft.net/54
- 231 http://b.hatena.ne.jp/hotentry
- 221 http://mitukiii.jp/2010/12/16/rspec-use-on-ruby-on-rails3/
- 190 http://hamasyou.com/blog/archives/000393
- 182 http://www.google.co.jp/reader/view/
- 168 http://www.google.co.jp/search?sourceid=chrome&ie=UTF-8&q=Rspec+context+subject
- 166 http://www.google.com/reader/view/

![[動画で解説]和田卓人の“テスト駆動開発”講座 [動画で解説]和田卓人の“テスト駆動開発”講座](http://f.hatena.ne.jp/images/fotolife/t/t-wada/20080105/20080105004602.png)




