> For the complete documentation index, see [llms.txt](https://brianwu.gitbook.io/brian/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://brianwu.gitbook.io/brian/brians-za-ji/wei-fu-wu/she-ji/7.md).

# 7

## 測試類型

參考附件。

位於象限底部的是 **面向技術** 的測試，也就是那些首先可以幫助到 開發人員 構建系統 的測試。 在這個象限內的測試，大部分都是可以自動化的。 EX：性能測試、單元測試。

相對的，位於頂部的測試，就是為了幫助非技術背景的人們，了解系統是如何工作的。 這種測試包括左上角的 大範圍、端到端 測試。 還有右上角 由用戶在 UAT 系統上，進行手工驗證的探索性測試。

在這個象限中，每個測試類型 都有自己的位置。 在不同系統中，每個測試類型的占比都會不同，但要知道自己有哪些選擇 \~ 放棄手工測試，盡量使用自動化測試是目前的趨勢。

手工測試是有用的，也有其必要性。但基於本章的內容，我們會主要 focus 在自動化測試的部分。

## 測試範圍

測試金字塔

（越往上、範圍更大、更有信心） 用戶介面測試 服務測試 單元測試 （越往下、執行速度更快、隔離的更好）

## 單元測試

單元測試通常只測試一個函數和方法呼叫。 通過 TDD (Test-Driven Design 測試驅動開發) 寫的測試就是屬於這一類。 由基於屬性的測試技術所產生的測試，也是屬於這一類。

單元測試是彼此獨立的，分別覆蓋一些小範圍的代碼。

這些測試的主要目的是對於功能是否正常 快速地給予反饋。 單元測試對於 **重構** 非常重要，因為我們知道，如果不小心犯了錯誤，這些小範圍的測試可以很快地給予提醒，讓我們可以放心地隨時調整代碼。

## 服務測試

服務測試是避開用戶介面，直接針對服務的測試。 在獨立的應用程序中，服務測試可能只測試 為用戶介面提供服務 的一些類。 對於包含多個服務的系統，一個服務測試只測試其中一個單獨服務。

只測試一個服務，是為了提高測試的隔離性，讓我們可以更快的定位到問題。

有些服務測試可能會執行的向單元測試一樣快。 但如果在測試中用了真實的資料庫，或是通過網路和外部進行合作，那麼測試的時間就會增加。

服務測試比單元測試的覆蓋範圍更大，因次當失敗時，也更難定位。 不過比起更大範圍的測試，服務測試中包含的部分已經很少了，因此也沒有那麼脆弱。

## 端到端測試

通常會覆蓋整個系統，涵蓋範圍最大，通常需要開一個瀏覽器去操作GUI的用戶介面。

這類測試會涵蓋大範圍的程式碼，倘若通過，會感覺十分愉悅 (誤) 但倘若失敗，要定位問題時，也會頭很痛 ((抱頭 Q\_\_\_Q

## 權衡

我們要了解

越靠近金字塔頂端，測試覆蓋的範圍越大，而測試完也會越有信心，但需要花的時間也越久，所以反饋時間會拉長。

越靠近金字塔底部，測試會越快，反饋時間變短，也可以容易定位是哪裡被破壞了。這可以避免我們在 已經破壞某個功能 的狀態下，去執行下一個任務。但從另外一個角度，只測試底層的程式，並沒有足夠的信心整個系統都可以正常運作。

當單元測試以上的測試失敗以後，我們通常會寫一個單元測試去重現問題，以便將來可以用更快的速度去捕捉同樣的錯誤。 我們利用這樣的方式，來盡可能地縮短反饋週期。

## 比例

既然所有測試都有優缺點，那應該怎麼抓比例呢? 有一個經驗法則，是下一層的測試數量，應該比上面的一層多一個數量級。 如果當前的權衡確實給你帶來問題，那麼嘗試調整自動化測試的比例，這是重要的。

作者曾經在一個單塊系統上工作， 單元測試 *4000 整合測試* 1000 UI 測試 \*60 而測試週期變得很長，主要是因為後兩者(尤其是後者)。 之後便盡量使用小範圍的測試來替換大範圍的測試。

有一個測試反模式，測試甜筒 / 倒金字塔 (真滴不要鬧了) 在這種模式，沒有小範圍，只有大範圍，測試 run 起來超慢，有問題超難找，真滴4不要鬧惹。

## 實現服務測試

服務測試 只是想測一個單獨的服務，為了隔離其他的相關服務

工廠模式 是所謂的打桩 Mock 是 Mock (雖然我還是不知道兩者的確實差異)

反正就是為了讓我們可以獨立測試要測的服務，將外部相依性拔掉的方式。 Mock 會進一步驗證請求本身是否被正確呼叫，但是過度使用 Mock 會導致測試變得脆弱。

如果可以，有個可以同時 打桩\&Mock 的工具總是好的。 也有人把這兩者，統稱為 測試替身 (Test Double)。

## 微妙的端到端測試

介面展示的一個功能，往往涉及多個服務，所以進行 端到端測試 的時候需要部署多個服務。 顯然，這種測試可以覆蓋較大的範圍，也可以讓我們對系統更有信心。但另一方面，他也會消耗更多時間。失敗時的定位也更困難。

```
客戶服務    構建--單元測試--服務測試--端到端測試
```

但是我們需要問自己 **當 端到端測試 需要部署其他服務時，我們應該使用哪一個版本的其他服務?**

那其他服務的端到端測試，應該怎麼辦? **這樣會導致我們需要花費大量的成本在重複部署這些服務。**

為了解決問題，有一種優雅的方式 **讓多個流水線扇入 (fan in) 到一個獨立的端到端測試階段 (stage)**。

使用這種方法，任意一種服務的構建，到最後 都會**觸發一次端到端測試**。

一些更好的 CI工具，可以很方便的地實現這樣的擅入模型。

## 端到端測試的缺點

**多**

#### 很多

### 非常多

## 脆弱的測試

有時候失敗並不是因為功能真的被破壞了，而是因為一些其他原因。

可能我們要測的一個功能涉及了四五個服務 1. 而其中一個服務停止運行，都會導致測試失敗，但這種失敗跟功能本身沒有關係。 2. 一個臨時的網路故障也可能導致測試失敗，它同樣跟功能無關。 3. 涉及多線程功能的測試，因為資源競爭、超時，有時是功能真的被破壞了。

**脆弱的測試是我們的敵人**

因為這樣的失敗什麼都不能告訴我們，也沒有幫助。每個人都會期望重新構建一次，剛剛失敗的測試就會通過，而養成不好的習慣。

當發現**脆弱的測試**時，我們應該竭盡全力去解決這個問題。 否則人們會開始對測試套件失去信心，也就會演變成所謂的 **異常正常化**(the normalization of deviance) (隨著時間的推移，我們對事情出錯變得習以為常，並開始接受它是正常的)

當發現脆弱的測試時，應該立刻記錄下來。如果不能立即修復，就先移出套件，這樣就可以不受打擾的修復它。 1. 首先，先看能不能通過重寫來避免多線程 2. 再看看能否讓運行環境更加穩定 3. 更好的方法：能否用不容易出問題的小範圍測試 來取代脆弱的端到端測試。

有時候改變測試軟體本身，使之能夠更容易測試，也是正確的方向。

## 誰來寫這些測試?

一個比較合理的想法是，擁有這些服務的團隊來寫這些測試。 但倘若有一個服務涉及多個團隊，而端到端測試也被多個團隊共享時，誰該負責實現和維護這些測試呢?

### 不好的方式１

測試對所有人開放，所有人都可以隨意添加測試，甚至對測試質量沒有任何理解時。Boom！

### 不好的方式２

由一個專門的測試團隊來寫這些測試。 因為開發人員逐漸遠離測試程式，週期時間會變長，因為開發人員要等待測試團隊撰寫端到端測試。 因為測試程式由別的團隊來撰寫，開發人員很少參與，所以很難了解如何運行和修復。 但這種方式卻是一種很常見的組織模式．．．．

### 有一種比較好的方式，是共享端到端測試的代碼權限，並同時對套件負責。**所有的開發團隊，都必須為了套件的健康負責**

## 測試多長時間

很少看到團隊精細的管理端到端測試、嘗試減少重複覆蓋的測試、花足夠的時間讓它變快。

運行緩慢、脆弱性 是很大的問題。 需要花一天甚至六個星期的測試 + 和功能無關的測試失敗，**這真是大災難**

當發現真的是功能被破壞時，都已經忘得差不多了 Q\_\_\_Q

並行的去run 測試程式可以改善緩慢的問題，可以使用 Selenium Grid 來達到效果。但並不能改善重複覆蓋的問題。

刪除測試程式令人惶恐，可能有點類似移除機場的某些安保措施類似 =A= 移除不一定會被感謝，但是發生問題則肯定會被吊起來。 這需要更好的去理解風險，但人類並不擅長這點... 結果，就是很少能見到有人可以精細的對大範圍的、高負擔的測試進行管理和維護。

## 大量的堆積

端到端測試的反饋週期過長，不僅影響生產效率，也會使修復的週期拉長。

而大量的堆積，指得是在修復的這段時間，所累積的大量提交。這除了使修復更加困難，要部屬的內容也會越來越多。

解決這個問題的一個方法是，在解決問題的這段時間不準提交。 但這通常是不切實際的 ... 七個小時都不提交? 六週不提交?

&#x20;*\*我想這也就是Git flow 裡面，為什麼會有 Release 線了。 累積的commit 會放在 Develop，準備要上線了，會開一個 Release 出來，執行端到端測試，測試完沒問題，就可以 Merge 回去 Develop 和 Master。 這時有一個小問題，感覺應該三種都要在 Feature 寫，這三個測試都要執行 Pass。 而在執行測試時，只執行自己的這些，不執行其他的。 那在Release 才執行全部的測試?*&#x20;

當部署變更的內容越多，發布的風險就越高，也就越可能破壞某些功能。 保障頻繁的發布，其實就是為了縮小發布的範圍。

## 元版本

在端到端測試階段，有可能會有一種謬誤：我知道所有服務在這個版本可以運作，何不一起部署呢? 為何不使用同一個版本號呢?

當修改、部署多個服務是可以接受、成為常態，很容易喪失微服務的主要優勢之一：**獨立於其他服務 單獨部署一個服務的能力。**

經常多個一起部署，經常會導致服務的耦合，本來分離的很好的服務，就會和其他糾纏的越來越緊密，這種耦合，會變得比使用單塊系統還要悲劇。

## 測試場景，而不是故事

這句話的意思是，端到端測試 並不需要把所有使用者案例、所有功能都測過一遍。當我們為每一個新添加的功能都增加一個端到端測試，我們會得到一個 ~~臃腫的測試套件~~、~~反饋週期很長~~、~~巨大的重疊測試覆蓋率~~。

**我們只需要測試整個系統的核心場景，那些核心場景以外的功能，就放在服務測試就好。** 這樣團隊之間需要就這些核心場景達成一致，並共同擁有和維護。 **盡可能減少測試的數量 & 降低重複覆蓋的測試。**

## 拯救消費者驅動的測試

CDC, Consumer-Driven Contract, 消費者驅動的契約

當使用CDC時，我們會定義消費者的期望，而這些期望會轉換成測試程式。 如果使用得到，CDC應該會成為 CI 流水線的一部分，這樣可以確保 如果契約被破壞，系統便無法部署。 更重要的，從測試的反饋週期來看，因為只需要針對開發人員執行這些CDC，所以他會比解決同樣問題的端到端測試更快也更可靠。

因為這些CDC 是對 服務 如何工作的期望，所以服務的下游依賴都可以使用 測試替身。雖然它們與測試金字塔中的服務測試都在同一層，帶側重的地方卻不一樣。 這些測試 是側重在 使用者 如何使用服務，而如果測試失敗的解決方式也會有所不同。如果CDC失敗了，消費者會有很明顯的影響。 這時候可以選擇修復問題，或是發起一個破壞性的討論。

所以通過CDC，我們在端到端測試之前，我們就有機會發現破壞性的變化。

### Pact

某個消費者驅動的測試工具，支援Ruby、JVM、.NET。

開始的時候，Client 使用 Ruby DSL 來定義 except，Pact 的規範文件是 JSON 格式 (雖然也可以自己手寫，但使用語言的API會比較容易)，同時提供一個 mock server，用來獨立測試 Client。

我們可以使用 JSON Pact 來 call 開發人員的API，根據 response 去驗證是否 PASS。 client 和 server 可以是不同的語言 \~

Pact 的 JSON 是由 Client 產生，該規範需要成為一個生產者可訪問的構建物。可以把這個構建物存儲在 CI/CD 工具的構建物倉庫中。 或是使用 Pact Broker，它可以讓你儲存 Pact 規範 的多個版本。

### 關於溝通

在敏捷中，故事通常被認為是一種促進溝通的方式。 CDC 也起到類似的作用。 它們可以推動關於 API 如何編寫的討論，當其被破壞時，也可以觸發該API該如何演進的討論。

CDC 需要 Client & Server 之間具有良好的溝通、信任。 如果雙方是同一團隊(甚至同一人)這應該不難，但如果你使用的服務是第三方溝通，那個CDC可能不適用，因為缺乏溝通和信任。 這種情況，可能就必須使用端到端測試。

## 還應該使用端到端測試嗎 OAO?

大部分的人更喜歡使用 CDC的工具 & 更好的監控 來代替端到端測試。

這代表要把端到端測試扔掉嗎? 不，他們會在使用一種叫做 **語義監控** (semantic monitoring) 的技術，在監控生產系統時，用到 端到端測試。(ch.8)

在使用 CDC 和 良好的監控、部署技術 後，可以慢慢嘗試減少對 端到端測試 的依賴，直到完全不需要它們。

學習如何監控、修復生產環境，是很有價值的。

## 部署後再測試

大多數的測試會在部署前完成。 我們希望通過一系列的測試來證明在功能、非功能需求方面，系統的工作方式、行為都符合預期。 但我們必須承認，使用這種方法得到的收益會逐漸減少。 僅僅依靠部署之前的測試，我們不能將缺陷率降為零。

### 區分部署 / 上線

我們可以將軟體部署到生產環境，但在真正生產負載(production load) 之前 先運行測試。我們可以發現特定環境中的問題。

針對新部署軟件的一系列 **冒煙測試套件**，這些測試幫助我們識別環境有關的問題。 如果我們可以用一行指令來部署任何微服務，那麼我們也可以用一條指令來運行冒煙測試。

而另外一種方式，就是所謂的 **藍 / 綠部署**。 我們會擁有兩套生產環境，一套是原先運行的，一套是新部署上去的。測試完後，我們會將生產負荷切換到新部署的服務，但依然會保留舊版本的一段時間，倘若有狀況，就可以很快再切回來。

條件：能夠切換生產流量到不同主機上，例如可以透過更改DNS or 負載平衡的配置。

### 金絲雀發布

它和藍綠部署的差異在於：新舊版本共存的時間更長，並且經常會調整流量。

**用來 觀察、比較 新舊版本的效益、運行狀況。**

當使用金絲雀發布時，需要選擇是要 引導部分生產請求到金絲雀，還是複製一個請求。這方便大家對新舊版本做比較，因為請求一模一樣。 不過複製請求有可能很複雜，尤其在事件/請求 不是冪等的狀況。

金絲雀發布是一個功能強大的技術，同時對於推出一個糟糕的版本，提供工具來控制風險。 但是需要更多配置、占用更長時間的硬體、更複雜的請求路由。

## 平均修復時間 勝過 平均故障間隔時間

在Web的世界裡，這通常稱為 平均故障間隔時間 (Mean Time Between Failures, MTBF) 和 平均修復時間 (Mean Time To Repair, MTTR) 之間的權衡變化。

在 MTBG 和 MTTR 之外，還有別的權衡存在。 如果妳正試圖了解是否有人會真正使用我的軟體，那就需要盡快發布軟體。 這遠比構建健壯的軟體更有意義。 因為可以驗證之前的想法、業務模型是否能讓別人接受，在確認這件事情之前，測試並沒有那麼重要。

## 跨功能的測試

有一種測試也非常重要 **跨功能性需求**(Cross-Functional Requirement)，是對 系統展現的一些特性 的概括術語。

1. 網頁可接受延遲時間
2. 系統能夠支持的客戶數量
3. 用戶介面如何讓殘疾人使用
4. 客戶數據安全

我們可以定義一些測試策略，來幫助我們朝著滿足目標的方向前進。 這些策略歸類為 **屬性測試象限**。

對於一些CFR，可能希望能夠在追蹤單一一個服務，核心業務可以持久一點，相對邊緣的則允許更多的停機時間。這些權衡會對你如何設計你的系統，有比較大的影響。合適粒度的微服務，會給我們機會做這樣的平衡。

CFR 的測試也要遵循金字塔。 有一些測試需要端到端，EX：負載測試。 但其它的不需要，通常很容易使用更快的測試。 例如確保HTML標記 使用適當的可訪問特性，來通過無障礙。

**建議盡早去看CFR**

## 性能測試

與功能測試相同，性能測試也可以是各種範圍的混合。你可能決定想測試單個獨立服務的功能，可以大量併發測試 系統中核心場景的端到端場景 性能。

為了產生有價值的結果，通常需要模擬客戶逐漸增多，呼叫延遲 隨著負荷的增加而變化，所以性能測試通持需要持續一段時間 \~

因為性能測試要運行很長的時間，有一種做法是：每天運行一個子集合，每周運行一個大的集合。不管選擇什麼方法，都要確保可以盡可能頻繁的運行。如果只要通過查看少量的提交，就能找到問題，不是好棒棒嗎 XD

## 運行完一定要看結果

### 運行完一定要看結果

#### 運行完一定要看結果

很多團隊建立了性能測試，但運行後卻不看結果。 =A= 性能測試需要有目標，有了目標之後，基於運行結果去讓構建變成綠色或紅色，紅色就是一個清晰的信號。

## 結論

1. 優化快速反饋，並相應的使用不同類型的測試。
2. 盡可能使用 CBC 來替換 端到端測試。
3. 使用 CBC 提供團隊之間的對話重點。
4. 嘗試理解 **投入更多的努力測試** 與 **更快地在生產環境發現問題** 之間的權衡(MTBF 與 MTTR 權衡的優化)

*<敏捷軟體測試> - Lisa Crispin & Janet Gregory*
