# 測試案例設計入門
在開始瞭解如何開始設計測試案例之前,首先你該知道測試的本質是在於檢驗事物的正確性。它是一種風險管理的工具,用來協助團隊理解現況與做出決定。

* 速度與成本: 品質不佳
* 成本與品質: 時間過長
* 品質與時間: 成本過高
在實務上測試工程師經常面臨的難題是如何在三者間取得平衡,充分的溝通與團隊合作可建立團隊共識,對於品質標準有一致性的認知。
而隨著產品商業環境的不同,品質標準與策略有可能截然不同。在金融與醫療產業,些許的差錯就可能造成金錢或是生命安全的損失,因此對於品質往往有非常高的要求與標準。
KKBOX 提供的是音樂串流服務,屬於娛樂性質的商業環境,快速的反應市場變化來創造話題與潮流。在這樣的生態下,快速發佈會比品質更為重要。
## 你會如何測試這部電話 ?

常見的幾種回答:
1. 測試能不能撥打電話
2. 測試能不能接收電話
3. 把所有按鈕按過一遍,確保每個按鈕都能正常做用
在開始進行測試之前,你應該對基本商業需求有所認知,了解使用者會如何使用這些產品。有了這些認知設計出來的測試案例會具有較高的價值。而伴隨著不同的測試目的,測試的方法與重點會截然不同。
## 認識測試案例
測試的本質是在於檢驗事物的正確性,然而單單找到問題還不夠。更重要的是問題是否可以被複製,可以被複製的問題才有機會被解決,透過系統思考與測試方法為測試本身建立測試案例,讓測試具有可預期性與重複性是測試工程師的核心價值。
所謂的**測試案例**是指描述測試內容的文件,其基本要素包含了下列項目:
- **編號**: 非重複性的編號,用於規劃與管理測試案例
- **標題**: 描述測試的目的,通常會包含情境與預期結果
- **前置條件**: 測試的前置狀態
- **測試步驟**: 測試的步驟
- **預期結果**: 如何判斷測試成功或失敗
- **優先權**: 測試的優先順序,當時間有限時它可以幫助你決定如何做出的取捨
<table>
<tr>
<th>編號</th>
<th>標題</th>
<th>前置條件</th>
<th>測試步驟</th>
<th>預期結果</th>
<th>優先權</th>
</tr>
<tr>
<td>TC_001</td>
<td>使用者可撥打內線電話與另一內線分機通話</td>
<td>電話已連接線路</td>
<td>輸入電話號碼 `666`</td>
<td>1. 撥打內線電話至分機 `666`<br />
2. 接通後可與對方通話</td>
<td>高</td>
</tr>
</table>
### 測試與開發間的關係
在傳統的瀑布式開發模型裡測試往往安排在開發完成之後,此一方法最大的問題在於無法提供即時的回饋,進而造成重工與時程上的延遲。

**V-Model**: 瀑布式開發模型模型的延伸。此一模型裡揭露的開發與測試間的關係,其最早可被追溯至 1986 年為德國軍方所使用,不同於瀑布式模型,它強調的是在系統建置與發展的各種時期,都應該有其對應的測試來即時給予回饋。此一模型內的觀念也被運用在現代開發/測試模型裡。如測試驅動開發 (TDD),驗證測試驅動開發 (ATDD)或是測試金字塔 (TestPyramid ) 等。

V-Model 模型將測試分為 4 個層級:
### 1. 驗收測試 (Acceptance Testing):
驗收測試的目的在於驗證產品需求的正確性,這類型的測試會在專案初期進行設計,也因為此時通常沒有技術文件可供參考,它通常不會考量太多技術細節。
產品需求可能以各種形式出現:
* 幾張圖:

