【TDD】課堂心得與筆記 - Day 2

91's 『自動測試與 TDD 實務開發(使用C#) 第八梯』 第二天課程筆記與心得,課程主題為 Web Testing 和 Refactoring,完全實作,使用程式碼說話。

一、回顧與展望

課程回顧

       各組寫下有映象的關鍵字,然後 91 會請你說出一個關鍵字並解釋意思,驗證你說出來的是否正確。

       條列關鍵字,複習與思考使用:Why Unit TestFIRST3AUnit Test 3 種驗證DI / IOCStubMockExtract And OverrideCode CoverageTest InternalIsolated Unit Test

       以上關鍵字都應該要有能力口述讓人明白意思,這樣才算是初步的內化與外顯。另外,91 特別強調 TDD 是程式開發與設計的方法,不要把 TDD 當成是寫測試程式。

 

作業回顧

       這次作業為「分頁取值」,91 故意出簡單題目,還送你測試資料與測試案例。用意是為了讓我們克制不要衝動把 production code 寫完,養成先寫 Unit Test 的習慣。明知道這邊加上一個 if 就完成了,明知這邊加上 for 就能撈出所有資料,請克制自己,還沒寫到相關的測試個案,內心要先當作沒有這個需求,只能針對紅燈寫相關功能。

       將 TDD 練功過程當作人的一生,剛學習 TDD 的人就像嬰兒一般,正在學習走路,左搖右擺看起來走的不穩,但確實一步步向前進。練久了漸漸就會跑了,到時候在邁開步伐勇敢向前吧,如果邁開步伐摔倒了,發現不合適的點後再縮小步伐。這次作業 91 希望我們像 baby step 般,先寫測試程式再逐步完成課題。91 另外說到,TDD 練功就像小腿綁鉛塊,等你實務上需要時,鉛塊拿掉,很大的機會寫的又快又好又可靠維護性高的程式。利用 TDD 練功的過程,反思程式碼的設計,然後寫出更好的程式碼。

       91 code review 後,認為最大的問題是命名很爛,測試案例是用來描述需求,所以命名應具備 domain 的意義,保持語意是重點。整體建議回饋如下。

       91 提供的本次作業重點(節錄至 91 於 github 上的 comment):

  • 呼叫端怎麼呼叫最好用,語意清楚,參數夠少,intellisense 輔助。
  • API 在需求異動時,能越穩定越好,代表支援有足夠的彈性。在面對重構時,也不會影響到呼叫端的使用,例如 property rename。
  • 測試案例要怎麼呈現,最像口語化的描述著呼叫端使用情境。包含怎麼寫語意最清楚,寫起來最快。

       另外 91 提供了 C# 的命名建議(節錄至 91 於 github 上的 comment):

  • 所有重要的、公開的,都是 Pascal,首字母大寫。
  • 其他的,都是 camel,首字母小寫。
  • 不用縮寫用全名。
  • Class/Interface 是單數名詞,function/method 是動詞,且不暴露太多實作細節,應該抽象一點。
  • 變數命名通常就是 type 的 camel 命名,例如 var order = new Order();;如果為集合,變數就是命成複數名詞,例如 var orders = new List<Order>();

 

展望

       上週課程主軸是 Unit Test,然後帶到重要的 Isolated Unit Test。第二天的課程主題是:

  • 測試顆粒最粗的 end to end 測試,在網頁上我們稱之為 Web Testing。
  • TDD 三大步驟之一「重構(Refactoring)」。

 

