---
# System prepended metadata

title: 0326 測試、TDD、Rails 環境設定
tags: [Ruby / Rails]

---

# 0326 測試、TDD、Rails 環境設定
###### tags: `Ruby / Rails`

複習：類別 classs  vs.  模組 module
```ruby=
Class A < B::C::D

end
# 上方程式碼中： B 跟 C 可能為模組也可能為類別，D 絕對是類別
# 因為只有類別可以繼承類別
# 模組不能繼承 也不能.new
```

## 測試

上課 PPT: [投影片下載](https://drive.google.com/file/d/1R7AFivp9J2cnYmF4Cf6wfTcx3fM5qjbj/view?usp=sharing)

Q: 什麼是測試？
A: 用一段程式碼，去測試另一段程式碼


寫測試的概念像是 PM 將客人的中文轉成程式碼
e.g. 客人：我想要有個把兩個數值加起來的東西

Q: 為何要測試？
A: 確保程式可以依照預期運作 / 確認結果如規劃般確實執行而做的動作 / 有助於在開發初期釐清程式介面如何設計。

Q: 測試的目的？
A: 要做一個防護機制，而不是 debug

Q: 要怎麼測試還不存在的程式碼？
A: 假設程式碼存在

Q: 要怎麼知道哪些東西應該要測？
A: 按照規格書進行操作，在意公開的部分就好，私有的不重要


* 測試本身就是規格 (Spec)
* 寫出更有信心的程式碼
* 可以做出比較好的設計
* 將來有重構(refactor)的可能性



```ruby=
def add
    #.....
end

#test case
puts add(2,5)  #7
puts add(-2,0) #-2
```
↑ 最下面兩行就是在執行測試 
 
｜｜


>互動頁面適合用人力點擊做測試，表格類的不適合，因為人會怠惰後續輸入值會有偏差，所以才要依靠電腦（程式）來做測試


Q. 為什麼不寫測試?

* 時間有限
  * 寫測試會花將近多一倍的時間
* 跑測試太慢，因為數據太多
  * 解套：換好設備或是委外
  * 當然也可以是個不寫的理由
* 測試完程式就壞了
  * 可能代表原本的程式碼有問題
* 不會寫測試
  * 但RUBY圈盛行寫測試

>先寫 Code 再寫測試跟 TDD 的差別
>>失去測試的意義，會影響測試的真實性（**測試目的不是debug，而是要做一個防護機制**）

---

### 如何開始寫測試？

**3A 原則** (下方範例中說明)

1. Arrange 安排 (準備測試環境)
2. Act 操作 (執行測試方法)
3. Assert 評估/斷言 (評估測試結果)
4. 補充說明：https://bit.ly/39hgMFk


---


## Test-Driven Development (TDD) 測試驅動開發


  - 先寫測試個案 (test case) 再寫程式碼，確保程式可以依照預期行為運作
  - 這是一種開發方法，一種流程
  - 測試**不存在**的功能，**假設**他可以正常運作
  - 測試個案通常會有「特殊 / 臨界值」
  - 愈白話愈好
  - 測試寫到中間如果太順利，可以故意放一個會失敗的例子看測試是否會偵測到

**臨界值（特殊值）的例子：**
- 存無限大會如何
- 存 nil 會如何

**RUBY 圈常用語言測試工具**
 - minitest 
   - 速度較快 
   - 算是RUBY內建，甭另外安裝
   - 較難寫
 - RSpec (R 開頭代表 Ruby)
   - 速度較慢 
   - 語法豐富 
   - 類似英文、好寫 
   - 資源多、市佔率較高

### 使用 RSpec 執行測試

---
官方文件：[按這裡🧡](https://rspec.info/documentation/3.10/rspec-core/)
```ruby=
RSpec.describe
#起手式
```
```ruby=
【 RSpec 方法 】

describe # 功能描述 |
context  # 執行情境 | 兩者同功能，只是語意不同
it  # 執行功能的描述
expect(帶入值).to be 預期值

# 原型 expect(帶入值).to(be(預期值)) 
```
expect 範例：
```ruby=
expect(1).to be 1   -> 預期(1)是 1
expect(1).to be 2   -> 預期(1)是 2 但代入 1 所以壞了
```
測試檔的命名方式
  - 通常會在後面加 "_spec" 標示是測試檔案
  - 範例：atm_spec.rb

通常會將實作檔跟測試檔分開，之後再從測試檔裡帶入實作檔
```ruby=
# 在測試檔中（e.g. atm_spec.rb) 加在最上方
require "./實作檔.rb"

#..... 在底下寫上測試內容
```
****通常在一個 `it` 方法中只會使用**一個** `expect` 測試**一個**預期值，若用到兩個的話需要是方向一樣****

執行測試
```
$ rspec 測試檔名.rb
```
設計方法時，也可設定確認方法的**回傳值** （見下方範例）

#### refactor 重構：不影響外在前提 (公開介面) 下做調整 -> 改得更精簡
- 測試公開方法(行為)`(public method)`
- 重構內部結構
- 重構完要記得再測試一次確認是否有錯誤

---

## 測試實作範例

開發ATM
 * **存錢功能**
   * 可以存錢
   * 不可以存0元或小於0元
 * **領錢功能**
   * 可以領錢
   * 不能領0元或小於0元
   * 不能領超過本身餘額


測試檔 (atm_spec.rb)
```ruby=
require ./atm

#類別方法(參數/建議帶常數，也可帶字串)
RSpec.describe (ATM) do
  context "存錢" do #做分類，不寫不會有影響
    it "可以存錢" do #他會發生可以存錢這件事情，一個描述
            #測試個案寫在這裡↓ (搭配 3A 原則)
      atm = ATM.new(10)    # Arrange 安排
      atm.deposit(5)    # Act 操作
      expect(atm.balance).to be 15    #Assert 評估
    end
  end
  
  context "領錢" do
    it "可以領錢" do
      atm = ATM.new(10)
      money = atm.withdraw(5)
      expect(atm.balance).to be 5
      expect(money).to be 5
    end

    it "不能領0元或小於0元" do
      atm = ATM.new(20)         
      money = atm.withdraw(-5)
      expect(atm.balance).to be 20
      expect(money).to be 0
    end
  end
end
  ```



實作檔內容 (atm.rb)
```ruby=
#「不能領0元或小於0元」
def withdraw(money)
  if money > 0 and money <= @amount
    @amount -= money     # 等於 @amount = @amount - money
    return money    # 要有回傳值，否則回傳的是 @amount 而非 money 值
  else
    return 0
  end
end
```
重構實作檔
- 定義方法的目的：為程式碼賦予意義
```ruby=
class ATM
  attr_reader :balance  # 要注意所有傳進去的變數名稱都要使用 balance

  def initialize(balance)
    @balance = balance
  end

  def deposit(money)
    @balance += money if money > 0
  end

  def withdraw(money)
    if money > 0 and enough?(money)  # 加入下方的 private
      @balance = @balance - money
      return money
    else
      return 0
    end
  end

  private            # 不寫也沒關係，但目的是讓接手專案的人容易理解＆維護
  def enough?(money)
    money <= @balance
  end
end
```

---

### 在 RSpec 中使 Arrange 不用重複的方法
#### RSpec 提供一個 `let` 的方法以減少重複的語法
範例：
```ruby=
# 在同一個 context/ describe Block 中使用
let(:atm) { ATM.new(10) }
```
可省去 it 中：
```ruby=
atm = ATM.new(10)
```


範例：
```ruby=
describe "領錢" do

  let(:atm) { ATM.new(10) } 

    it "可以領錢" do 
      #atm = ATM.new(10)  => 使用let方法可以不用寫這個
      money = atm.withdraw(5)
      expect(atm.balance).to be 5
      expect(money).to be 5
      #在這裡面money可以寫中文
    end
      
    it "不能領 0 元或是小於 0 元的金額" do 
      #atm = ATM.new(10)  => 使用let方法可以不用寫這個
      money = atm.withdraw(-5)
      expect(atm.balance).to be 10
      expect(money).to be 0
    end
end
```


---

## 題外話們 for 測試
- 會寫測試的 QA 很值錢👀
- 會跟客人溝通且知道需求工時的 PM 會很受歡迎
  - 會測試的 QA 跟 PM 比寫 code 的工程師值錢
- 面試最後一問可以問：貴公司有沒有使用版控 (不是Git也行) 或在開發流程中寫測試？
- 老手跟新手交錯寫測試對新人發展和團隊合作有幫助
- Behavior-driven Development (BDD) 是一種 TDD 方法
  - 引述自 RSpec 官網：BDD 讓 TDD 更有效率也更有趣🧡
  - https://rspec.info  ／ RSpec 官方文件 （建議閱讀）
![](https://i.imgur.com/6it8JRE.png =450x)
- Unit Test 是測試的***最小***單位，針對一個有功能的特定模組或物件進行測試，例如針對一個 Class 做測試。(接著可以再對 Model, Controller, View 整體進行測試=>整合測試) - 

* 龍哥好書分享："Testing with RSpec"/電子書
https://leanpub.com/everydayrailsrspec


---

## Rails 環境設定
### 安裝 Rails
```shell=
$ gem install rails
```
### 開啟一個新的 Rails 專案
```shell=
$ rails new *專案名稱*
```

## 題外話 for Rails

- 授權方式
  - BSD授權條款 (Berkeley Software Distribution) / MIT授權條款 (MIT License)：
真開源、真免費
  - [MIT license 介紹](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89)
  - [BSD license 介紹](https://zh.wikipedia.org/wiki/BSD%E8%AE%B8%E5%8F%AF%E8%AF%81)
  - General Public License (GPL)：必須公開原始碼，不代表免費
  - Beerware啤酒軟體：介紹 https://bit.ly/3lUzLdL 

## 題外話 for 終端機Terminal

* 終端機就是那個黑色畫面只有英文字的介面/視窗
* sudo = 強制要別人做朋友
  * 定義：是一種程式，用於類 Unix 作業系統如 BSD、Mac OS X 以及 GNU/ Linux 以允許使用者透過安全的方式使用特殊的權限執行程式（通常為系統的超級使用者）。<執行一些只有系統管理員或其他特定帳號才能完成的任務。>
  * 使用情境：~~你的電腦其實不是你的電腦QQ~~可能會在你想安裝某個東西或是想修改某個檔案的時候，會出現Permission denied這種畫面，表示你的身分權限不夠，這時候可以在你想輸入的指令前面加上sudo，就可以暫時提升你的權限到root(系統管理員)，那行指令就能成功輸入了!