* 一句話: 使用者可以透過撥打電話與另一方通話。
* Story: 當使用者撥打電話後,系統會連接使用者與另一方如此雙方便可以使用語音快速溝通。
難題在於一句話各自表述,俗話就是各自腦補,每個人對這樣的產品都有各自的解讀。且難以察覺彼此想法的不同,若能將這些產品需求實例化,可有效減少認知上的錯誤。
#### 實例化
- 當使用者輸入電話號碼 `666`,即可撥打內線電話至分機 666。
- 當使用者輸入電話號碼 `(02)2655-7557`,即可撥打國內電話至 KKBOX Taiwan。
- 當使用者輸入電話號碼 `948794`,無法進行撥號。
#### 測試案例
<table>
<tr>
<th>編號</th>
<th>標題</th>
<th>前置條件</th>
<th>測試步驟</th>
<th>預期結果</th>
<th>優先權</th>
</tr>
<tr>
<td>TC_001</td>
<td>使用者可撥打內線電話與另一內線分機通話</td>
<td>電話已連接線路</td>
<td>輸入電話號碼 `666`</td>
<td>1. 撥打內線電話至分機 `666`<br />
2. 接通後可與對方通話</td>
<td>高</td>
</tr>
<tr>
<td>TC_002</td>
<td>使用者可撥打國內電話與其他號碼通話</td>
<td>電話已連接線路</td>
<td>輸入電話號碼 `(02)2655-7557`</td>
<td>1. 撥打國內電話號碼 `(02)2655-7557` <br />
2. 接通後可與對方通話</td>
<td>高</td>
</tr>
<tr>
<td>TC_003</td>
<td>系統會提示使用者撥打了無效的電話號碼</td>
<td>電話已連接線路</td>
<td>輸入電話號碼 `948794`</td>
<td>系統回覆該號碼不存在</td>
<td>中</td>
</tr>
</table>
### 2. 系統測試 (System Testing):
系統測試的目的是在於驗證應用程式的正確性,此一階段伴隨著明確的系統需求與技術規範,此時測試會將技術細節考量進行。在這個階段從基礎的功能性測試乃至相容性測試、多國語系等都會視專案需求而分階段進行。
為了驗證系統的正確性,測試工程師會盡可能的設計各式測試案例來減少系統的錯誤。剛進入測試領域的工程師很容易陷入窮舉的陷阱,而設計出低效率的測試案例。
以一個僅接受 `1 ~ 1000` 的可輸入欄位為例,你會需要設計幾個測試案例來測試它 ?
#### 測試案例
<table>
<tr>
<th>編號</th>
<th>輸入數字</th>
<th>預期結果</th>
<th>備註</th>
</tr>
<tr>
<td>1.</td>
<td>0</td>
<td>失敗</td>
<td>最小值 - 1</td>
</tr>
<tr>
<td>2.</td>
<td>1</td>
<td>成功</td>
<td>最小值</td>
</tr>
<tr>
<td>3.</td>
<td>2</td>
<td>成功</td>
<td>最小值 + 1</td>
</tr>
<tr>
<td>4.</td>
<td>666</td>
<td>成功</td>
<td>界於最小值與最大值間的數字</td>
</tr>
<tr>
<td>5.</td>
<td>999</td>
<td>成功</td>
<td>最大值 - 1</td>
</tr>
<tr>
<td>6.</td>
<td>1000</td>
<td>成功</td>
<td>最大值</td>
</tr>
<tr>
<td>7.</td>
<td>1001</td>
<td>失敗</td>
<td>最大值 + 1</td>
</tr>
</table>
此一方法為**邊界值分析**,是測試工程師不能不瞭解的技巧。進一步的分析,我們可以把這 7 個數字切分為 3 個區塊.
```
..... -2 -1 0 1 2 ..666.. 999 1000 1001 1002 1003 .....
-------------|--------------------|--------------------
不合法輸入 1 合法輸入 不合法輸入 2
```
同一區塊內,你該考量的是測試效益的問題,比方說: 2, 3, 4, 666, 997, 998, 999 這幾個數字在此一條件下其實等效的,測試這些數字除了增加測試時間外,實質上對於正確性來說沒有絲毫的幫助。
透過這個方法我們其實可以把先前的測試略做簡化,但仍不損測試的完整性。
<table>
<tr>
<th>編號</th>
<th>輸入數字</th>
<th>預期結果</th>
<th>備註</th>
</tr>
<tr>
<td>1.</td>
<td>0</td>
<td>失敗</td>
<td>最小值 - 1</td>
</tr>
<tr>
<td>2.</td>
<td>1</td>
<td>成功</td>
<td>最小值</td>
</tr>
<tr>
<td>3.</td>
<td>666</td>
<td>成功</td>
<td>界於最小值與最大值間的數字</td>
</tr>
<tr>
<td>4.</td>
<td>1000</td>
<td>成功</td>
<td>最大值</td>
</tr>
<tr>
<td>5.</td>
<td>1001</td>
<td>失敗</td>
<td>最大值 + 1</td>
</tr>
</table>
此一方法為**等價類劃分法**,它經常搭配**邊界值分析**共同使用,透過這些方法可幫助你刪除那些無謂的測試,並增進整體效率。
### 3. 整合測試 (Integration Testing):
整合測試概念上來說其實是相當模糊與籠統的,它可以是跨越兩個 Function Call 以上的測試,也可以是跨越二十個 Class 以上的測試。如果我們將單一函式視為最小單位的積木,整合測試即是確保由數個積木組合而成的元件符合我們的設計。
而就如同你想像的,數個元件的結合就構成了一套系統。
### 4. 單元測試 (Unit Testing)
所謂單元測試就是以程式中最小的邏輯單元為對象,撰寫測試程式,來驗證邏輯正確與否。一般來說,程式中最小的邏輯單元就是函式,或是方法(method)。
而通常單元測試會由開發人員負責,原因是在於單元測試經常伴隨著重構 (Refactoring) 進行。
### 測試審查 (Test Review)
在設計測試案例的過程中,取得團隊的回饋是相當重要的。測試審查應發生在當完成階段性的測試設計工作後,透過邀請產品與開發人員來共同審查測試的涵蓋率。確保重要的使用者情境與要素有被涵蓋。
而使用心智圖來呈現測試涵蓋範疇是目前比較常見的做法。

