RSpecのここがすごい!

9,844 views

Published on

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

Published in: Technology, Education

RSpecのここがすごい!

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

×