二、介紹 End to End Test - Web Testing

       什麼是 Web Testing 呢?是 end to end 測試,因為在網頁上所以又稱為 Web Testing,其精神是模擬使用者在網頁上操作方法,驗證程式碼是否如預期的執行。

       明明有 Unit Test 了,為什麼還要 Web Testing 呢?就像是請 A 去買一盒螺絲,請 B 去買一支螺絲起子,結果買回來才發現十字的螺絲起子無法轉動一字形的螺絲。這就像是通過 Unit Test 的前端網頁與後端 api,要組合才發現異常,而 Web Testing 就是提早發現這樣的問題。

       寫網頁程式的最討厭的是不同瀏覽器可能會有不同行為,為了讓使用者有良好體驗,花費很多心思設定,讓使用者於不同瀏覽器上使用起來一模一樣。設定完還要花費很多心力手工測試,以確保各瀏覽器行為一致。改用 Web Testing 不但可以減少手工測試,還可以提早找出問題。Web Testing 貫穿前台與後台(含 DB)的 end to end 測試,高度相依環境,也能驗證不同執行環境但執行結果一樣。譬如測試機執行成功,但改到正式機卻執行失敗,這種問題也能靠 Web Testing 找出問題。

       但 Web Testing 也是有缺點的:

  • 因高度相依於環境,執行速度慢。
  • 若瀏覽器版本更新,程式中操作瀏覽器的套件可能要更新。
  • 因測試顆粒粗,很大機率只能發現問題而無法詳細定位發生問題的位置。

       91 課堂上有說明,如果 Web Testing 發現問題而無法定位問題點,又每個 Unit Test 又沒問題。建議改用範圍較小的整合測試,優先針對重要的 Service 層和 Data Access 層進行整合測試,若沒有問題那就是前面的問題,用這種方式逐漸逼近問題點。如果遵從實務 TDD 開發,前端與後端程式都會有整合測試(Integation Testing)和單元測試保護,到 end to end 階段較可能發現環境上的錯誤或前後端串接這種錯誤。如果 end to end 階段發現重大 BUG,要回去反思是否少了整合測試的測試個案。

       另外, Web Testing 不是絞盡腦汁努力想出所有可能性,針對重要或使用者常使用的功能進行 Web Testing 測試即可。還有一個問題是當你微調畫面就要改動 Web Testing 程式碼,感覺很麻煩?其實這才是正常的且重要的警訊。如果你們網頁是常改版常異動 UI,譬如每年都改版一次,到這種頻率可考慮不要用 Web Testing,但後端程式的整合測試就變成非常重要。

       另外也有聽過一種說法,Web Testing 就是將繁雜的手工測試自動化,這也是不錯的思考切入點。

 

三、Web Testing - Selenium IDE & Web Driver

簡述

        在 Web Testing 中,最有名的 end to end 工具就是 Selenium(取自化學元素中的『硒』),分成 IDE 與 Driver 兩種。

 

Selenium IDE

        Firefox 上的擴充套件用途是錄製操作網頁的過程並驗證預期結果。只要開啟 Selenium IDE 後,操作網頁過程都會被記錄下來,最後加上驗證項目,確保執行結果和驗證所想的一致。

        安裝與基本操作請參閱 Yowko 寫的使用 Selenium IDE 與 C# 做 Web UI 測試。找了一個 Demo 影片供大家參考,也請大家手動操作看看,操作幾次後對下面文章會有更深的體會。

        從影片知道,一個不會寫程式,只要會操作網頁的人,都能錄製。好處是手工測試人員可以將操作過程錄製下來,然後交給開發人員,還原問題發生前的操作過程。也因為真的很簡單,公司要導入 Web Testing 時可先導入 Selenium IDE,團隊接受度較高,尤其是不會寫程式的,就手工測試時順便錄製。其優點關鍵字為 SAFE,分述如下:

  • Simple(簡單):點擊錄製,進入網頁,操作畫面,關閉錄製,選擇驗證項目。
  • Auto(自動):錄製好的可以儲存或匯出,可以一直重複播放錄製內容。
  • Fast(快速):不管是錄製速度和播放速度都很快。
  • Export(匯出):可以將錄製結果轉成程式碼,複製貼到測試專案內,版控進 CI 就能一直跑一直跑了。

        關鍵在於 Export,Selenium IDE 匯出支援 C# 的 NUnit,也支援 JAVA、Python、和 Rudy 語法下的測試框架,但沒支援微軟的 MSTest。此點 91 有提供 MSTest 的 Formatter,請參閱 [Tool][Selenium IDE]Export to C#/WebDriver/MSTest,讓 Selenium IDE 支援 MSTest。匯出的程式碼貼到測試專案中,建議於 NuGet 抓取 Selenium WebDriver,將預設瀏覽器改成 Chrome,可以少踩很多雷,修改片段如下。