## 如何規劃與執行測試案例
在測試案例設計完成後,測試工程師必須思考如何有效率的規劃與執行這些測試案例。在 Android App 的生態裡碎片化是一個普遍的問題,你會如何確保 App 能在不同的裝置上運作 ?

### 選擇測試組合
經由分析、歸納與分類我們可以把龐大且複雜的問題,分解成多個小問題。把問題縮小了,才有機會解決。
而 [Android Dashboards](https://developer.android.com/about/dashboards/index.html) 即是官方針對此一議題釋出的數據,透過 3 種要素來簡化其複雜度:
#### 1. Android 版本: 12
<table>
<caption>Android 版本</caption>
<tr>
<th>版本</th>
<th>API</th>
<th>比例</th>
</tr>
<tr>
<td>2.3.3 - 2.3.7</td>
<td>10</td>
<td>0.6%</td>
</tr>
<tr>
<td>4.0.3 - 4.0.4</td>
<td>15</td>
<td>0.6%</td>
</tr>
<tr>
<td>4.1.x</td>
<td>16</td>
<td>2.3%</td>
</tr>
<tr>
<td>4.2.x</td>
<td>17</td>
<td>3.3%</td>
</tr>
<tr>
<td>4.3</td>
<td>18</td>
<td>1.0%</td>
</tr>
<tr>
<td>4.4</td>
<td>19</td>
<td>14.5%</td>
</tr>
<tr>
<td>5.0</td>
<td>21</td>
<td>6.7%</td>
</tr>
<tr>
<td>5.1</td>
<td>22</td>
<td>21.0%</td>
</tr>
<tr>
<td>6.0</td>
<td>23</td>
<td>32.0%</td>
</tr>
<tr>
<td>7.0</td>
<td>24</td>
<td>15.8%</td>
</tr>
<tr>
<td>7.1</td>
<td>25</td>
<td>2.0%</td>
</tr>
<tr>
<td>8.0</td>
<td>26</td>
<td>0.2%</td>
</tr>
</table>
#### 2. 螢幕解析度: 15
<table>
<caption>螢幕解析度</caption>
<tr>
<td></td>
<td>ldpi</td>
<td>mdpi</td>
<td>tvdpi</td>
<td>hdpi</td>
<td>xhdpi</td>
<td>xxhdpi</td>
</tr>
<tr>
<td>Small</td>
<td>0.7%</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Normal</td>
<td></td>
<td>1.5%</td>
<td>0.2%</td>
<td>30.5%</td>
<td>36.7%</td>
<td>21.3%</td>
</tr>
<tr>
<td>Large</td>
<td>0.1%</td>
<td>2.9%</td>
<td>1.6%</td>
<td>0.5%</td>
<td>0.9%</td>
<td>0.1%</td>
</tr>
<tr>
<td>Xlarge</td>
<td></td>
<td>2.0%</td>
<td></td>
<td>0.5%</td>
<td>0.5%</td>
<td></td>
</tr>
</table>
### 3. OpenGL Version: 3
<table>
<caption>OpenGL 版本</caption>
<tr>
<th>版本</th>
<th>比例</th>
</tr>
<tr>
<td>2.0</td>
<td>36.9%</td>
</tr>
<tr>
<td>3.0</td>
<td>45.6%</td>
</tr>
<tr>
<td>3.1</td>
<td>17.5%</td>
</tr>
</table>
現假設有 15 項測試案例必須完整考慮這 3 種要素的所有可能,那 15 項測試案例會變成 8100 項測試,測試總數會隨著必須納入考量的因數而呈現指數級的成長。

在多數的情況裡,測試工程師不會有足夠的時間或資源來完成所有測試。因此溝通與簡化測試組合就非常重要,透過溝通與協同合作,你的團隊可以幫助你理解哪些測試組合可以被捨去,哪些又該被考量進來。
此外,這裡也有一些基本的準則與方法可以幫助你與團隊做出決定。
1. 最舊的版本
2. 最新的版本
3. 最小的螢幕
4. 最大的螢幕
5. 最多人使用
以 KKBOX 為例,基於這些準則與商業環境考量,我們略作簡化如下:
#### 1. Android 版本: 12 -> 6
<table>
<caption>Android 版本</caption>
<tr>
<th>版本</th>
<th>API</th>
<th>比重</th>
<th>KKBOX</th>
<th>備註</th>
</tr>
<tr>
<td>2.3.3 - 2.3.7</td>
<td>10</td>
<td>0.6%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>4.0.3 - 4.0.4</td>
<td>15</td>
<td>0.6%</td>
<td>V</td>
<td>最低支援版本</td>
</tr>
<tr>
<td>4.1.x</td>
<td>16</td>
<td>2.3%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>4.2.x</td>
<td>17</td>
<td>3.3%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>4.3</td>
<td>18</td>
<td>1.0%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>4.4</td>
<td>19</td>
<td>14.5%</td>
<td>V</td>
<td>最多人使用的 4.X 系列</td>
</tr>
<tr>
<td>5.0</td>
<td>21</td>
<td>6.7%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>5.1</td>
<td>22</td>
<td>21.0%</td>
<td>V</td>
<td>最多人使用的 5.X 系列</td>
</tr>
<tr>
<td>6.0</td>
<td>23</td>
<td>32.0%</td>
<td>V</td>
<td>最多人使用的 6.X 系列</td>
</tr>
<tr>
<td>7.0</td>
<td>24</td>
<td>15.8%</td>
<td>V</td>
<td>最多人使用的 7.X 系列</td>
</tr>
<tr>
<td>7.1</td>
<td>25</td>
<td>2.0%</td>
<td></td>
<td></td>
</tr>
<tr>
<td>8.0</td>
<td>26</td>
<td>0.2%</td>
<td>V</td>
<td>最新的版本</td>
</tr>
</table>
#### 2. 螢幕解析度: 15 -> 5
<table>
<caption>螢幕解析度</caption>
<tr>
<td></td>
<td>ldpi</td>
<td>mdpi</td>
<td>tvdpi</td>
<td>hdpi</td>
<td>xhdpi</td>
<td>xxhdpi</td>
</tr>
<tr>
<td>Small</td>
<td>**0.7%**</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Normal</td>
<td></td>
<td>1.5%</td>
<td>0.2%</td>
<td>**30.5%**</td>
<td>**36.7%**</td>
<td>**21.3%**</td>
</tr>
<tr>
<td>Large</td>
<td>0.1%</td>
<td>2.9%</td>
<td>1.6%</td>
<td>0.5%</td>
<td>0.9%</td>
<td>0.1%</td>
</tr>
<tr>
<td>Xlarge</td>
<td></td>
<td>2.0%</td>
<td></td>
<td>0.5%</td>
<td>**0.5%**</td>
<td></td>
</tr>
</table>
### KKBOX App 未特別用 OpenGL 無需納入考量
在一樣的 15 項測試案例下,測試總數從 8100 下降 450 但仍能保有足夠的信心。

### Pairwise Independent Combinatorial Testing tool
在先前的討論裡,我們提到了若要完整考慮 Android Dashabords 裡的 3 種要素,總計會有 12 x 15 x 3 x 15 = 8100 項測試。
這種做法雖然全面,但非常不實際,除了減少低風險的要素外,還有一種方法可以有效的減少測試組合,它稱作為 [Pairwise Testing](http://www.pairwise.org/)
Pairwise 是透過找出兩要素間的最大關聯取來代窮舉方法,在這個模型底下測試組合可以被大幅縮小但仍保有有效性,要素越多 Pairwise Testing 越能發揮效用。

### 安排測試順序
有了測試案例與測試組合後, 便可以開始安排測試進行的順序。測試階段會隨著軟體開發模式與發佈頻率的差異而有所不同,但普遍來說, 測試順序可概略的分成 3 個階段:
1. 新功能測試
2. 回歸測試
3. 驗收測試

#### 新功能測試
在這個階段,測試的重點在於確保新功能在各種面向是否正確。在 KKBOX 此時測試的面向包含了:
1. 功能測試
2. 相容性測試
3. 多國語系測試
#### 回歸測試
回歸測試的目的在於確保過去已開發的功能,不會受到這次新功能的增加而造成副作用。在這個階段我們會進行:
1. 問題的驗證
2. 探索測試
3. 既有功能的回歸測試
有別於一般的測試講的是`可重複性`與`預測性`,探索測試的精神在於不要總是測試一樣的路徑。當執行探索測試時,我們會設定測試時間為 120 分鐘,並賦予一或多個測試主題,比方說: 網路慢的時候。測試人員會一邊思考網路速度不佳的情境,一邊執行測試。它仰賴的是測試人員對於產品與技術的熟悉程度,透過這種非既定的測試能發現一些出乎意料的問題,並且能持續改進測試案例的涵蓋範疇。
#### 驗收測試
驗收測試其實也是一種回歸測試,只是它的範疇更小一點。規劃這樣的測試階段是因為通常回歸測試的期間,測試的版本會持續地推進。比方說開始測試時是 1.1.0 到了測試完成時已經進入到 1.1.3。這表示回歸測試期間跨越了 4 個版本,而每進版一次就增加了一些風險,因為這些變動也很有可能會造成退化問題 (Side-Effect)。
為了降低這些風險,在最後階段每一個版本我們都會進行所謂的驗收測試。這種快速精簡的驗收測試可以減少後續緊急修正的機率。在某些組織裡會採用僅執行驗收測試的策略,而沒有完整的回歸測試,這一切的考量最終都是回到時間、成本與品質上的取捨。
## 持續學習
由於台灣的產業結構,目前投身軟體產業的人相較於硬體仍是少數,而軟體測試工程師則是更是少之又少。Marc Andreessen 於 2011 年發表了一篇文章: [Why Software Is Eating The World
](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460),文章裡說的是軟體正在改變世界,而過去 6 年來此一趨勢演變的更加顯著,然而台灣的測試思維仍是過於硬體導向。透過這篇基礎文章,希望能發揮啟蒙的功效,並讓軟體測試的議題更加被重視與注意。
最後推薦一些持續學習的資源,也歡迎各方意見與想法上的交流。
### 測試社群
* Test Corner: https://www.facebook.com/groups/test.corner/
### 推薦書籍
##### 軟體測試之道: 微軟測試團隊的成功經驗、方法與技術

##### Google軟體測試之道:進行Google級的軟體測試

##### Specification by Example 中文版:團隊如何交付正確的軟體

##### Lessons Learned in Software Testing: A Context-Driven Approach
