📖 Testing
Terms
system requirement specification (SRS)
軟體需求規格書。
system design document (SDD)
系統設計書。
application under test (AUT)
將要被測試的應用 (包含多個系統)。
system under test (SUT)
將要被測試的系統。
class under test (CUT)
將要被測試的類別。
method under test (MUT)
將要被測試的方法。
將提供給 AUT / SUT 的測試資料。
expected output
程式或系統預期的執行結果。
test data
test input 與其對應的 expected output。
test case
測試案例。
它應當描述當系統在一個特定條件 (condition) 下時預期的反應。
通常我們可以根據測試案例的條件,來決定 test data 的具體值。
- 例如
根據一個登入功能,可以設計一個測試案例:
當使用者輸入正確的帳號和密碼時,應該成功登入。
negative test case
一種反向的測試案例。
它通常會給定 bad test input,看看 AUT / SUT 有何反應。
也就是說,這種測試案例是在考驗系統的例外處理做得好不好。
- 補充
這個環節通常可以觀察出你的社會化程度,
因為人心是險惡的,你永遠不知道使用者用什麼神祕姿勢在敲鍵盤 (bad input 千奇百怪);
因為公司的軟體服務是堅若磐石的,面對邪惡 bad input 絕不停機屈服。
- 記住
在多人團隊裡,不做例外處理的那個 developer 簡直就是老鼠屎。
test suite
Test Case 的集合。
true oracle
已存在一個軟體,能根據指定輸入,輸出絕對正確的結果。
這個軟體就被稱作 true oracle (在現實情境中,完美的幾乎不存在)。
有了他,你的 test case generation 就很方便。
實例:一個運作多年、人人稱羨的成熟系統,通常就逼近為一個 true oracle。
(雖然它可能是個舊系統,因效能問題要進行 refactor。但是我們在開發新系統時,還是可以拿它做驗證)
partial oracle
已存在一個特定算法,能根據輸入和輸出,來告訴你輸出是否正確。
這個特定算法就被稱作 partial oracle (僅在簡單場景下存在)。
有了他,你的 test case generation 就很方便。
實例:比如說檢驗排序算法的正確性,只要檢查左邊的數字是否必定大於右邊,就好了。
testing left-shift
研究指出:
- 引入 BUG 最常發生在:開發階段 (Coding)。若此時修正,成本低。
- 發現 BUG 最常發生在:系統測試階段 (System Test)。若此時修正,成本高。
而進行測試可以發現 BUG,
因此為了降低修正成本,我們需要將測試左移 (盡可能早地完成測試)。
- 測試左移前
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 →
- 測試左移後
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 →
incoming dependency
給定輸入的資料,通常是經由先前的操作而被動流入 unit of work 的資料。
例如:query 資料庫的資料、API 的回應。
outgoing dependency
呼叫外部函式,這是 unit of work 的一種 exit point。
例如:寫入資料庫、呼叫 API 或 webhook。
stub | mock | spy
類型 |
定義 |
目的 |
範例 |
stub |
假的輸入,模擬實作並取代原本的實作 |
隔離 incoming dependency |
假造的測試資料、物件或函式 |
mock |
假的輸出,模擬實作並取代原本的實作 |
隔離 outgoing dependency |
呼叫假的服務、寫入假的資料庫 |
spy |
監控呼叫,模擬實作但不取代原本的實作 |
記錄互動 |
是否被正確呼叫 |
Rules
F.I.R.S.T 原則
F.I.R.S.T Principles of Unit Testing
Fast
:快速的
Independent
:獨立的 – 測試之間要相互獨立,如果互相依賴的話,一個測試失敗會影響其他測試也都失敗
Repeatable
:可重複的 – 要在任何環境都可重複執行
Self-Validating
:可自驗證的 – 可從 report 直接了解失敗原因
Timely
:及時的 – 最好是在寫程式之前先寫測試 (TDD 概念)
3A rule
考量 Independent 時遵守該原則
Arrange
(Given)
建立此測試案例需要的初始值,和思考好的命名和變數命稱來讓測試更容易理解
Act
(When)
呼叫目標方法
Assert
(Then)
驗證是否符合預期
Management
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 →
Tester
- 根據 test spec、test plan,編寫 test case
- 事後發現缺陷
- 品質是檢驗出來的
- 測試只是幫助我們了解品質的現況
- 專注於測試的具體實現
- 近年來 programmer 也需扮演 tester 的角色
QA
- 編寫 test spec、test plan 或 automation
- 事前預防缺陷
- 品質是計畫、設計、建置出來的
- 思考什麼樣的品質能達到公司的目的
- 專注於產品需求的全貌
- 將品質的觀念帶給 developer team
- 不能歸 developer team 管轄,如同司法部門不能在行政部門底下
SDET
- Software Development Engineer in Test
- 建立測試架構平台
- 設計與優化自動化測試架構
QA Manager
V model

