您的軟體架構夠敏捷嗎?(三)- 使用 TDD 實現最後的設計

圖片取自:https://2.blog.xuite.net/2/8/9/b/11597010/blog_3502284/txt/218534111/5.jpg

前言

在前一篇文章『您的軟體架構夠敏捷嗎?(二)- 持續演進的軟體架構』一個持續演進的軟體架構我們其實做到一半而已,而在上一次,我們針對一個 ATM 自動櫃員機 的需求做了初步的分析,注意了,只是個『初步』的設計,這個設計其實還看不出來程式會怎麼寫,但敏捷又不希望我們在初期進行過多的設計,但是這不代表我們完全不設計啊?這一點我在第一篇文章:『您的軟體架構夠敏捷嗎?』有特別禪述,這裡就不再說講。

下面,我們就繼續將這最後一塊拼圖給完成吧~


測試先行的軟體架構

(1). 單元測試的最小粒度

在這裡,我們使用 TDD (Test Driven Development) 為主要的開發方法,TDD 是先寫測試、爾後再撰寫實際的程式碼,這部分我想會讀到此篇文章的讀者們應該都清楚這個部份了,還不清楚可參考訪間相關的 TDD 測試驅動開發相關說明,本篇就不再詳述。

重點來了,既然是撰寫單元測試 Unit Test、那麼單元測試不就是寫一段程式碼、來去測試一段程式碼嗎?其實這樣說並不完全正確喔!如果你今天是先撰寫產品代碼、爾後才撰寫測試的程式碼(當然這就不是 TDD 了)這樣說或許還算正確,但是 TDD 指的是一種『開發的方法論』,不單只是撰寫一段程式來測試另一段程式那麼簡單,

因為許多的開發者一提到『單元』測試,就認為單元就是指一段程式碼,但其實不是的,這裡的單元指的是應該是一個『行為』的『單元』,它是一個獨立的、可驗證的行為。它必須對系統產生可觀察的影響,而這就是我們這裡定義的單元測試的最小粒度,當然,同樣為確保測試的獨立性它又不與其他的行為有耦合情況發生。


(2). 要怎麼測試先行?

很多教你的 TDD 沒有人在講 UML 啊?沒有關係,我這邊慢慢教你怎麼開始?

要開始撰寫測試先行的程式碼,我們也得要知道這個系統到底要要幹嘛?對吧?根據前面的 Domain Modeling 就是我們這個系統目前的全貌的樣子,當然這個全貌還不清楚,但最少有個基本的方向,而且方向在第一個最小可行性產品的增量是可以被評估出可以做出什麼,對吧?而我們一開始,也沒有花太多的時間,進行這些 UML 的設計,對吧?算一算,我這裡繪製前面那幾張圖的時間也不過花了不到三小時的時間。

要找出一個可驗證的行為,其實非常的簡單,基本的 OOA/OOD 其實都有告訴你過該怎麼做!UML 裡,基本的方式就是 Sequence Diagram 了,我們的第一張 Domain Modeling 只有介面、能夠與之互動的,也只有介面而已,但這樣就夠了。

回到原始的 ATM 自動櫃員機 的需求,我們依照銀行的客戶,可以透過【插入提款卡】、【輸入密碼】、並【提領現金】的主要需求,分析顧客可以在 ILoginService 進行什麼動作?提領現金這件事又可以在 ICustomer & IDeposit 上發生那些可觀察的行為與結果?

圖(一)、ATM 自動櫃員機 需求主要 Sequence Diagram


最小批次構建

在上圖的 Sequence Diagram 中,我們從一連串的領域物件 Domain Objects 傳遞訊息與互動的過程來定義每一個 Domain Object ,以 Sequences Diagram 中的最小工作單元即是一個 Method,但事實上,當然這個工作單元可以再切得更小,但是在敏捷開發裡面的『小』究竟是多小還是取決於該工作單元可以展現出一個可觀察可度量的結果。

