<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":"[]"}