unit test 基礎想法
名詞解釋
| 名稱 | 內容 |
|---|---|
| SUT | System Under Test,就是待測物。舉例來說,當我們在寫 Unit Test,SUT 往往就是 Class,那個我們要測試的 Class |
| DOC | Depended-On Component,SUT 所依賴的元件,也就是 Mock, Stub ..想要抽換掉的元件。 把這些相依性抽換成我們可以控制的狀態,以專注去測試 SUT。 |
| test fixture | 測試的時候特意準備的東西,讓測試可以順利跑完所需要,有些人會叫做 test context。 舉例來說,一組測試用的資料。 |
| control point | 用 control point 來操作 SUT,有些 control point 只是為了測試而存在。 舉例來說,某個 class 開了純為測試存在的 API,這種 control point 當然不能在產品裡面使用。 |
| indirect input | 改變 SUT 的數值,並非直接來自我們的操作,而是來自於 DOC。 好比我們對一個物件呼叫 updateName,然後物件去問另外一個資料庫物件,取得了在資料庫裡面的數值來更新自己 |
| indirect output | 當 SUT 的一些狀態改變了,而我們沒有辦法透過直接的 API 去取得改變的狀態。 可是,它同時也會影響到其他元件的狀態,後面這些能被觀察的狀態, 就是 indirect output。這與後面的 Mock object 有關。 |
| Test double | 這個字不好翻譯,double 當作名詞有「替身」的意思, Test Double 就是我們要拿來替換掉真實運作的 DOC 的物件們,得到一個更簡單,可以測試的環境。 |
Test Double
自動化測試中,我們常會使用一些經過簡化的,行為與表現類似於生產環境下的對象的複製品。引入這樣的複製品能夠降低構建測試用例的複雜度,允許我們獨立而解耦地測試某個模塊,不再擔心受到系統中其他部分的影響;這類型對像也就是所謂的 Test Double。實際上對於 Test Double 的定義與闡述也是見仁見智,Gerard Meszaros 在這篇文章中就介紹了五個不同的 Double 類型;而人們更傾向於使用 Mock 來統一描述不同的 Test Doubles。不過對於 Test Doubles 實現的誤解還是可能會影響到測試的設計,使測試用例變得混亂和脆弱,最終帶來不必要的重構。
大致能夠把 Test double 分成以下幾種
測試的過程,可用以下這圖說明一下
- Setup,生成 Test double 來取代 DOC,並準備好 Fixture 方便接下來的測試
- 初始化 SUT,通常就是建構一個 object
- Exercise,執行測試。這時候 SUB 還可能會去跟 Test double 去要一些數值
- Verify,確認執行完的結果是否無誤
- Teardown,把之前測試過程產生的東西給清乾淨
Test Stub
提供 indirect input 給 SUT 的 Test double
舉例來說,我們要測試登入用的程式是不是有正常運作。SUT 就是登入用的主程式,塞了一個假的 Network 物件給他,只要對 Network 物件下 GET 總是回傳 200 OK。這個 Network 物件就是 Stub。
Stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.Stub 代指那些包含了预定义好的数据并且在测试时返回给调用者的对象。Stub 常被用于我们不希望返回真实数据或者造成其他副作用的场景。
Stub 的典型应用场景即是当某个对象需要从数据库抓取数据时,我们并不需要真实地与数据库进行交互或者像 Fake 那样从内存中抓取数据,而是直接返回预定义好的数据。
Test Spy
就是能力更強,有記錄功能的 Test Stub,提供 indirect input 給 SUT 的 Test double
以前面的例子來說,在驗證的階段,可能還想要知道這個 Network 物件的 API 被呼叫了幾次,有沒有使用到錯的 end-point?能夠告訴我們這些資訊的,就是 Test Spy。
Mock Object
就是帶有判斷功能的 Stub,判斷 SUT 是不是正確地使用這個 DOC
前面的 Spy 是看最後有沒有正確的 indirect output。但也有可能,最後的結果是對的,中間的順序錯了。好比說呼叫 web api 的順序是不是正確的?這時候我們需要在 Test double 裡面偷看實作的運作。Mock Object 就會在被使用的過程中,擁有檢查的邏輯。
Mocks are objects that register calls they receive. In test assertion we can verify on Mocks that all expected actions were performed.Mocks 代指那 些仅记录它们的调用信息的对象,在测试断言中我们需要验证 Mocks 被进行了符合期望的调用。
当我们并不希望真的调用生产环境下的代码或者在测试中难于验证真实代码执行效果的时候,我们会用 Mock 来替代那些真实的对象。典型的例子即是对邮件发送服务的测试,我们并不希望每次进行测试的时候都发送一封邮件,毕竟我们很难去验证邮件是否真的被发出了或者被接收了。我们更多地关注于邮件服务是否按照我们的预期在合适的业务流中被调用,其概念如下图所示:
就是帶有判斷功能的 Stub,判斷 SUT 是不是正確地使用這個 DOC
前面的 Spy 是看最後有沒有正確的 indirect output。但也有可能,最後的結果是對的,中間的順序錯了。好比說呼叫 web api 的順序是不是正確的?這時候我們需要在 Test double 裡面偷看實作的運作。Mock Object 就會在被使用的過程中,擁有檢查的邏輯。
Fake Object
就是提供比較簡單、輕量實作的 DOC
Fake Object 不在意 Indirect input 也不在意 indirect output,只專心地滿足自己的介面。
前述的幾個物件,要嘛是專門提供假的固定值(Stub),要嘛是有檢查的功能。Fake 物件就是一個簡化過 DOC,有著一樣的介面但是實作都很簡單,譬如說是個 In-memory 的資料庫物件,用起來像是真正的資料庫,但是操作沒有 disk IO,而且資料可能是我們寫好的 fixture。
Fakes are objects that have working implementations, but not same as production one. Usually they take some shortcut and have simplified version of production code.Fake 是那些包含了生产环境下具体实现的简化版本的对象。
如下图所示,Fake 可以是某个 Data Access Object 或者 Repository 的基于内存的实现;该实现并不会真的去进行数据库操作,而是使用简单的 HashMap 来存放数据。这就允许了我们能够在并没有真的启动数据库或者执行耗时的外部请求的情况下进行服务的测试。
Dummy Object
用來填充的無用物件,只是為了滿足 API 的介面
建構一個物件,或是呼叫一個方法的時候,有時候需要傳入一些物件當作參數,而我們知道這些物件完全不會被使用到,但是為了要滿足介面,成功編譯,塞進去的填充物就是 Dummy Object。
測試的時候可以只針對一個 file 去作測試,只需要指定路徑
npm run test -- src/app/components/header/header-right/header-right.component.spec.ts