<style> h1, h2, h3, h4 { text-transform: none !important; } .reveal section img { border: 0; } </style> # BDD 實戰經驗分享 MarsZ 2017.1 --- # About me - Rails developer since 2011 - CEO @ 5FPRO.com (伍樓專業) - github.com/marsz - @marsz330 ---- # About team <img src="https://i.imgur.com/up3KmZe.png" style="height: 300px; width: 900px" /> - https://5fpro.com - 在接案過程中磨練各種 Best practice。 - 開發自己的狗食。 - 電商平台的專案經驗居多。 --- # 選擇 BDD 的緣起 ---- ## 1. 測試程式碼無法反應規格的演進 ---- 完整的 feature 包含了多個 request(controller) 的測試。 ---- User story 改變了,但整合測試的程式碼沒有從結構上跟著改變。 ---- 即使寫了許多測試,關鍵功能沒有確實被測試出來。 ---- ## 2. 測試程式碼造成的負擔逐漸加重 ---- 應該從專案內移除的測試,沒有被移除。 ---- 過多的 unit test、過少的 integration test ---- CI build 過久。 ---- ## 3. 規格文件跟不上系統功能的演進 ---- 程式碼和規格文件的更改不同步。 ---- 新加入的開發者難以迅速掌握核心功能。 ---- 棄用規格文件,改放在 PM 腦袋裡。 --- # 什麼是 BDD ? ---- ## Behavior-driven development ---- ### 測試即文件、文件即測試 ---- ### 讓你的規格文件是可以被執行的: - 可讀性 - 可執行 ---- ### 讓你的測試,可以當文件給人閱讀: - 可讀性 - 可執行 ---- ### 和 TDD 的不同: - BDD 強調規格文件和測試之間的關係。 - TDD 的核心論點是先寫測試再開發。 --- # 為什麼選 BDD ? ---- 不再追求測試覆蓋率,而是追求「核心功能是否確實被測出來」 ---- 文件必須和測試綁在一起。 ---- ### RSpec 的語法特性是為了能做文件輸出 ```ruby describe 'User login' do ... describe 'False cases' do it 'Invalid email format' do .... end end end ``` rspec --format doc ``` User login False cases Invalid email format ``` ---- ### RSpec 還有 format html & json <img src="https://coderwall-assets-0.s3.amazonaws.com/uploads/picture/file/1949/rspec_html_screen.png" /> ---- ### 接下來的問題就是定義文件規範了... --- # BDD 的文件規範 1. Specification By Example (SBE) 2. Given-When-Then (GWT) ---- SBE 和 GWT 並非互斥的選擇,而是在於用什麼方式達到目的 ---- ## Specification By Example (SBE) (最後我們沒有採用此方法,但分享其核心觀念給各位...) ---- <img src="http://image.slidesharecdn.com/specificationbyexample-120518090221-phpapp02/95/specification-by-example-60-728.jpg?cb=1337332368" /> ---- ## Given When Then (GWT) 1. 入門容易 2. 工具較多 3. 維護方便 ---- ``` Feature: User trades stocks Scenario: User requests a sell before close of trading Given I have 100 shares of MSFT stock And I have 150 shares of APPL stock And the time is before close of trading When I ask to sell 20 shares of MSFT stock Then I should have 80 shares of MSFT stock And I should have 150 shares of APPL stock And a sell order for 20 shares of MSFT stock should have been executed ``` ---- ### 選擇了 GWT 後,接下來 tune 文件格式的過程就是地獄的開始了... ---- # 寫文件過程中遇到的主要問題 ---- ## Feature、Scenario、GWT 之間界線模糊 - 為了把 feature 描述清楚,結果寫 scenario 時,有似曾相似感。 - scenario 寫細一點用來區隔 feature 時,又好像把 GWT 寫一部分進來。 ---- ## (給非技術人員的) 可讀性不夠 - 由 PM 來寫,也可能會讓工程師看不懂,所以兩者之間只能多練習與磨合。 ---- ## 描述的方式過於冗長 - 「淺出」是需要練習的。 ---- ## 名詞定義不一致 --- # 寫文件心得 ---- ## Feature 可 nested,而 scenario 則只有一層 ---- ## Feature 越簡短越好 ---- ## Scenario 可以寫詳細一點,但不能有「腳本化」的現象 ---- ## GWT 的部份可接受腳本化 - 腳本化意味著,容易需要修改,因此能避免就避免 ---- ## top-down 定義文件 ```graphviz digraph hierarchy { nodesep=1.0 a->b a->c b->d d->dd dd->dd1 dd->dd2 b->e e->ee e->eee c->f c->g g->gg c->h h->hh f->i f->j i->k j->l ee->ee1 ee1->ee2 ee1->ee3 dd1->ddd1 dd2->ddd2 eee->eee1 eee1->eee2 k->kk l->ll gg->ggg ggg->ggg1 hh->hhh hhh->hhh1 a [label="Feature"] b [label="Feature"] c [label="Feature"] d [label="Scenario"] e [label="Scenario"] f [label="Scenario"] g [label="Scenario"] h [label="Scenario"] dd [label="Given"] ee [label="Given"] eee [label="Given"] i [label="Given"] j [label="Given"] gg [label="Given"] hh [label="Given"] dd1 [label="When"] dd2 [label="When"] ee1 [label="When"] eee1 [label="When"] k [label="When"] l [label="When"] ggg [label="When"] hhh [label="When"] ddd1 [label="Then"] ddd2 [label="Then"] ee2 [label="Then"] ee3 [label="Then"] eee2 [label="Then"] kk [label="Then"] ll [label="Then"] ggg1 [label="Then"] hhh1 [label="Then"] } ``` ---- ## 不可能全部的功能都寫得進來 ## 挑認為需要文件化的部分就夠了 --- # BDD Tools 1. Cucumber 2. rspec-given ---- ## Cucumber <div style="background-color: white;"> <img src="https://cucumber.io/images/cucumber-logo.svg" /></div> - https://cucumber.io/ ---- <img src="https://i.imgur.com/mWjSiGJ.png" /> ---- <img src="https://i.imgur.com/9l6pwi4.png" /> ---- <img src="https://i.imgur.com/hQwtOX7.png" /> ---- ## Cucumber 心得 1. 非常理想,但也太過於理想了。開發者在 .feature 和 _step.rb 之間切換寫 code 的成本過高。 2. BDD 經驗不足的條件下,容易寫出難以維護的 .feature。 3. 高度使用整合測試,CI 跑得更久。 ---- ## RSpec-given - https://github.com/jimweirich/rspec-given - 延伸既有的 rspec 語法格式,容易上手 ---- ## 致命問題 - 只解決了在 code 裡面可以用 Give, When, Then 的語法,無法解決其他 BDD 所要解決的問題,例如可讀性。 ---- rspec-given 算是比較接近我們當時的需求:「寫 code 的方式不往複雜方向走,又能讓 rpsec 的 doc 輸出符合 GWT 需要」 ---- 其實... ---- 不用任何工具,我們也還是可以解決 BDD 上的需求 XD ---- ```ruby describe 'Feature: 登入' do describe 'Feature: FB' do describe 'Scenario: User 尚未註冊以前,使用 FB 登入時,自動建立帳號並完成註冊' do describe 'Given user table 為空' do before { .... } descrie 'When user 以 FB 登入' do before { .... } it 'Then user 登入成功' do ... end end end end end end ``` ---- doc output: ``` Feature: 登入 Feature: FB Scenario: User 尚未註冊以前,使用 FB 登入時,自動建立帳號並完成註冊 Given user table 為空 When user 以 FB 登入 Then user 登入成功 ``` ---- 自己擴充了 RSpec method <a href="https://gist.github.com/marsz/0426ebbd032b1c664686a901e60b7d8c" target="_blank"><img src="https://i.imgur.com/yQpZ0Fp.png" height="540" /></a> ---- ```ruby feature '登入' do feature 'FB' do Scenario 'User 尚未註冊以前,使用 FB 登入時,自動建立帳號並完成註冊' do Given 'user table 為空' do before { .... } When 'user 以 FB 登入' do before { .... } Then 'user 登入成功' do ... end end end end end end ``` (output 不變) ---- <img src="https://i.imgur.com/huGaaqs.png" height="540" /> --- # Full workflow ---- ## Step 1: PM 列出 features <img src="https://i.imgur.com/0KfjOrf.png" height="540" /> ---- ## Step 2: 團隊與 PM 共同討論 features 下的 scenario 以及取捨 ---- ## Step 3: PM 補齊 GWT 的部份 ---- ## Step 4: 照一般的 scrum or kanban 開 ticket,然後順便把該 ticket 對應到的 BDD 文件一起補到測試裡 <small>(我們沒有 TDD)</small> ---- ## Step 5: 在 CI 上利用 gh-pages 輸出 ``` xvfb-run -a bundle exec rake spec:nofeatures bundle exec rubocop git clone --branch gh-pages git@github.com:5fpro/jrf-sunny.git ./live-document bundle exec rspec --format progress --format html --order defined --out ./live-document/"${CI_BRANCH}".html ./spec/features/ cd live-document git add . ts=$(date +"%Y-%m-%d %T") git config user.name 'CodeShip' git config user.email 'codeship@5fpro.com' git commit -m "'CI at ${ts} --skip-ci'" git push origin gh-pages json="{\"text\":\"Live doc: http://5fpro.github.io/jrf-sunny/${CI_BRANCH}.html for ${CI_BRANCH}\",\"username\": \"CodeShip\",\"channel\": \"#ci-server\",\"icon_url\": \"https://a.slack-edge.com/7bf4/img/services/codeship_48.png\"}" curl -H "Content-type: application/json" -X POST -d "${json}" https://hooks.slack.com/services/xxxxxxxxxx ``` ---- ## Step 6: 文件隨著 CI 發到 Slack 上 <img src="https://i.imgur.com/oIaymwo.png" height="540" /> ---- # Example http://5fpro.github.io/jrf-sunny/develop.html ---- ## 補充說明 1. 開發者依舊可自行斟酌寫 unit test 2. BDD test 一律寫在 spec/features 下 3. 我們自己寫了 rake spec:nofeatures 的 task,跳過 spec/feautres ,因產生 BDD 文件過程中會再跑一次 ---- lib/tasks/spec.rake ```ruby if Rails.env.development? || Rails.env.test? namespace :spec do RSpec::Core::RakeTask.new(:nofeatures) do |task| file_list = FileList['spec/**/*_spec.rb'] %w(features).each do |exclude| file_list = file_list.exclude("spec/#{exclude}/**/*_spec.rb") end task.pattern = file_list end end end ``` --- # 導入 BDD 後的人生... ---- ## Document = Testing = Reality ---- ## CI build 真的跑很久的話,可以選擇讓 feature branch 只跑(或不跑) spec/feautres ---- ## 工程師聚焦於滿足 feature spec ---- ## 工程師、管理者、客戶(老闆)有相同討論基礎 --- # 血淚心得 ---- ## 先解決「文件該怎麼寫」的問題 ## 後面的事情才有可能繼續 ---- ## 詳盡的文件是假的 ## 易於維護的文件才是真的 (勇敢割掉非核心功能和情境吧) ---- ## 開發者會反應文件的矛盾之處 ## 但別讓工程師來維護文件內容 ---- ## 確保文件真的有被「使用」 ---- ## 技術、工具都是其次,重要的是找出適合團隊的 Living Document 以及運作模式 --- # Future Work ---- ### 易於搜尋和瀏覽的規格文件 ---- ### 讓技術能力較低的 PM 也能 commit 文件的修改 --- ## 特別感謝 - 司改會 https://www.jrf.org.tw/ <img src="https://i.imgur.com/xB1lFK6.png" /> ---- ## 特別感謝 - 五倍紅寶石 https://5xruby.tw/ <img src="https://i.imgur.com/v2OJuK8.png" /> --- # 參考資料 ---- ## 名詞 - BDD - TDD / ATDD - Specification By Example - Living Documentation - Continuous Integration (CI) ---- ## 工具 - RSpec - capybara + webkit - cucumber.io (fail) - rspec-given (fail) ---- ## 第三方服務 - CodeShip - Slack - GitHub pages ---- ## 參考內容 - https://www.linkedin.com/pulse/agile-development-difference-between-tddatddbdd-komal-sureka - https://www.relishapp.com/relish/relish/docs/living-documentation --- # Thanks ---- # Q & A
{"metaMigratedAt":"2023-06-14T11:56:20.373Z","metaMigratedFrom":"YAML","title":"BDD 實戰經驗分享","breaks":true,"slideOptions":"{\"transition\":\"fade-out\"}","contributors":"[]"}
    4284 views