前言
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Why We Should Test Code ?
- Reduce Bugs
- Reduce Costs
- Improve Design
- 當你發現測試很難寫時, 需要開始擔心架構設計上是否有問題.
- Documentation
- Eliminate Fear
How Do We Write Testable Code ?
- Create seams(縫隙) in code
- Simplify construction
- Work with dependencies
- Decouple from global state
- Maintain single responsibility
- Use Test-driven Development
Create seams in code
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
seam 是指程式碼中可以抽換不同功能的地方
- 使用虛設常式類別
- 虛設常式(stub)是指系統中產生一個可控的替代物件, 來取代相依物件, 以方便測試。
- 增加一個建構子 (透過建構子參數傳入可控物件)
- 增加一個可設定的公開屬性
- 把一個方法改成可 override 的虛擬方法
- 把一個委派拉出來變成一個參數或屬性供類別外部來決定內容 (個人不推薦)
增加 seam 可能會破壞類別的封裝, 需謹慎之
Problem
當程式碼中沒有 Seam 的存在時, 會有下列問題
- Can not pull apart code
- Can not connect a test harness to the class we want to test
- 無法測試類別行為, 因為你可能沒辦法控制它的行為.
- Can not replace dependencies
- Can not test in isolation
- 當所有東西都耦合再一起, 你就無法單獨測試某一段邏輯.
Symptoms
當你遇到下列情境時需先思考一下, 這個地方是否真的不需要 Seam
- Keyword "new" in code
- new is glue !!! new 出來的物件, 是沒辦法被外界(UT) 替換的.
- Static method call
- 靜態成員是不能被 override 的. 但你若需要控制此方法成員的回傳值, 則會有問題
- 當靜態方法的使用是不需要被替換, 且通常此靜態方法已經被別的 UT 測試過時, 請放心使用.
e.g. Math.Pow() 等等
- Direct coupling
- 你必須判斷對於某個外部資源或是套件的直接依賴是否會影響你的開發.
- e.g. Log/File/Database 的操作使用.
Solution
- Create seams in code
- Decouple Dependencies
- Program to interfae (we should follow DIP)
- Inject Dependencies
- 依賴注入的幾種方式
- 擷取與覆寫 (Extract and Override)
- 工廠模式
- 屬性注入
- 方法注入
- 建構子注入 (常用)
- Test in isolation
值得思考的範例(?)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
實務上我覺得還是盡可能不要使用 Setup() , 也盡可能不要使用 field 去儲存 mock 物件會比較好.
因為隨著 Test 的增加, Setup() 可能變得越來越複雜 ==
Moq.AutoMock
An automocking container for Moq. Use this if you're invested in your IoC container and want to decouple your unit tests from changes to their constructor arguments.
這個套件能幫你模擬 DI 注入的情境. 太神啦~~~傑克
參考資源
Moq.AutoMock
Moq.AutoMock Git hub
自动Mock,让编写单元测试更简单
(如何模擬使用 IIndex) How to unit test with Keyed Registrations?
Constructing Testable Objects
Constructors
- Used to build objects
- Prepares object for use
通常我們使用建構子去設定物件的初始狀態. 但當你在你的建構子內放太多的設定邏輯時,
這可能會導致一些問題.
Problems
- Creates tight coupling
- 你可在建構子內建立任何物件, 但這導致強相依.
- 盡可能遵守 DIP 原則, 並使用 DI 技術實現
- Logic is difficult to test
- 建構子內的設定邏輯通常是很難驗證的. 因為這些"設定邏輯" 很常是一些設定 private 變數的邏輯.
- Logic is difficult to set up
Symptoms
- Keyword "new"
- 在建構子內透過 new 物件去初始化的場合, 都需要謹慎考慮這是否利大於弊
- 簡單型別, 字串或是 IEnumerable 型別的 new , 是完全可以接受的.
- 防禦性 coding 那堂課有講到, 如果你有一個 IEnumerable 變數, 請記得初始化它. 不然外界不會知道你的 IEnumerable 變數尚未被 new. 使用前需要自己 new ==
- Logic in constructor
- 理論上建構子就是負責物件的初始化, 應該就只要簡單的初始化或是設定物件所需要的東西就好. 若是有過多的邏輯判斷( if/switch/for/while … ) , 可能導致你之後很難測試.
- Any non-assignment code
- 理論上建構子就是負責物件的初始化, 應該就只要簡單的初始化或是設定物件所需要的東西就好.
Solution
- Inject depenedencies
- Avoid logic in constructor
- Use factory, builder or IOC/DI
- Don't mix construction and logic
- Serarate Injectables vs. Newables
- An injectable is an object that is composed of other injectables and performs work on newable objects. Injectables are generally services that implement interfaces. For example, our Database, Printer and InvoiceWriter classes are all examples of injectables.
- An newable is an object at the end of your object graph. these are generally things like entities and value objects. For example, invoice, customer, address and credit card numbers are all newables.
- An injectable class can ask for other injectables in its constructor; however it should not ask for any newables in its constructor. Inversely, a newable can ask for other newables in its constructor; however, it should not ask for an injectable in its constructor.
Working with Dependencies
Law of Demeter
- Only talk to your immediate friends
- Don't talk to strangers
Problems of violating the Law of Demeter
- Tight coupling
- Difficult to set up
- 當你的耦合增加, 這代表你要 Mock 的東西也變多了 XD
- Dependencies not explicit
- 原本你可以從建構子參數快速知道你要 mock 甚麼東西, 但因為違反了 Law of Demeter, 這代表會有 strangers 存在. 故你必須要進去看 code 才知道有哪些 strangers 是你需要 mock 的
Symptoms
- Series of appended methods (不是 Method Chain 不好, 而是當你看到時, 需要小心)
- it is okay for method chain when using the builder pattern with fluent notation
- Container dependency
- 容器(immediate friends) & 容器的回傳值(strangers)

