# 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(系統管理員),那行指令就能成功輸入了!