Test-Driven Development (TDD)
Reference
可參考:🎬 ArjanCodes - Test-Driven Development In Python
核心理念
先寫測試,再做開發
優點
- 可確保每次改動的品質,不會改 A 壞 B
- 可利用 test case 釐清使用情境,減少溝通成本
- 新人可透過 test case 更了解每個 function 在做什麼事,包含呼叫情境、傳入引數及預期結果
開發三步驟
- 第一步 紅燈 RED
寫測試,並且執行測試 (此時 test case 應當全 FAILED。很正常,因為你還沒實作介面)
- 第二步 綠燈 GREEN
寫程式。目的是要讓所有 test case PASSED。
- 第三步 重構 REFACTOR
重構程式。增加程式碼的可讀性、記憶體的優化、運算效能的優化…。與此同時須保持所有 test case PASSED。

Test Plan


Testing Requirements
|
SRS |
SDD |
code |
System Test |
✓ |
|
|
Integration Test |
✓ |
✓ |
|
Unit Test |
✓ |
✓ |
✓ |
Test Case Convention
naming
MethodUnderTest_TestedState_ExpectedBehavior
- 方法:
public double calculateDiscount(double price, double discount)
- 測試:
calculateDiscount_ValidPriceAndDiscount_CorrectDiscountApplied
檢查當傳入有效的價格和折扣百分比時,是否正確計算出折扣後的價格
calculateDiscount_ZeroDiscount_NoDiscountApplied
當折扣為零時,返回的價格應該等於原價格
calculateDiscount_NegativeDiscount_ThrowsError
如果傳入負數折扣,應該拋出錯誤或返回一個適當的錯誤訊息
given
:給定初始條件
when
:在某個狀態下
then
:預期產生什麼結果
功能性測試 Functional Testing
單元測試 Unit Testing
黑箱測試 black-box testing
在不知道程式內容的情況下所進行的測試 (通常需要 SDD)
白箱測試 white-box testing
在知道程式所有內容的情況下所進行的測試 (通常由 developer 設計與執行)
-
獨立路徑 independent path
- 說明:在程式流程圖中的,若其至少有一段路徑是前面找到的獨立路徑所沒有的,即為一條新的獨立路徑。
- 範例:如下程式流程圖,總共有 4 條獨立路徑 (Path 4 應為 1-2-3-4-5-9)。

-
第一種覆蓋:行數覆蓋 line coverage
- 難度:Easy
- 目標:測試執行時,只求能跑過程式的每一行
- 範例:如下
雖然 line coverage 是 100% (確實 get_email 每一行都被執行過),
但問題是,測試沒有測到 is_cool_user == False
的情況,
因此 branch coverage 就沒有 100%。
正好,此 MUT 的 bug 會在 is_cool_user == False
時發生 (user 為 None)。
-
第二種覆蓋:分支覆蓋 branch coverage
- 難度:Medium
- 目標:測試執行時,只求能跑過程式中的所有分支
- 範例:如下總共有 6 個分支


-
第三種覆蓋:路徑覆蓋 path coverage (basis path testing)
- 難度:Hard
- 目標:測試執行時,盡可能跑過程式的每個獨立路徑 (一個獨立路徑對應著一個 test case)
- 注意:即便通過路徑測試,也不代表程式一定不出錯 (因為有些錯誤,可能要多跑幾次迴圈才會出現)

-
循環複雜度 cyclomatic complexity
- 說明:路徑測試中,如果 test case 數量等於此值,可以保證所有程式都被測試覆蓋 (upper bound)。
- 關係:branch coverage ≤ cyclomatic complexity ≤ number of independent paths
- 算法一:
CC(G) = Number(edges) - Number(nodes) + 2
- 如範例程式流程圖,可用此方法得知 CC = 11 - 9 + 2 = 4
- 算法二 (適用無 goto):
CC = 條件敘述數 + 1
(switch-case 中每個 case 都算一個條件)
- 如範例程式流程圖,可用此方法得知 CC = 3 + 1 = 4
整合測試 Integration Testing
- 目的:檢驗軟體結構中各模組的的每個功能與性能介面功能是否正常
- 步驟:根據整合測試計畫逐步將單元測試完成整合成系統

整合方式:top-down

整合方式:bottom-up

整合方式:sandwich

流程


系統測試 System Testing
整合測試 Integration Testing
- 目的:測試多個模組之間的互動和整合情況。它的重點在於驗證模組之間的接口是否能正確協作。
端到端測試 End-to-End Testing (E2E)
- 目的:模擬使用者實際使用情境,測試從開始到結束的完整工作流程,目的是驗證不同系統或子系統之間的整合是否無縫運作 (涉及外部系統)。
回歸測試 Regression Testing
- 目的:驗證新功能的添加、現有功能的修改,或系統修復後,是否對已有功能產生了非預期的影響。
冒煙測試 Smoke Testing
自動化測試時,通常須備好大量 test cases。而在資源不足的情況下,就可改採冒煙測試,只準備少量 test cases (比如 10 個),它會跑過最主要的功能和邏輯,沒跑過就代表冒煙。
非功能性測試 Non-Functional Testing
壓力測試 Stress Testing
- 目的:透過超出正常負荷的方式操作系統,並測試其行為
驗收測試 Acceptance Testing
- 目的:測試使用者體驗 (UX) 是否良好
- 注意:使用者的要求經常超出最初的 SPEC,或者可能導致架構需調整或更改
使用者測試 User Testing
Alpha 測試 alpha testing
- 系統在開發團隊的自身環境中進行測試。
- 大部分功能皆已開發完畢。
Beta 測試 beta testing
- 系統在使用者的實際環境中進行測試。
- 主要 bug 皆已修復。