- 範例
- Suspicious names (Container dependency 常有一些名字)
- container, contextes, environnent, or service Locator…
Solution
- Follow the Law of Demeter
- 試圖封裝那些 strangers. 讓 user 只需要認識 immediate friends.
- Inject dependencies we need
- Use Dependencies directly
- Inject only what is needed
- 你注入的容器最好只提供你所需要的資訊就好. e.g. 你可能注入一個有一百個屬性的容器類, 但你只需要其中的五個屬性, 這就很不好 ==
- Make dependencies explicit
- 透過建構子注入的參數/公開屬性/私有field, 可以讓我們清楚的知道這個類僅依賴於哪些相依.
Managing Application State
Global State
- Set of variables
- 其實就是一群變數, 這些變數控制著系統的狀態
- 實現機制
- Global variables
- Application-State object (Asp.Net 專用)
- Only a single instance
WPF 沒有 Asp.Net 的 Application-State 可以使用. 但有別種方式可以實現資料交換.
可能的 WPF 資料交換的實作方式
- MVVM
- 舉例 : 三個 View 使用同一個大VM , 三個 View 的資訊溝通會透過大VM 傳遞 (不推)
- Static Class (全域變數存起來)
- Dependency Injection
- 資料放在 Service , 再注入 Service
- Singleton Pattern
Problem
- Coupling to global state
- Difficult to set up tests
- 再執行 UT 之前, 需要先設定這些 Global State
- Prevents parallel tests
- UT1 可能需要設定 State Varable 為 A , 但 UT2 可能需要設定為 B. 此時若此兩個 UT 為平行執行測試, 會出現問題. e.g. UT1 設定完 State Varable, 然後 UT2 跑完全部的測試. 此時 UT2 必定 failed.
- Spooky action at a distance (若有看到這現象, 這必定是一個 anti-pattern)
- Changes to a class in one part of the application should generally not affect classes in other parts of the application in unpredictable way
- 這代表你每一個耦合 Global State 的類別的測試, 都必須要設定 Global State, 因為這些 UT 不再具有隔離性(彼此之間獨立互不影響). UT1 對於 Global State 的設定可能會影響到 UT2
- 舉例 : Driver 若改變 PassengerDoorLock 的變數, Passenger 會再不知道變數被改變的情境下繼續使用 PassengerDoorLock 的變數.
Symptoms
- Global variables
- Static methods and fields
- GoF - Singleton Pattern
- Unit Test randomly fail
Solution
- Avoid coupling to global state
- Keep state local if possible
- 除非必要, 不然盡可能讓變數存在 Local 處.(instance) , 少用 static
- 原則上我們應該讓變數的生命週期越短越好. 像是 Clean Code 那堂課講過的浮游變數 !
- Inject a wrapper class
- 建立 Facade 去封裝外界不必要知道的資訊. e.g. 靜態方法的使用
- Use IoC/DI Singleton instead of original implementation of Singleton Pattern.
Maintaining Single Responsibility
Single Responsibility Principle
- Only one reason to change
- Cohesion and coupling
- 希望增加類別之間的內聚力 –- 由於相同原因而變化的事物之間的凝聚力
- 希望減少不同類別間的耦合程度 –- 因不同原因而變化的事物之間的耦合
- Do one thing well
Problem of violating SRP
- Many Tests per class
- 一個類別越複雜, 自然需要為其寫更多的 Unit Tests e.g. 想像一個 class 的 unit test 有一百多個, 這類別的功能該有多複雜 ?
- Complex setup
- 一個複雜的類, 妳為了控制它能執行某個行為, 你可能需要付出比較多的前置動作.
- Frequently changing tests
- 一個類別的職責越多意味著你改變它的可能性越高, 也就代表你修改 UT 的頻率越高
Symptoms
- Described with "and" / "or"
- Class or Method is large
- 大就代表複雜, 就可能會有上述提到的三個問題
- 一個複雜的方法或是類別可以考慮將其重構成數個方法或類別 (大拆小)
- Many Injected dependencies
- 一般來說, 一個類的依賴關西越多, 可能就代表這個類的職責越大
- class changes frequently
Solution
- Identify responsibilities
- Label responsibilities
- 當你發現隱藏在一個類裡面的職責時, 可以試圖去為它們取一個可以簡潔描述其職責的名稱.
- Decompose into SRP classes
Thank you!
You can find me on
若有謬誤 , 煩請告知 , 新手發帖請多包涵
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →