TDD重點觀念及摘要
主題: |
[.Net]TDD重點觀念及摘要 |
文章簡介: |
記錄TDD重點觀念及摘要。 |
作者: |
陳乾正 |
版本/產出日期: |
V1.0 / 2016.12.20 |
1. 前言
TDD,常被人誤解是測試的方法理論,也有許許多多的工程師不得其門而入,或者初接觸即認為只會增加工作負擔沒有實質助益中途作罷,甚者觀念認識不清走偏了方向,尤其在只重交付不重過程品質、成本導向的民風文化及速食教育環境,單元測試都鮮少被要求及灌輸應有概念下,TDD的推行及普及上一直還未得到很大的迴響及追隨。
但身為軟體開發產業的軟體工程師應該要有所體認,雖然技術會不斷的推陳出新,提升我們生產力,工具會不斷的改善改良,增快我們的開發速度,但品質沒有快捷鍵,任何的工具都只是輔助,惟有逐步踏實的將TDD學好、做好,不但可以達到快速交付、及早發現改正錯誤及做好事後保護,讓不管是原始打造的或是後續接手的人都可以輕鬆放心的開發修改程式,不用擔心因為一個異動修改造成了踩地雷或多花數倍冤枉時間來找臭蟲,最終降低開發維護成本及風險,人人早早下班快快樂樂回家才是康莊大道。
本篇不做工具操作介紹,只談觀念。
2. 目的
瞭解TDD、靠近TDD、擁抱TDD。
3. 開始前準備
Open Your Mind。
4. 關於TDD
4.1 什麼是TDD?
測試驅動開發(Test-driven development, TDD)是一種軟體開發過程的方法,是輔助軟體開發的一種程序,是由需求切入的程式設計方式,不是軟體測試的方法。
4.2 為什麼TDD會失敗?
大部份人都直接進入TDD或使用工具以為就是TDD,但缺乏前置的其他技術基礎,例如程式語言、物件導向…,最後覺得TDD難以使用。
4.3 TDD怎麼做?
TDD是一個不斷循環的過程,不論是在軟體開發或維護階段,持續重複著失敗(紅燈)成功(綠燈)重構的過程,簡單的說就是先寫測試(Test)再撰寫程式碼(Production Code),整個循環主要活動有:
(1) 撰寫測試程式
當一個新的需求情境(Scenario)產生時,針對該需求撰寫測試案例;若是全新的功能需求則以最快速簡單的方式撰寫產生邏輯處理方法介面,方法介面最簡單只須先有介面簽章不須實作內容,只要可對該方法介面進行測試案例撰寫即可。
(2) 執行測試且其結果為失敗
因新增需求邏輯處理程式碼尚未實作,此時執行該案例的測試其結果必是失敗的,若是成功表示測試程式碼有問題或測試案例設計有誤。
(3) 撰寫最少的程式碼讓測試通過
以最少的程式碼撰寫實作需求邏輯,只要能夠滿足通過該測試案例即可,看到那些需求就只實作那些需求,不要多想多寫其他多餘的邏輯或過度設計。
(4) 通過所有的測試案例
當每次實作需求邏輯及撰寫測試案例完成後,要確保所有的測試案例都是通過的,且最終所有的測試案例須能符合涵蓋到所有案例需求;若因增加了需求邏輯導致原本通過的測試案例失敗,表示修改的程式碼有誤或是測試案例己不符合情況。
(5) 重構程式碼
將程式碼做重組及調整,改善不好的結構設計或重覆的程式碼,使程式碼簡單好維護有架構,借由測試案例的保護重構後所有測試案例仍應是通過的。
當所有的測試案例都是通過時,就可以很放心的把程式碼做交付。
(6) 重覆循環並加入新的案例
引用自http://quintagroup.com
4.4 TDD測試怎麼做?
(1) 單元測試
a. 單元測試是最小的測試單位。
b. 只針對公開的方法(Public Methods)做測試,以需求為出發點。
c. 測試的目標與外部資源間不能有相依性,例如網路、檔案、資料庫、服務…。
d. 不測試具商業邏輯程式碼,例如資料的If Else、Loop…比對。
e. 一個測試案例只測試一件事情。
f. 測試案例彼此獨立沒有相依性。
g. 單元測試特性FIRST
• Fast:每個測試案例執行時間最好0.5秒以內,最多不要超過1秒。
• Isolated:每個測試案例目標都是獨立且隔離的沒有相依性,將測試案例聚焦在單純的環境下,不會因外部的因素影響到測試結果,提升測試程式的健壯性。
• Repeatable:每個測試案例都是可重覆執行的,且不會因上次執行影響到下次執行結果。
• Self-Verifying:每個測試案例都要能夠測試期望結果,並且在失敗時可以從結果看出失敗的原因。
• Timely:在建構程式碼之前我們應該都要知道想要建構的是什麼,所以所撰寫的測試案例就可看出建構程式碼的行為邏輯,而測試案例即是欲建構程式碼的第一個使用者,當程式碼好測試即代表著程式碼好使用,且完整的測試案例除了表示滿足需求外,也可在程式碼實作完成後立即驗證即早發現錯誤做修正。
h. 單元測試3A原則
• Arrange:初始化目標物件、初始化方法參數、建立模擬物件、設定環境變數、指定預期結果,或是預期與相依物件的互動方式。
• Act:實際呼叫目標測試物件的方法。
• Assert:驗證結果是否符合預期。
[TestMethod()] public void AddTest_first_1_second_2_should_be_3() { // arrange var target = new MyCalculator(); var first = 1; var second = 2; var expected = 3; // act var actual = target.Add(first, second); // assert Assert.AreEqual(expected, actual); } |
i. 單元測試驗證方式
• 驗證測試目標回傳值。
• 驗證測試目標狀態(屬性)的改變。
• 驗證測試目標與外部的互動(透過Mock物件)。
(2) Web測試
a. 使用HTTP封包錄製工具,錄製與Web UI或Web API互動的過程。
b. 修改替換HTTP封包內容關鍵資料,及設定驗證項目製作成測試案例。
c. 觸發執行測試案例進行自動化UI測試。
d. 部份工具提供可將錄製的測試案例轉化為單元測試程式碼使用。
e. 常見的工具有Selenium、FluentAutomation。
4.5 TDD應具備的程式技巧
(1) OOD/OOP
為了達到單元測試的標準及特性,必須借助物件導向的封裝、繼承、多型、覆寫的設計,歸納商業模型及賦予責任,透過介面的運用降低物件間的偶合性,達到可任意替換、隔離相依及單一職責,聚焦在測試目標的需求邏輯上;否則在傳統結構化程式設計或義大利麵條式的程式碼測試將難以施行或做到全面涵蓋。
(2) DI
運用依賴注入的技巧,可將測試目標相依的物件使用Mock偽物件注入,切開外部資源依賴及影響,被測目標單純化,亦增加系統的擴充性。
(3) Refactor
使用重構的技巧,將冗長或重覆的程式碼擷取成為私有方法,或將程式碼獨立成為類別方法職責分離,
4.6 TDD的其他
(1) 為什麼需要單元測試?
程式是照你寫的跑,不是照你想的跑確保跟你想的一樣。
(2) 開發常見的問題及解決方式
a. 測試環境無法測試、環境複雜Mock偽物件注入。
b. 有錯,但不知道哪裡錯了,花時間一行一行追蹤測試案例涵蓋需求。
c. 不知道那些程式有被測過,心裡不踏實交付即包含測試程式。
d. 改東壞西不自知隨時執行測試。
e. 平行開發相依彼此等待關注點分離,依賴介面。
(3) 物件直接相依的問題
a. 測試可能因直接相依而變慢。
網路、資料庫、檔案,其至相依另一個深不見底的服務,為了可重複執行,需要資料的初始/還原。
b. 相依物件發生問題時,會導致誤判測試的結果。
我關注的並不是你呀!!
c. 要等相依物件開發完成,才能測試目標物件。
開發/測試效率低落、發現問題的時間點延後,成本提升。
d. 無法測試
開發環境無法完全仿照正式環境、特殊商業邏輯,例如亂數。
(4) 測試涵蓋率
a. 至少要大於0%,代表產品存在相關的自動測試。
b. 檢視測試案例是否包含最主要的情境,以及曾經出錯的情境。
c. 檢視是否有不必要的產品程式碼或缺少對應的測試案例。
d. 相對趨勢大於絕對數字,鼓勵逐步增加測試比訂一個很高的標準來的好。
e. 童子軍守則: 讓出去的營地比進去時乾淨。
(5) 單一職責、依賴介面、依賴注入
單一職責、依賴介面、依賴注入
單一職責、依賴介面、依賴注入
很重要,所以說三次。
(6) 測試比例金字塔
在軟體發展過程中,理想的測試比例
(7) 程式碼循環複雜度
代表一個方法中程式碼分歧路徑,路徑越多表示複雜度愈高,也就愈難維護,建議值低於10,最多不超過15,超過應做程式碼重構。
(8) 重構
a. 重構前務必有測試保護(測試案例)。
b. 小範圍重構的效益,比大範圍重構來得有效
c. 一次只做一件事,勿一邊重構一邊加需求
(9) TDD的準則
a. 在沒有紅燈情況下,不應該更改程式碼,除非重構或新需求。
b. 已有紅燈情況下,應該去改正程式碼讓測試案例通過,而不是再寫其他更多紅燈的測試案例,紅燈可能來自於其他紅燈。
c. 修正程式碼使用測試案例可以通過就好,不要多寫其他不存在或不需要的程式碼,避免過度設計。
d. 著手寫程式碼之前應該與客戶或SA確實確認需求情境,若連測試案例都寫不出來,就會不知道自己在寫什麼程式碼,確保想清楚再寫程式,可以運用User Story引導出需求。
e. 需求情境一定要有範例,測試案例來自範例,需求情境就等於使用者接受測試。
4.7 寫在最後
回到最初,TDD,以測試驅動開發,讓測試保護異動,先寫測試案例再實作商業邏輯程式碼,讓撰寫測試程式碼變成一種習慣,世界其實可以很美好。
5. 參考來源
(1) TDD課堂筆記。
(2) 30天快速上手TDD (91)
(3) 程式設計師升級必練內功:TDD Kata