有時候,有些工作可能需要費時數日、甚至數周才能有可觀察的行為出現,某些因為需要將工作單位變得更大,但抵較少見,大部分是我們得將供作分割得更小,但是正確的來說應該是要在兩者之間取得一個平衡會比較好。這麼一來,我們也才能夠在比較短的時間獲得使用者的反饋、也能夠在比較短的時間內得知產品的發展方向是否符合使用者期望、也比較能夠爭取到調整的空間與時間。


開始撰寫單元測試

現在,我們知道顧客需要登入 ATM 提款機 [ 登入方法:Login()]、系統必須確認是不是本行的客戶 [檢核帳戶身分:CheckIsCustomer()],我們可以開始撰寫測試,因此,我們可以假定 Login() 方法得傳入 AID 與 Password,AID 可能由卡片中並由讀卡機讀取,但本篇只是假定讀卡機已經讀到該卡片的所有人是誰,身分證號是多少等。



當我們開始撰寫測試,我們經由 Sequence Diagram 分析的行為可以得到我們想要的預期結果,也就是 Login 方法得要最少傳入 Password 密碼,但我們希望傳入的是一個 IAccount 這個介面,所以這時候我們才會反過來 Update 前面的 Domain Modeling 裡的 IAccount

圖(二)、滿足 Login() 測試行為所需要的 Password 等輸入條件

但是只有介面我沒有版法實體化,所以這證明需要一個 Account 物件來實體化 IAccount

圖(三)、滿足登入行為所需要的實例化的登入帳號物件

最後、我們完成的 LoginService 的 Login 方法的紅燈測試結果如下:

圖(四)、完成第一個紅燈測試

最後..

由於 Login() 方法還未實作,當然會收到 NotImplmenetedException 的錯誤訊息,所以在 TDD 的開發模式裡面,我們只先確定軟體該做什麼,但是應該是由測試來決定 Production 程式碼該做什麼,粒度可以定義到每一個基本的單元(可定義可觀察且獨立的行為)為一個基礎的單位,使用這種模式開發的單元測試也仍然要遵守單元測試與單元測試之間不具備耦合性、具備獨立性 等條件,才能可望做到實際要撰寫的程式碼有在(測試/需求)保護之下,也就是用測試來覆蓋要交付的程式碼。

使用測試先行開發的好處就是,沒有一段程式碼沒有通過測試就開始撰寫,這跟你原本先撰寫好 Production 的程式碼,之後才住撰寫測試更容易的成功、守住基本的品質,且在有測試的情況下也才有機會將測試自動化,並作為 CI/CD 的基本條件之一。

文章中,使用極多的基礎 OOA/OOD 的概念,這些源自羧基礎的物件導向思維,想了解更多物件導向程式設計、系統分析設計基本概念可參考我的線上課程:決戰 OOAD 系列課程(線上版)想購買的朋友請使用此連結,因為與 HiSKIO 的分潤因素,從這買這樣才是我賺唷!XDDD

待續… 還沒完 XD




關於 Gelis:

資深 .NET 技術顧問

FB 社團 (軟體開發之路):

https://www.facebook.com/groups/361804473860062/

FB 粉絲團 (Gelis 的程式設計訓練營):

https://www.facebook.com/gelis.dev.learning/

我講授過的課程 SlideShare:

https://www.slideshare.net/GelisWu


以下是我經營的項目與內容:

(1). 企業內訓課程

(2). 專業顧問


企業內訓課程:

1. .NET Core 3.1 從入門到進階

先前實體課程連結

2. 跨平台的 Web API Framework 框架設計

先前實體課程連結

3. 決戰 OOAD 系列課程 - 使用 UML

先前實體課程連結

4. 單元測試 UnitTest 與 Moq 物件實務課程

先前實體課程連結

5. 快速開發系列 - C# Project Templates 範本設計

留言

這個網誌中的熱門文章

軟體架構設計:API 設計準則(二)、API Design-First 原則、策略與開發流程

常見的程式碼壞味道(Code Smell or Bad Smell)

什麼是 gRPC ?