[TestInitialize]
public void SetupTest()
{
    // driver = new FirefoxDriver();
    driver = new ChromeDriver();
}

        另外一個建議,如果錄製過程中遇到輸入帳號密碼,請不要將帳號密碼錄製進去,因為這會有資安問題。Selenium IDE 錄製可以中斷並手動輸入帳號密碼後,再自動往下跑後面流程。另外,Selenium IDE 內建排程功能,可以設定什麼時候執行你錄製好的程式,可別拿去做搶票功能阿。

        Selenium IDE 問題

  • 只支援 Firefox,無法錄製實務中常見的 IE only 的網頁。
  • Export 出來的程式碼很醜,不夠語意化,每次調整畫面勢必要調整這些很醜的程式碼,不好維護。

        基於以上問題,如果只是想錄製手工測試,用來還原問題,推薦使用 Selenium IDE。如果想要在 CI 上長久的跑自動化測試,建議還是自己寫 code。

 

Selenium Web Driver

        Selenium Web Driver 用途是讓程式可以操作瀏覽器,並依據你所寫的程式自動執行瀏覽器,達到 end to end 的自動化測試。於 NuGet 搜尋 Selenium 會發現一堆東西,主要下載 Selenium.WebDriver 和 Selenium.Support 這兩個。當你將 Selenium IDE 匯出放進測試專案時,也是要參考這兩個元件。不管使用 Selenium IDE 匯出程式或自己寫程式,因為 Selenium 抓取畫面元素就是一行又一行 FindElement,所以程式碼真的不好看。Web Testing 是描述使用者如何操作網頁,可惜的是 Selenium 語法不夠語意,很難清楚描述出意圖,無法清楚表達需求與意圖的測試程式都是不合格的測試程式。為了改善這個問題,推薦使用另外一個套件 FluentAutomation。

        另外,若瀏覽器版本更新,有機會 Selenium Web Driver 會無法使用,記得 Selenium Web Driver 也要更新。當寫好程式發現運行異常,記得確認瀏覽器版本和 Selenium Web Driver 版本。

 

四、Web Testing - FluentAutomation

簡述

        FluentAutomation 其背後就是 Selenium WebDriver,其用途是解決 Selenium 程式碼不夠語意之問題。因為其程式寫法有如人和網頁溝通聊天一般,相當好上手,也容易讓第一次接觸的人也能輕易理解內容。讓測試程式碼更接近自然語言,更接近 spec。

 

設定

  • 使用方法為至套件管理主控台輸入「Install-Package FluentAutomation.SeleniumWebDriver」和「Install-Package Selenium.WebDriver.ChromeDriver(安裝這個才能使用 Chrome 瀏覽器)」,就可以安裝好相關套件。
  • 測試程式繼承 FluentTest
  • 建構子中加上 SeleniumWebDriver.Bootstrap(SeleniumWebDriver.Browser.Chrome);。其中 Chrome 可以改成其他瀏覽器名稱,建議使用 Chrome,地雷較少。

 

程式

        簡述說接近自然語言,接近 spec。口說無憑,直接來一段 Demo Code:

