RSpecのここがすごい!
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

RSpecのここがすごい!

on

  • 10,370 views

1/30に行った、Thrive on development 勉強会で使用したスライドです。

1/30に行った、Thrive on development 勉強会で使用したスライドです。
RSpecの使い方と、振る舞い中心のUnitTestのやり方について言及しています。

Statistics

Views

Total Views
10,370
Views on SlideShare
10,007
Embed Views
363

Actions

Likes
17
Downloads
37
Comments
0

8 Embeds 363

http://www39.atwiki.jp 293
http://www.slideshare.net 46
http://s.deeeki.com 11
http://webcache.googleusercontent.com 3
http://redmine.tes.co.jp 3
http://slideshare-download.seesaa.net 3
http://twitter.com 2
http://192.168.33.10 2
More...

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

RSpecのここがすごい! Presentation Transcript

  • 1. RSpec のここがすごい! RSpec で宣言的な UnitTest! RSpec ? Ruby には、 Test::Unit があるし、 普通に UnitTest が書けるよね? なんで??
  • 2. RSpec のここがすごい! RSpec で宣言的な UnitTest! まずはこれを見てくれ require 'lru_cache' describe LruCache do describe " を初期化する場合 " do it " は、サイズを渡したら、そのサイズのキャッシュができる ." do targ = LruCache.new(10) targ.limit.should == 10 end it " もし、サイズにマイナス値を渡したら、例外が発生する ." do lambda{ LruCache.new(-1) }.should raise_error(ArgumentError) end it " もし、サイズに nil を渡したら、例外が発生する ." do lambda{ LruCache.new(nil) }.should raise_error(ArgumentError) end it " もし、サイズに数値以外を渡したら、例外が発生する ." do lambda{ LruCache.new("a") }.should raise_error(ArgumentError) end end end http://github.com/mitim/tddbc-lrucache/blob/master/lru_cache_spec.rb
  • 3. RSpec のここがすごい! RSpec で宣言的な UnitTest! 1. まず LruCache に関する 記述 (describe) だよ、と宣言して 1. そのうちの「初期化をする場合」のテストだよ、と宣言して 1. 「サイズを渡したら、そのサイズのキャッシュができる」べきだと、通常できることを説明していて 1. その内容で targ.limit は 10 であるべき (should) と明示して 2. 「サイズにマイナス値を渡したら、例外が発生する」べきだと、引数がおかしい場合に起こることを説明していて 1. その内容で ArgumentError という error が発生するべきと明示して 3. 「サイズに nil を渡したら、例外が発生する」べきだと、引数がおかしい場合に起こることを説明していて 1. その内容で ArgumentError という error が発生するべきと明示して 4. 「サイズに数値以外を渡したら、例外が発生する」べきだと、引数がおかしい場合に起こることを説明していて 1. その内容で ArgumentError という error が発生するべきと明示して これを見るだけで、 LruCache が 「何をするプログラムなのか」 が見えてくる ! しかも、 Test::Unit ( Java の JUnit4.1 以前相当)よりも、より 記述が簡便 で読みやすい !
  • 4. UnitTest の次の流れ -より人が 読みやすい テストへ RSpec で宣言的な UnitTest! UnitTest が広まるなかで、認識されていったこと 1. UnitTest は、ホワイトボックス・テスト ( テスト対象の全コードを理解したうえ でのテスト ) ではない 。 2. UnitTest でテストしているのは、テスト対象のクラスとメソッドの インタ フェース だ。 1. つまりは、ブラックボックス・テスト、ステートボックス・テストに 他な らない 。 3. インタフェース の肝とは、インプットとアウトプットが何であるかをしっかり 確立すること。 1. つまりは、その インタフェース の振る舞いをしっかり確立すること。 そうだ! 振る舞い をテストするんだ!!
  • 5. UnitTest の次の流れ -より人が 読みやすい テストへ
    • -> Behavior Test という認識へ。
    RSpec で宣言的な UnitTest! だったら、振る舞いをもっとわかりやすく記述できないかな?
    • -> Java の場合 : JUnit4.5 assertThat() の登場
    • 6. -> Ruby の場合 : RSpec の登場
    いずれでも、根底にある思想は「 コードは徹頭徹尾人が読むためにある存在 」だということ。
  • 7. ブラックボックス? ステートボックス? RSpec で宣言的な UnitTest! どちらかというと 設計 に該当する考え方。しかし、そのコードに与える影響の強さは、フローチャート以上。 ブラックボックスやステートボックスの考え方自体は、構造化手法とそれに伴うモジュール化の時代から存在している。
  • 8. ブラックボックス? ステートボックス? RSpec で宣言的な UnitTest! ブラックボックス・テスト? ブラックボックスとは、あるクラスの公開アクセッサに注視して、その インタフェース を明らかにすること。 じっさいの コードやオブジェクトの状態は考慮に入れない で行う。 クラス
    • そのクラスの公開されている部分(メソッドやプロパティ等)が、外部からアクセス可能になっていること。
  • 9. ブラックボックス? ステートボックス? RSpec で宣言的な UnitTest! メソッド / プロパティ
    • そのメソッドの IN 値 が明らかになっていること。
      • IN 値に閾値があるときは、それの範囲
      • 10. IN 値に特値(特別な値)があるときは、その値
      • 11. IN 値がオブジェクトの場合:そのステートに制限があるときは、ステート
    • IN 値が 対象外 のときの動作が明らかになっていること。
      • 例外の発行
      • 12. 単なる無動作
    • IN 値を与えたときの OUT 値 が明らかになっていること。
      • IN 値に閾値があるときは、とくにその境界値の OUT 値
      • 13. IN 値に特値があるときは、その場合の OUT 値
      • 14. クラスにステートがあり、それにより OUT 値が左右される場合は、ステート別の OUT 値
  • 15. ブラックボックス? ステートボックス? ステートボックス・テスト? ステートボックスとは、あるクラスの オブジェクトのステート やそのクラスが 他に及ぼす影響 に注視して、そのステート・影響を明らかにすること。 じっさいの コードは考慮に入れない で行う。 クラス RSpec で宣言的な UnitTest!
    • そのクラス ( オブジェクト ) のステートと遷移が明らかになっていること。
  • 16. RSpec で宣言的な UnitTest! ブラックボックス? ステートボックス? メソッド / プロパティ
    • そのメソッドによる、クラス ( オブジェクト ) の ステートの変更 が明らかになっていること。
      • IN 値によるステートの変化値
      • 17. IN 値と現ステートの組み合わせによるステートの変化値
    • そのメソッドによる、 外部への影響 が明らかになっていること。
      • DB やファイル等、記録媒体への影響
      • 18. システム的なオブジェクトなど、自身に管理権限のない他のオブジェクトへの影響
    • そのメソッドでおこる、 外部からの影響 が明らかになっていること。
      • DB やファイル等、記録媒体からの影響
      • 19. システム的なオブジェクトなど、自身に管理権限のない他のオブジェクトからの影響
  • 20. RSpec で宣言的な UnitTest! ブラックボックス? ステートボックス? これらのインタフェースを意識していくと しぜんに、次のような設計を心がけるようになっていく。 1. メソッドの 引数には、なるべく閾値を設けない 。設ける場合も、列挙(enum)を使 うなどして閾値外にならないように工夫する。 2. クラスの ステートは、必要最低限に構成する ようになる。複数のステートが絡み複 雑になる場合は、クラス分けを考え出す。 3. 外部への 影響は、なるべく排除する ように考え出す。外部からの影響(変更可能 性)も必要最低限へ。 TDD ではどう扱うか? 1. どうテストを書いたらいいかわからなくなった!そんな時の指針になる。 2. ただし、あまりガチガチに縛られないように。
  • 21. RSpec のやる事、やらない事 RSpec のやる事 RSpec のやらない事
    • ( もちろん! )UnitTest が書ける。
    • 22. 即座に UnitTest を実行して、その結果をすぐに得られる。
    • 23. モックやスタブを使える。
      • モックやスタブを使えば、テスト範囲を絞れる。
    • Ruby のありとあらゆる機能が利用できる!
      • とくに、リフレクションがそのまま使えたり、文字列生成が楽なのがうれしい。
    • ホワイトボックス・テストはサポートしない。
      • もちろんデバッガ機能なんてないし。
    • コード・カバレッジは計測しない。
      • rcovなどを利用しよう。
    • 変更点を感知しての自動実行はされない。
      • 別途CI等を利用しよう。
    RSpec で宣言的な UnitTest!
  • 24. RSpec の構文 なにはなくとも require require 'lru_cache' テスト対象のファイル を読み込ませる。 ちなみに、RSpecの何かをrequireする必要なない。 RSpec で宣言的な UnitTest!
  • 25. RSpec で宣言的な UnitTest! RSpec の構文 まずは基本 describe do end で、一番外側のブロックを記述する。 通常は、次のようにテスト対象のクラスを宣言しておく。 また、一緒に説明を付けることも可能。 describe LRUCache do end describe LRUCache , " を初期化する場合 " do end もちろん、説明だけにすることも可能。 describe "LRUCache のケース " do end
  • 26. RSpec の構文 RSpec で宣言的な UnitTest! describe の中 describeの中にもdescribeを重ねられる たとえば、同じクラスのテストでも、 初期化のテストをがっつりやって、 次にhogeメソッドのテスト、 そしてfugaメソッドのテストを …とやっていくと、 必然的にテストが長く見づらくなってくる 。 たとえば、 hogeメソッドのテストと fugaメソッドのテストとでは、 前準備で必要なものがぜんぜん違う 。 そんなときには、 describe のなかにさらに describe を書いて 、整理をつけることができる。
  • 27. RSpec の構文 RSpec で宣言的な UnitTest! describe の中 describeの説明文 ここに何を書くべきか。 自然に仕様書っぽく構成した文書にしたいなら、次のように気をつけて記述してみるといい。 [ クラス名 ] , “[ て / に / を / は / の ]○○ する場合 ( ケース )” ※クラス名は、ひとつ上のdescribeでまとめてしまった方が記述がスッキリするのは、 言うまでも無い。
  • 28. RSpec の構文 RSpec で宣言的な UnitTest! describe の中 テストの前準備 before テスト本文(it)を実行する前に必要な、 テストと直接は関係ない準備のための処理 を記述する。 たとえば、テスト対象のオブジェクトを生成して、インスタンス変数に入れたり。 たとえば、モックやスタブを用意して、本物のオブジェクトと摩り替えたり。 たとえば、ファイルを用意したり。 before :each do end before :all do end :each を指定した before は、各テスト (it) のたびに、その前に必ず実行される。 :all を指定した before は、 describe の最初に一度だけ実行される。
  • 29. RSpec の構文 RSpec で宣言的な UnitTest! describe の中 テストの後処理 after テスト(it)を実行した後に必要な、 テストと直接は関係ない後片付けのための処理 を記述する。 after :each do end after :all do end :each を指定した after は、各テスト (it) を実行するたびに、その後に必ず実行される。 :all を指定した after は、 describe の最後に一度だけ実行される。
  • 30. RSpec の構文 RSpec で宣言的な UnitTest! it の中 テストのコードは、すべて it の中に記述する。 基本的な書き方は、次のとおり。 it " テストの説明 " do [ テスト対象オブジェクト ] . [ テスト対象メソッド ] .should == [ 結果 ] end
  • 31. RSpec の構文 RSpec で宣言的な UnitTest! it の中 ここに何を書くべきか。 自然に仕様書っぽく構成した文書にしたい場合、次のように気をつけて記述してみるといい。 "[ どのような操作をする ] と、 [ その結果はどうなる ] 。 " itの説明文 基本的な機能要件を説明する場合 特殊な機能要件や、エラー的な機能要件を説明する場合 “ は、○○すると、 ×× になる。” “ もし、○○すると、 ×× になる。”
  • 32. RSpec の構文 RSpec で宣言的な UnitTest! it の中 shouldメソッドは、そのオブジェクトの状態を確認し、指定された状態であるか否か( ~であるべき )を検査する。 == 演算子のほか、 be 系の Matcher が多数用意されている。 全てのオブジェクトに動的に加えられたメソッドなので、基本的には何でも検査可能。 shouldメソッド should_notメソッド shouldと違い、こちらは否定検査( ~であってはいけない )をするときに使用する。
  • 33. RSpec の構文 RSpec で宣言的な UnitTest! it の中 shouldで検査できるよう、多数のMatcherが用意されている。 Matcher群 == expected ==比較の結果が同じか be_true 真であるか be_false 偽であるか be_nil nilか be_empty Arrayが空か be_an_instance_of Class クラスがClassと一致するか be_a_kind_of Class クラスが指定Class、もしくはそのサブクラスか == expected ==比較の結果が同じか be_true 真であるか be_false 偽であるか be_nil nilか be_empty Arrayが空か be_an_instance_of Class クラスがClassと一致するか be_a_kind_of Class クラスが指定Class、もしくはそのサブクラスか
  • 34. RSpec の構文 RSpec で宣言的な UnitTest! it の中 Matcher群 have_key key keyがあるか be_close E,D 数値が、E~Dの範囲に収まっているか change receiver,message,&block Procオブジェクトが変化するか change(receiver,message,&block).by value Procオブジェクトが指定された値で変化するか(should_notは使用できない) change(receiver,message,&block).from(before).to(after) Procオブジェクトがbeforeからafterに変化するか(should_notは使用できない) eql expected ==とほぼ同義((eql?で比較) equal expected 同じオブジェクトか have(n).items 配列などのコレクションオブジェクトが、n個の要素を持っているか。 have_exactly(n).items 配列などのコレクションオブジェクトが、ちょうどn個の要素を持っているか。(should_notは使用できない) have_at_least(n).items 配列などのコレクションオブジェクトが、n個以上の要素を持っているか。(should_notは使用できない) have_at_most(n).items 配列などのコレクションオブジェクトが、n個以下の要素を持っているか。(should_notは使用できない)
  • 35. RSpec の構文 RSpec で宣言的な UnitTest! it の中 Matcher群 include expected 配列などのコレクションオブジェクトに、expectedが入っているか。 match regexp 正規表現regexpにマッチするか。 raise_error 例外が発生するか。 raise_error Expected Expectedな例外が発生するか。 raise_error Expected,message Expectedな例外が、messageを伴って発生するか。 raise_error Expected,regexp Expectedな例外が、正規表現にマッチするメッセージを伴って発生するか。 respond_to method,method,method... オブジェクトが、指定メソッドを全て持つか。 satisfy {|e| ...} ブロックの実行結果(eにテストオブジェクトが渡される)が真になるか。 thorw_symbol(symbol=nil) symbolがthrowされるか。 書いてる本人が、使ったことがないMatcherが多数。
  • 36. RSpec の構文 RSpec で宣言的な UnitTest! it の中 例外が発行されたかどうかはどうチェックする? 次のようにすると、例外の捕捉ができ、例外発行チェックができる。 proc{ [ターゲットオブジェクト].[ターゲットメソッド] }.should raise_error
  • 37. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 RSpecには、簡単なスタブとモックを組み込む機構が用意されている。 ここでは、RSpecのスタブ/モック機能に焦点を当てる。 簡単な機構なので、もっとダイナミックな仕組みが欲しい場合は、mochaやflex_mockなどのモック専用ライブラリ/フレームワークを利用した方が効率がいい。
  • 38. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 スタブとモックの違い スタブもモックも、UnitTestで必要になる、内部使用している部品をエミュレートすることで、 本物の部品の代用となる空箱 のようなもののことを指す。 なぜこんなものが必要なのか?それは、こんな理由によっている。
    • 全てを「本物」でテストしようとすると、「全てが揃わないとテストできない」という本末転倒な事が起こりかねない。
    • 39. たとえば時刻に関するオブジェクトのように、システムの構成によって変化してしまうオブジェクトがあると、テスト環境によって差異ができてしまう。
    • 40. UnitTest が大きな問題に移ると段々と結合テスト化してしまう、という問題がある。
    ある程度のスタブ/モックを使用することで、これらの問題が有機的にクリアされていく。 ※ ただし、スタブ/モックを多用し過ぎると、今度はインタフェース不一致の発見を先送りにする、という状況にもなりかねない。このあたりはさじ加減が必要。
  • 41. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 スタブとモックの違い では、スタブとモックの違いはなにか? 最近、 Martin Fowler らが、このスタブとモックの違いに関して面白い考察をしていた。この定義は今や一般化しており、RSpecでもこの意味でスタブとモックとを使い分けている。 スタブ 「 インタフェースの定義だけ一致 していれば、中身はどのように動いてもいいので、とりあえず用意しておく空箱」のことを言う。 モック 「インタフェースの定義だけではなく、 それがどの様に呼ばれるか、またそれに対して何を返すべきかまでを模倣 した空箱」のことを言う。
  • 42. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 スタブ 何かのクラスのアクセッサをスタブ化するときは、次の構文でアクセッサをスタブに摩り替える。 たとえば次のように書くと、Stringクラスのsizeメソッドが、必ず10を返却するようになる。 [クラス] .stub! (:[アクセッサ]) .and_return ([戻り値]) # [クラス]は[アクセッサ]をスタブ化し、[戻り値]を返す。 String .stub! (:size) .and_return (10)
  • 43. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 モック 何かのクラスのアクセッサをモック化するときは、次の構文でアクセッサをモックに摩り替える。 [クラス] .should_recieve (:[アクセッサ]) # [クラス]は[アクセッサ]が呼ばれることを期待する。 [クラス] .should_recieve (:[アクセッサ]) .and_return ([戻り値]) # [クラス]は[アクセッサ]が呼ばれることを期待し、その結果として[戻り値]を返す。 [クラス] .should_recieve (:[アクセッサ]) .with ([引数]) .and_return ([戻り値]) # [クラス]は[アクセッサ]が[引数]で呼ばれることを期待し、その結果として[戻り値]を返す。
  • 44. RSpec の構文 RSpec で宣言的な UnitTest! スタブ / モック機能 たとえば次のように書くと、Arrayクラスのsliceメソッドが引数(5,5)で呼ばれることを期待し、その結果として[5,6,7,8,9]を返却するようになる。 Array .should_recieve (:slice) .with (5,5) .and_return ([5,6,7,8,9]) モック
  • 45. RSpec を実行しよう! RSpec で宣言的な UnitTest! 通常のプログラミング時にテストを行う場合 RSpecは、コンソールからコマンドラインで実行する。(NetBeansを使うと、なんと!IDE内でグラフ付で実行できる!) $ spec -c -fs [specコードファイル.rb]
    • コンソールを出す。
      • Windowsの場合、Ruby Consoleを出して!
    • specコードのあるディレクトリに移動する。
    • 46. RSpecに、specコードを渡して実行する。
  • 47. RSpec を実行しよう! RSpec で宣言的な UnitTest! CI のなかでテストを通して行う場合 $ spec -c -fh [specコードファイル.rb] > result.html 次のようにすると、 HTML 形式で結果を出力してくれる。 出力したHTMLを特定HTTPサーバに配置するなり、メールに添付するなどまでを自動化すると、かなり使えるツールになる。