---
# System prepended metadata

title: '[08/10_a] 測試驅動開發TDD'
tags: [Rails]

---

# [08/10_a] 測試驅動開發TDD

寫測試：寫一段程式，測試功能出來否。
> 如：用if else測試。

計算分數 的測試功能 (不是 TDD)
```ruby=
def calc_score(x, y)
    return 2 * x * y
end
```
測試為
```ruby=
if calc_score(3, 5) == 30
    puts "success!"
else
    puts "error"
end
```
## TDD原則
TDD：確保程式依照預期行為運作

* 先寫測試，再寫程式
* 測試「不存在」的功能，「假設」它可正常運作
* 測試失敗（紅燈），再想辦法修正它（綠燈）

## 測試框架
### minitest
內建，速度快，難寫

### RSpec
「用Ruby寫規格書」
主流套件，要額外安裝，執行慢，語法易懂。

#3A原則 **Arrange, Act, Assert**
#執行法 **rspec atm_spec.rb**

例：ATM取/存款功能
**存錢功能**
- 可以存錢
- 不可以存 0 元或是小於 0 元的金額（越存錢越少！）

**領錢功能**
- 可以領錢
- 不能領 0 元或是小於 0 元的金額（越領錢越多！）
- 不能領超過本身餘額

* 最初會出現「uninitailize ATM」，因為沒有class ATM
* 要引入class

```ruby=
require ('./atm')
```

* 接著出現「uninitailize constant ATM」
* 亂加常數 ATM=1 就過，應該寫 class ATM

```ruby=
# 檔名慣例 atm_spec.rb
## context是語意化包裝
### 要有class做測試（寫在一起或require）
RSpec.describe ATM do
  context"領錢功能" do
    it "可以領錢" do
      atm = ATM.new(20)
      money = ATM.withdraw(10)
        
      expect(atm.balance).to be 10
      expect(money).to be 10
    end

    it "不能領0元或負數" do
      atm = ATM.new(10)
      atm.withdraw(-50)
      expect(atm.balance).to be 10
    end

    it "不能領超過餘額" do
      atm = ATM.new(20)
      money = atm.withdraw(30)
      
      expect(atm.balance).to be 20
      expect(money).to be 0
    end
  end

  context"存錢功能" do
    it "可以存錢" do
      atm = ATM.new(10)
      atm.deposit(20)
      expect(atm.balance).to be 30 #(be(30)) 這是matcher，會比較預期值
    end
  end

  context"存錢功能的限制" do
    it "不可存0元以下數額" do
      atm = ATM.new(10)
      atm.deposit(-20)
      expect(atm.balance).to be 10 #判斷餘額是否10元
    end
  end
end
```

沒有初始化，ATM.new(10)會引數錯誤
沒有deposit方法，要自己寫
沒有withdraw方法，要自己寫
```ruby=
# 檔名慣例 atm.rb
### 被測試的對象class
class ATM
  def initialize(balance)
    @balance = balance #初始化進實體變數
  end

  def withdraw(money)
    if money > 0 && money <= @balance
      @balance = @balance - money
    return money
    else
      return 0 #領錢小於0的話 不給領錢
    end
  end

  def deposit(money)    #存錢，最初的錢（實體變數）+錢且錢須大於0
    @balance = @balance + money if money > 0
  end

  def balance
    @balance    #剩下就是餘額
  end
end
```

另外一種寫法，抽出功能放到private
```ruby=
  def withdraw(money)
    if money > 0 && enough?(money)
      @balance = @balance - money
    return money
    else
      return 0 #領錢小於0的話 不給領錢
    end
  end

private

def enough?     #錢夠嘛？
    money <= @balance    #錢必須小於等於餘額
end
```

## 為什麼要寫測試
* 測試本身就是規格
* 寫出更有信心的程式碼
* 可以做出比較好的設計
* 將來有重構（refactor）的可能性


## 補充
* Capybara https://github.com/KtPractice/capybara

## 面試題
1. 怎麼測試還不存在的程式碼？假設它存在！
1. 怎麼知道要測什麼？看規格！

推薦PragProg書店

###### tags: `Rails`