I.Open("https://www.google.com.tw/")
    .Enter("自動測試與 TDD 實務開發").In("input[name='q']")
    .Press("{ENTER}")
    .Assert.Text("skilltree.my");

        此測試程式意圖淺顯易懂,我【I】打開【Open】瀏覽器並進入 Google 首頁,輸入【Enter】字串「自動測試與 TDD 實務開發」於【In】搜尋框(備註:google 輸入框的 name = q)中,按下【Press】Enter 按鈕,期望【Assert】看到「skilltree.my」這段文字【Text】

        FluentAutomation 程式一律為 I 起頭,意思是「我」到底要於瀏覽器上做什麼事呢?以「我」為出發點,以「我」為中心思考。下面列出我常使用的語法。

  • 輸入值:I.Enter("abc").In("#id"); (我在 id 欄位輸入 abc)
  • 點擊 DOM:I.Click("input[type='submit']"); (我點擊 submit 按鈕)
  • 驗證:
    • URL:I.Assert.Url("http://www.google.com"); (驗證現在網址是不是 google 首頁)
    • 文字:I.Assert.Text("xxxx").In("#message"); (驗證 #message 上的文字是不是 xxxx。可省略 In,這樣會全畫面搜尋是否存在 xxxx 文字)
    • 存在:I.Assert.Exists("a[href='http://www.google.com']"); (期望頁面上存在 google 網址超連結)
  • 快照:I.TakeScreenshot("{{快照檔名}}"); (請事先於建構子中設定 Config.ScreenshotOnFailedAction(true).ScreenshotPath("{{存檔路徑}}");,開啟拍照功能)
  • 按下 Enter:I.Press("{ENTER}"); (按下 Enter 鍵。發現多瀏覽器執行和 Chrome 以外瀏覽器常異常)

       剩下功能可以自行去官方文件查找。

        補充:「按下 Enter 鍵」有兩種寫法,單一瀏覽器執行可使用 I.Press("{ENTER}");,多瀏覽器請使用 I.Append(OpenQA.Selenium.Keys.Enter).To("Selector");

 

結論

        大家注重 production code 要寫的好讀易懂,測試程式也是一樣。Selenium 寫起來太像程式,FluentAutomation  多一層封裝並將測試程式抽象,除了開發人員也好寫外,其他人閱讀程式時也能快速了解其意涵。

        FluentAutomation 是有一些問題,多瀏覽器或 Chrome 以外瀏覽器常發生異常,又很久沒更新,連官網都掛了,只剩下 Github,但真的很好用,在找到替代品之前就繼續使用了。完整使用 FluentAutomation 使用範例可參考 91 寫的 [Web Testing][Tool]FluentAutomation (More Behavior-Driven)

 

五、Web Testing - Page Object

        Page Object 是我認為第二天課程中最重要的兩項之一,Page Object 是一種 Pattern,嘗試使用 Teddy Chen 部落格上常看到的 Pattern 格式整理。

  • Name(名稱):Page Object
  • Context(問題背景):調整 UI 時,Web UI Testing 程式也要調整。
  • Problem(問題)
    • 花費時間確認要改哪些 Web UI Testing 程式。
    • 可能要修改多處雷同程式碼(複製貼上)。
    • 確保  Web UI Testing 程式沒有少改。
  • Force(限制條件)
    • DRY - Don't Repeat Yourself.
    • 讓 Test 更趨近於 Specify,更接近自然語言
    • 必要時只異動一個地方(class)
  • Solution(解決方案)將一個頁面封裝成一個物件,將頁面模組化,把該頁面互動行為全部放到該物件中
  • Resulting Context(套用解決方案後的變化)
    • 「使用者」與「網頁」互動的行為更抽象化
    • 職責分離

        橘色底線是 what,綠色底線是 why。

        將 Page Object 概念繪製成圖。下圖 Page Object 對 SUT 我是寫「操作」而非「驗證」,因為 Page Object 確實會依據你寫的測試程式來操作 SUT,但是不是由 Page Object 驗證 SUT 有兩派說法。主張 Tell, Don't Ask. 的人會將 Assert 寫在 Page Object 中;另外一派是說 Page Object 職責是封裝頁面操作行為,並不包含驗證邏輯,且行為混雜驗證會讓 Page Object 臃腫肥大。

        Page Object 一樣是使用 FluentAutomation 這個套件實作,不選擇 Selenium WebDriver 是因為程式碼不好看。先去官網看一下解說與範例,恩,範例真的很好懂,但缺少了一些前置作業,也不容易看懂哪裡有行為抽象與職責分離,以下利用官網內的範例進行解說。

  • 加入 FluentAutomation 參考,測試程式繼承 FluentTest。
  • 依據 3A,撰寫測試程式。以官網例子,大致寫法如下。切記,測試程式只要抽象撰寫就好,譬如只寫搜尋,搜尋背後的詳細描述不應該寫在這邊。可參考官網範例內的 SearchForFluentAutomation() 這段。
    • Arrange:到 Bing 搜尋首頁。
    • Act:搜尋 FluentAutomation,顯示搜尋結果。
    • Assert:驗證搜尋結果。
  • 利用 IDE 自動產生「搜尋首頁(BingSearchPage)」和「搜尋結果頁(BingSearchResultsPage)」這兩個 Page Object,並繼承 PageObject<T>,其中 T 為 Page Object 的 class name。再利用 IDE 快速產生建構子與方法。
  • 於 Page Object 中,從 I 開始撰寫使用者與 SUT 的互動行為。譬如測試程式中只寫搜尋,那對應的 Page Object 中要詳細描述「我在哪裡輸入了什麼資訊,然後按下什麼東西」。

        結論:Page Object 讓測試程式增加一層抽象,讓測試程式更好閱讀,職責分離,也能降低測試程式複雜度。也因為將所有 UI 元素封裝進去,所以 UI 調整,只要調整對應的 Page Object 就好。另外,基本上一個頁面一個 Page Object,但不是絕對。若有共用部分,可以再往外面抽出共用。也可以針對 Panel 去實作一個 Page Object。切記不要 DRY就好。

 

六、測試金字塔

主要有三種類型測試:Unit Tests、Integration Tests、和 End-to-End GUI Tests。

  • Unit Tests(單元測試):基於每個 public 和 internal 的 function/method 進行測試,顆粒最小,有異常能馬上發現問題點。
  • Integration Tests(整合測試):用於模組間或各分層間的測試,顆粒次粗。在有 Unit Tests 下,若 Integration Tests 異常代表夾縫接合處有問題。
  • End-to-End GUI Tests(端到端測試):顆粒最粗的測試,串接所有重要的流程,確保所有模組與分層串接上沒有問題,也能確保程式不會因為環境因素造成異常。

總體測試程式中,這三種測試程式佔比多少呢?這邊就要說一下測試金字塔。

 

上圖資料來源為 Inverting the Testing Pyramid

        圖中左邊倒金字塔是手動測試,一般手動測試中,end to end 佔所有測試 80% ~ 90%,比例相當的高。前面有說 end to end 測試有兩大問題,執行速度慢且可以發現問題但很難發現問題點。意思是生命都浪費在無謂的測試上了。

        圖中右邊是寫測試程式,單元測試是每個 function/method 的保母,理所當然整體佔比最高,約 70%,往上整合測試與 end to end 測試越來越少。基本上從 Biz Logic Acceptance Tests 到 End to End Flow Tests 之間的測試都是所謂的整合測試(Integration Tests),佔比 29%。剩下 1% 才是 end to end 的 UI Tests(網頁上又可稱做 Web Tests)。

        課堂上 91 說自己的經驗,大概如下:

測試粒度(由上至下越來越細) 測試案例個數
End-to-End UI Testing 10%
Integration Testing 20% ~ 40%
Unit Testing 70% ~ 50%

        基本上 DB 的 Test Case 較多,譬如一堆表單要 CRUD,這時候你的程式整合測試比例就會變高。另外有些 Controller 只是單純呼叫 Service 然後將資料拋給前端,這種基本上也會使用整合測試進行測試。遇到 AOP 或 Action Filter 這類型程式,除了針對 AOP 或 Action Filter 本身功能進行 Unit Testing 外,被掛上功能的功能也能用整合測試,確保掛上去的功能正常運作。還有一種情況,使用 GOOS 開發模式,整合測試比例也會比較高。

        不是寫不出單元測試就用整合測試,寫不出單元測試很大原因是你程式本身設計有問題,整合測試多用於程式邊界上與其他物件(DB、Api、模組、分層... 等)間的互動。可以看看公司專案,測試案例個數比例是否異常,若有異常很大的原因是程式碼本身有問題。

 

七、重構(Refactoring)

前言

        重構是 TDD 三大步驟中的一環,重構是不改變程式外在行為下改動程式碼。或許你會疑問,為什麼 TDD 過程要綁定重構這步驟?或者問說如何確保重構過程中不改變程式功能?先回答前面問題,TDD 能驅動你寫出還不錯的程式,寫出不錯的設計,但這樣只是不錯,剛好及格。要再讓程式昇華至更高境界,只能仰賴重構程式,讓程式變的更好。TDD 高手們每次綠燈後都順手重構程式碼,其因素是因為有測試程式保護,確保等會的重構不會改變程式功能。第二題的答案就是利用測試程式保護程式功能,讓我們可以安心的放手去重構程式碼,讓我們的程式碼推到 80 分、90 分、或更高分。也因此,重構技巧已經是開發人員必備的技能之一。

        大家都知道程式多人修改後,隨著需求增長會慢慢歪掉,甚至可能改不動了。為了讓系統活更久,我們要給程式保養,利用重構讓系統維持一定的可讀性、可修改性、和可擴充性。在重構過程中,依據 Code Smell 進行重構。怎麼是 Code Smell? 此術語出現於經典書籍 《Refactoring: Improving the Design of Existing Code》 內,書中將程式碼比喻為尿布,聞到尿布臭了就換掉它。

        童子軍原則告訴我們,簽入時程式碼要比簽出時還要乾淨整潔,沒有怪味道。代表重構絕對不是單獨的作業,此項作業應融入日常開發中。若採用 TDD 開發模式,自然而然就會將重構納入日常開發中。現實中不是完美的,尤其是維護人員,手邊應有很多沒有測試程式的系統,面對這種系統該如何重構呢?這也是 91 第二天課程下午的主軸,如何重構 Legacy Code。

        很多人認同使用測試程式保護程式,確保重構過程中不會改變程式外在行為。但重構 Legacy Code 時,常會遇見:

  • Legacy Code 好醜,有一堆 Code Smell,我要重構程式碼。
  • 準備幫我這次要重構的 Legacy Code 加上單元測試。
  • 糟糕,Legacy Code 太爛無法加上單元測試,加上測試前要先重構。
  • 重構前要先加上單元測試。

        這進入了死結,標準的先有雞還是先有蛋。

 

重構 Legacy Code

        如果需求是加上新的 class 而不會異動現有程式,恭喜你,利用 TDD 流程完成需求吧。一般這種好康的需求只會發生在你同事身上,不會發生在你身上,所以還是學習如何重構 Legacy Code 吧。

        彙整 91 課程範例之步驟,這些步驟能運用於任何 Legacy Code 上。

  • 先說聲 Fxck 

                此為程式碼品質度量指標。

  • 加上顆粒粗的測試程式

                課程中為範例程式加上 Web Testing(使用 Selenium IDE)。因為程式太爛無法加上顆粒細的測試程式,所以加上顆粒粗的測試程式,確保程式執行沒有問題。

  • 讀懂你的程式

                了解每段程式其背後所代表的意義。Legacy Code 一個 function/method 很可能不只一件事,甚至暗藏很多邏輯與判斷條件。先讀懂程式碼意圖後,可利用 comments 為你現有的程式分段一下。碰到真的很爛的程式,恭喜你,這一關應該會卡很久很久,因為會讀不懂。閱讀程式碼時,也要去找變更點與測試點。找到哪裡可以變更,最簡單的方法是找到一件事並將其抽出來成為一個方法。另外要思考那邊可以插入測試,或提出取來的方法能否測試,思考這段程式碼要如何異動。

  • 打破依賴關係
    • Use ViewModel. (解耦 UI 和後端程式的好方法,尤其是 Web Form,將畫面與 Code Behind 解開。)
    • Extract Mathod. (先將一團程式碼拆分成一個又一個 method。)
    • Move to Class. (將一個又一個 method 分類放到 class 中。)
  • 寫 Unit Test

                針對提出獨立的程式碼撰寫 Unit Test。

  • 修改與重構
    • Create Interface. (觀察剛剛新建的 class,因為是從同一 Legacy Code 移出來的,有關連的機率很高,給這些 class 一個 Interface。)
    • 套用設計模式,譬如使用工廠模式決定呼叫那個 class。
    • 確認這些新抽出來的 class 是否好擴充,符合 SOL(L)ID。

        記得,只要異動程式碼,請頻繁的跑錄製的粗顆粒測試程式,確保出意外可第一時間發現。另外,上述畫線各步驟內容只針對本次課程用到的手法說明,若學習各步驟下實戰手法,推薦去看《Working Effectively with Legacy Code​》。該書中光打破依賴的技術就寫了一整章,共有 24 個方法,各位只能自己去看書了。

        91 課堂上拆分步驟更細,可參閱 Mystic 寫的 [91大的TDD課程心得] Refactoring

        大家還記得第一天有提到針對 Legacy Code 有一招超好用的技巧 - Extract and overreid,雖然這次沒用到這技巧,但 91 準備了另外一組範例,第三天課程一開始就要練習這個技巧,讓我們面對 Legacy Code 時更有信心去重構程式碼。

        上課範例衍生一些程式設計議題,如何加上新功能?課堂是說到新功能要符合開放封閉原則,然後於工廠內新增判斷即可。雖然有機會讓工廠內程式變的更醜,那就更醜吧,犧牲工廠換取其他程式碼乾淨。另外探討假設不同情境,呼叫方法所需參數不同,那應該要怎麼做呢?這一題全班講了一堆辦法,譬如工廠內組 DTO 再利用建構子傳入參數。課後 91 提供的另一種寫法,利用 static factory method,封閉建構子改用靜態方法接收參數並回傳自己。不管哪種作法,意思是工廠要髒就讓他髒到底,不要污染其他程式碼的概念。是否覺得將 Legacy Code 變漂亮後,重構這關要思考的點無窮無盡,那重構到怎麼地步才能停呢?

 

重構到什麼時候

        每個人的程度不同,重構到認為可以停就可以停了。基本上簡單的衡量標準是程式是否符合「簡單設計」,是否有「SOL(L)ID」風格,是否符合「Clean Code」,是否有「Code Smell」,自己認為可以了就可以停了。

 

推薦書籍

        91 推薦以下書籍,建議由上而下閱讀:

  • 《Working Effectively with Legacy Code​》:經典名著,對付 Legacy Code 一定要閱讀此書。簡體中文版叫做《修改代碼的藝術》,但簡體中文版已經絕版了。沒有繁體中文版,可以買英文版來看看。
  • 《Refactoring: Improving the Design of Existing Code》:經典名著,記載常見的 Code Smell 與對付 Code Smell 手段。繁體中文版叫做《重構─改善既有程式的設計》,還買的到。
  • 《Refactoring to Patterns》:將重構與 Pattern 結合,將實務常使用的重構手段以 Pattern 形式寫出。繁體中文版叫做重構-向範式前進

 

結論

        重構時機應融入日常開發中,伴隨每一次的簽出,都納入重構思維,讓程式簽入時變的更好。重構目的是因為工程師潔癖,無法容忍難看惡臭的程式碼存在。為了達到這個目的,要學習分辨 Code Smell,學會消滅 Code Smell,學會寫出 Clean Code,學會消滅一堆判斷條件(一堆 if ),學會各式各樣的設計(設計模式、簡單設計、SOL(L)ID、DDD...等),為了命名要擴充單字量。太多技能要學習,每項技能又需要去看好多書。為了自己的職業未來,還是去學習吧。

        很多人思考如何用 TDD 驅動出設計模式,如何用 TDD 驅動出演算法。好的測試個案確實能讓你接近,但要有簡單又高效的演算法或套用漂亮的設計模式,最終還是要靠重構。使用 TDD 的好處是有測試程式保護下,給你安全的環境,讓你放心的去重構,那怕是打掉重新寫演算法也可以,因為你有測試程式協助,確保不會異動功能。

        不要學會重構技巧就想拿 Legacy Code 開刀,遇到需求在改。寫新的程式時,藉由 TDD,先讓程式能動,TDD 也能驅動出 SOL(L)ID 風格的設計,然後再藉由重構追求程式碼的可讀性。切記,不只是 Production Code,所有的測試程式也要重構。

        重構是一門又深又廣的一門學問,主旨是要達到 Clean Code 境界。我們都知道,不可能一開始就完美設計,只有透過持續的重構,在重構的過程中逐步改善程式碼。養成這個習慣,久而久之就會寫出有可讀性、健壯性、和擴充性高的程式碼。

 

八、心得

        這麼文章超難寫,因為第二天課程都在寫程式,然後程式碼每個段落都是重點,感覺 91 用程式碼來告訴我們實務會遇到的問題與觀念。尤其是下午的 Page Object 和 Refactoring,兩個範例就講了一個下午,每到一個段落 91 會一邊 Demo 一邊講解,絕對要放下手邊事物專心聽講。用程式碼交談,看到範例會讓我感覺踏實,看的到摸的到。

        所以這篇文章才會寫這麼久,回來一邊寫作業,一邊再次練習課堂範例,然後邊寫程式邊思考,將課堂範例操作步驟抽象化,思考每段程式碼其背後所表達的概念,還去翻了一下修改代碼的藝術。相當充實的一堂課,回來一個星期努力消化所學,但還是要寫 Blog,持續內化學習到的東西。

        這堂課的主題就如同我文章的 Tags:

  • Web Testing:測試粒度最粗的測試,模擬使用者操作畫面的步驟,驗證程式是否如我們預期的和使用者互動。
    • Fluent Automation:在測試程式與 UI 間再加上一層,用途是將測試程式語意化,彷彿像對話一般互動。有些公司會自己寫這一層,封裝 Selenium WebDriver,讓程式碼更好讀易懂外,若未來出現比 Selenium 更好用的工具,隨時都能抽換核心而不用改動測試程式,這才是墊這一層的主因。
    • Page Object:將 UI 元件從測試程式中抽離出來,將使用者與 UI 互動情形放入裡面,做到職責分離。若有調整 UI,只要找到對應的那一支 Page Object 調整就好。
  • Refactoring:講也講不完的主題,只要知道技術債總有一天要還的就好。除了自己種下的技術債外,別人種下的技術債,總是會很莫名的是變成自己去還債。
  • 特殊的 Unit Tests:91 提供 MVC 中 controller 和 Web Api 中 MessageHandler 如何做單元測試,此為補充教材,提供範例程式碼和 Tips 供我們自行練習。

        想讓別人讀你的程式碼像讀文章一樣毫不費力?這堂課上完讓我們知道自己不足之處,也是一大的收穫。