BDD by Jasmine (jscafe 13)

2,166 views

Published on

javascriptでjasmine使ってBDDする話。

Published in: Technology
  • Be the first to comment

BDD by Jasmine (jscafe 13)

  1. 1. BDD by jasmine Ryuma Tsukano jsCafe13
  2. 2. ● BDDについて ● jasmineの基本文法 ● TDDのプロセスをハンズオン 今日の目次
  3. 3. ● javascript上でBDDをするためのtest framework ○ assertion/testdouble等、testに必要な機能全部入ってる ● 主にUTをカバーするもの =>BDDとは? jasmineとは
  4. 4. BDDとは
  5. 5. Behaivor Driven Development ● 振る舞い 駆動 開発 ● Dan North氏が提唱したもの。 振る舞い=そのシステムの動作仕様の事 ● ①specification : 詳細設計付近で定義されるような細かな動作の事 ● ②story : 要求仕様付近のアプリの動作の事 ※後述するが、それぞれツール群が異なる =>つまり、仕様から始める開発方法 ● 当たり前。 ● =>なぜ、こんな事を言ってるか? BDD
  6. 6. 元々BDDはTDDから派生したもの。 ● TDD:テスト駆動開発 by Kent Beck ->本 幾つか代表的な特徴を上げると、 ● テスト作業をプログラムで書く ● 実装の前にテストを書く(Test First) ● 初めに通過するcodeを書いてその後書き直して行く(Refactoring) いわゆるxUnitが普及 =>Junit始め各言語で出てきた。 Dan North=TDDの教育者。教えた現場の中で、 ● TDDが上手くいく現場と、 ● TDDが上手く行かない現場があった。 Dan NorthとTDD
  7. 7. <BAD> TDDがうまくいかない現場の様子 ● 何を書くかで悩む ● 工数が膨らむ? 原因 ● 彼らは「テスト」の観点でTDDを実施していた。 <GOOD>TDDがうまくいっている現場の様子 ● 何を書くかで悩まない ● 工数が削減? ● 副産物の効果に気づいてる(後述) 原因 ● 彼らは「設計」の観点でTDDを実施していた。 ● (&結果としてテスト)も出来た。 2patternの現場
  8. 8. TDDの観点として本当の意味でのテストをすると... ● 何をtestするか?で悩む ● 膨大な量のテストを書く事に ● 自動化難しいtestも書く? ● 追加改修時直す所沢山 =>とても大変な事に! また、TDD = テストと認識すると =>別の手段で詳細設計LVの事をしてしまう現場も有り =>TDD=設計(※後述)とすると、2重に設計作業してる事になる。 =>設計で2倍の工数がかかる。 TDDとテスト 引用元:http://www.hayst.com/Pages/positioning.aspx
  9. 9. TDDしてる瞬間 ● test書いてる時 ○ これから書くソースのロジック考える ■ =>詳細設計/プログラム設計してる ● test書いた後 ○ これを残してあげてる事で次の人に役立つ。like API仕様書 ■ =>詳細設計/API仕様書/IF仕様etc書いてる =>実は設計してる(&結果として、それがテストの一部に) =>TDDが上手く機能している現場は、これに気づいていた。 TDDする時にテストから設計にフォーカスを移すと... TDDと設計 テスト 設 計
  10. 10. TDDで設計すると(テストするTDDと比べて) ● 観点が明快なのでテストソース作りで悩む事が少なくなる。 ● テストの量が効果的に減る ○ テストソースも見通しが良くなる ○ 後での変更の量も少なめ ● 結果として後でも仕様として意味のあるテストが残る ○ TDDでテスト:仕様として冗長で分かり辛い ○ TDDで設計:第3者が見て仕様を把握し易い ● 位置づけを設計と明示=>事前に詳細設計的な作業を含まない ○ =>さっきのような誤って工数2倍が起こり辛い 実はKentBeckもTDDはテストというより設計(分析)と言ってる =>設計を前面にした方が正しい。 TDDで設計する効果
  11. 11. TDD(Test Driven Development) = テスト < 設計なんじゃん! ● テストって頭につくから勘違いされて失敗するんじゃんか。 ● 仕様/設計っぽい言葉(振る舞いBehaivor)を入れようぜ! =>そこでBehaiver Driven Development BDD(特にUT系のtool)の特徴(TDDと比べて) ● 自然言語で動作振る舞いを書く ○ TDD:method => BDD:describe “person” .. it “should have pen”.. ● 自然言語っぽく確認メソッドも書く ○ TDD: assert => BDD:shouldやexpect ● 自然言語の順で検証する「[実際値]は[期待値]と等しい」 ○ TDD:assert_equals 期待, 実際 => BDD:expect実際toBeEqual期待 ● 自然言語っぽく前提条件、後処理 ○ TDD : setup/teardown => BDD : before/after ※TDD系のToolもBDDっぽくなってるので、一概には言えないが... =>いずれにせよ自然言語的に振る舞う事を前面に出してる点が特徴 Dan North
  12. 12. 仮にテストする事を数値化したとすると ● 100%:やらなくてはいけないテスト ○ 60%:BDDが結果としてやるテスト(仕様として意味有) ○ 残り=40%。残ってる!この扱いは考えないといけない 残りの40%に対しての色んな考え方 ● ①テストしない(勿論、機能試験や受け入れ試験は実施するが) ○ 考え方:40をどこまで減らせる?に注力 ○ 「不安がある所をテスト(BDD)する」 ● ②昔ながらのSIっぽい網羅的な手動テストする ○ User多いor社会的な影響大きいと必然こうなる ● ③99%全部自動化する ○ 元の話に戻るが、こういう現場も ○ 保守/追加改修で、ただのテスト部分(上記の40)が邪魔になる ■ この部分(40)を後で削除する人達もいる=>自動化無意味? ○ 保守の為のTDD?設計の為のTDD?testの為の...とか言ってる =>求められている品質、納期に基づいて調整を 結局、テストはどこにいった?
  13. 13. ● 1)各言語でBDDツール出来た ○ ‘03 jBehave(java)から、C#版、php版、python版諸々出た ○ 初めはstory形式(given when then) => 結合テストより上 ● 2)rspec(ruby)が流行った。 ○ 結局、1のツールは普及しなかったが、rspecが流行った。 ○ rspecはspecifications形式(like TDD) => 単体テストをカバー ● 3)cucumber(ruby)が出てきた。 ○ 2)のrspecのstory runnerの話が膨らんでcucumberとして独立 ○ 1のstory形式をより洗練化=>表向き全部自然言語で書く ■ そこそこ流行った。 ● 4)jasmine(javascript)が出てきた。 ○ 元々jsUnit(xUnit系)作ってたPivotal社が改めて作った。≒ rspec ※最近jsではmocha(+chai等)が勢い有。BDDで書けばjasmineと大体一緒 BDDのその後の動き
  14. 14. jasmine
  15. 15. jasmineを始めましょう。 公式でふたつあり 1. stand alone・・・js単体で動作する物 2. ruby gem版・・・rails等ruby系frameworkの中で使うための物 1を使おう。 公式ページの一番下のDownloadsの 「stand alone release」を クリックしてダウンロード =>解凍してみよう! Let’s start jasmine
  16. 16. 本体(lib/specRunner)と、サンプルのjs(src)とその仕様(spec) ● lib : jasmineの本体。実行に必要なファイル。 ○ jasmine-html.js : html上のreport生成 ○ jasmine.css : そのcss ○ jasmine.js : jasmine本体 ● spec : サンプルの仕様(test) ○ PlayerSpec.js : サンプルのPlayer関数に対してのspec(test) ○ SpecHelper.js : 全体的な設定等を記載する所 ● src : サンプルのjsファイル ○ Player.js : サンプルのPlayer関数(≒class) ○ Song.js : サンプルのPlayerの使ってるSong関数(あえて開発中) ● SpecRunner.html : テスト結果を表示する(サンプルを実行) stand alone版
  17. 17. ブラウザでSpecRunner.jsを表示してみる =>サンプルのテストを実行 その結果 ● 抹茶色の帯 ○ 5つの仕様(spec)の確認に成功したと書かれている ● その下 ○ 振る舞いが書かれている ○ ex)PlayerはSongをplayできる ○ ex)songを停止した時 ■ songが現在停止を示す ■ 再開する事が出来る.. =>Jasmine実行結果から仕様が分かる SpecRunner.js
  18. 18. さっき、表示していたのは、コレ。 describe("Player", function() { var player; var song; beforeEach(function() { player = new Player(); song = new Song(); }); it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); //demonstrates use of custom matcher expect(player).toBePlaying(song); }); spec/PlayerSpec.js
  19. 19. ● describe : suites ○ 意味のある仕様(spec)の集合 ■ ≒ディレクトリ的なもの。名前 ○ describeはnestできる ○ >第1引数:タイトルやパターン名等 ○ >第2引数:function(){ ※specs(複数の仕様)を書く } ○ 例)describe(“Player”, function() { … ■ describe(“when song has been paused”, function() { ... ● it : specs ○ 仕様 ■ ここにexpectaions(振る舞いの期待)を書く ○ >第1引数:どう振る舞うか自由に記述 ○ >第2引数:function() { ※expectaion(振る舞い期待)を書く} ○ 例)it(“should be able to play a Song” , function() { ... describeとit
  20. 20. expectation : 振る舞いの記述 ● expect(実際の値).マッチャー(期待値) 例) playerのcurrentlyPlayingSongプロパティに songが入ってる事を確認したい it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); =>意図通り :SpecRunner.htmlでpass =>誤っている:SpecRunner.htmlでfailure ● 試しに、上のsongを”fake”にしてみると.. expectation
  21. 21. 書き方:to□□□□(期待値) ● expect(x).toEqual(y) : xはyと等しい ● expect(x).toBeTruthy() : xはtrueである ● expect(x).toContain(y) : xはyを含む(x = 配列か文字列) ● expect(x).toBeGreaterThan(y) : xはyより大きい ● 他、多数 自作matcherも作れる matcher
  22. 22. addMatchers({ 自作matcher名 : function(期待値) { ... ※この中でactual = 実際の値を使える return 真偽 } }); =>全体で使いたければ、SpecHelper.jsに書く 例)現在の歌と期待値が同じ、かつ演奏中である事を確認するmatcher this.addMatchers({ toBePlaying: function(expected) { var player = this.actual; return player.currentPlayingSong === expected && player.isPlaying; } }); // これが、src/SpecHelper.jsに書いてある 自作matcher
  23. 23. 前提条件 ● beforeEach(function () { … 前提条件となる処理 } ); ○ 後処理は、同じ書き方でafterEach 例)前提条件としてテスト対象となるplayer/song関数をnewする beforeEach(function() { player = new Player(); song = new Song(); }); 実行順)itの度にbeforeEachする describe(“suites”, function() { [a] beforeEach(function() { ... }) [b] it(“specs-first”, function() { … }); [c] it(“specs-second”, function() { … }); => 実行順 [a] => [b] => [a] => [c] 前提条件
  24. 24. spy : test doublesを実現するための仕組み ● test doubles=テスト対象が依存してるcomponentを置換する仕組み テスト対象だけを確認したいのに、 依存する外部のコンポーネントに何か理由があって、 確認が出来ない時に、外部のコンポーネントを 別のものに置換する、というもの。 実際はfake/dummy/mock/stub/spyと色々あるがjasmineでは意識しない jasmineとtest doubles テストソース jasmine テスト対象 js 依存する外部の コンポーネント js
  25. 25. ● まだ実装されてないコードの代替 ○ 実際、これが一番一般的なシチュエーション? ● slow test対策 ○ 特にDB等ioに関する処理は遅い=>遅い所を置換する ● 外部のリソースアクセス ○ 例)某位置SNSのAPIよく落ちてる=>testの成否が不安定化 ■ このAPIを置換しておけばUTの結果は安定 ● ※UnitTestはこれでいいが、このエラーはエラーで対応考 えないといけないのでご注意を。 ● 純粋なUnitTestをするため ○ 教科書的にはComponent TestとUnit Testは違う test doublesはいつ使う?
  26. 26. ● spyOn(対象constructor, 関数名) ○ =>これで実装がどうであれ仮の関数が作り出される ● expect(実際の値).toHaveBeenCalledWith(期待する引数) ○ =>期待する引数で仮の関数を使っている事を確認する it("tells the current song if the user has made it a favorite", function() { // ★これ (src/Song.js見るとpersistFavoriteStatusはエラーを返す) spyOn(song, 'persistFavoriteStatus'); player.play(song); player.makeFavorite(); // この中でsong.persistFavoriteStatusを実行 // ★これ (player.makeFavoriteの中でtrue引数で実行を確認) expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); jasmineのtestdouble
  27. 27. spyオブジェクト.andReturn(期待する戻り値) 例) spyOn(song, 'persistFavoriteStatus').andReturn("success!"); expect(song.persistFavoriteStatus()).toEqual("success!"); ※この例はあまり意味が無いけど、前述のように 例えばtestする関数が使用してる別の関数が出来てない時等で使う spy.andReturn
  28. 28. TDD(BDD)のプロセスをハンズオン
  29. 29. 今からサンプルにソースを追加してBDDの一通りの流れを見てみる <やりたい事> Player.jsに ● volumeプロパティを追加 ● setVolume()を追加 ○ setVolume()にvolumeが0〜100の範囲かのvalidationを追加 ○ この範囲内:volumeプロパティ=値を入れて「true」を返す ○ この範囲外:「false」を返す ※setterでtrue/false返すのおかしくないか? 的なツッコミは置いておいて!気にしないで!>_< ただの例なの! BDDしてみよう
  30. 30. 失敗=>成功=>Refactoring TDDの流れ RED (fail) GREEN (pass) Refactor Start
  31. 31. TDDの流れ ● 0:やるべきタスクを整理 ○ todoを作る(by kent beck) => jasmineのdescribe/itで良いかと ● 1:仮実装(Fake it) ○ Red)仮のtest書く=>元が無いので失敗 ○ Green)とにかくtestに通過する仮の実装をする ○ Refactor)一旦整理 ● 2:三角測量(triangulate)=>明白な実装(obvious implementation) ○ Red)三角測量するtestを書く=>失敗 ○ Green)実際の処理を実装(明白な実装)=>成功 ○ Refactor)汚くなったら整理 その後、適宜Refactoring。 もし、何かしらの原因でRedになったらGreenになるべく調整 具体的な流れ
  32. 32. ①−1)// spec/PlayerSpec.js 最後の括弧とじの前に以下を追加 describe("#setVolume", function() { it("can set 80", function() { expect(player.setVolume(80)).toEqual(true); }); }); => SpecRunner.html : Red(失敗) ①−2)// src/Player.js Player.prototype.setVolume = function() { return true; }; => SpecRunner.html : Green(成功) このガリガリのハードコーディングが仮実装 ※specの確認をしてる。正しい値があればspec動くか?の確認 ①仮実装
  33. 33. 同じテスト対象に対して異なる入力値を与える(=三角測量) ②−1)// spec/PlayerSpec.js it("can not set 200(>= 100)", function() { expect(player.setVolume(200)).toNotEqual(true); }); => SpecRunner.html : Red(失敗) ②−2)ここで正しく実装する。(=明白な実装) // src/Player.js Player.prototype.setVolume = function(volume) { this.volume = volume; if(0 <= volume && volume <= 100){ return true; } else { return false;} }; => SpecRunner.html : Green(成功) ②三角測量=>明白な実装
  34. 34. todo=>仮実装=>三角測量=>明白な実装 ● TDD(BDD):設計で悩むときは、頭の中で考えるより、とりあえず手を動か していった方がいい。 ○ そのための、プロセスを提示している ● 逆に、特に悩む事が無ければ、いきなり明白な実装しても良い ○ 絶対的なルールではない。 それぞれ意味がある ● todo ○ やる事を整理 ● 仮実装 ○ 正しいテストを作る ● 三角測量 ○ 入力値にはパターンがある事を認識 ● 明白な実装 ○ 上記踏まえて実装 TDDの進め方
  35. 35. BDDとは ● TDDから派生。 ● テストでなく設計を前面に出したもの。 ○ =>そうした方が悩まなくて済む jasmine ● describe { }とit { }で囲む ● expect( … ).マッチャーで確認 ● spyでtest double TDDのプロセスハンズオン ● 基本:テスト失敗=>成功=>リファクタリングの繰り返し ● 流れ:いきなり明白な実装しても良いが、悩んだら、以下 ○ todo整理=>仮実装=>三角測量=>明白な実装 まとめ
  36. 36. おしまい

×