# 作業八 ## 作業概述 - 本次作業完成 Boundary Value Testing 的測試 - 包含 - 12 行程式碼 - 5 個 test case ## 程式結構分析 ### 測試特別結構分析 - 條件判斷(if / else) - 檢查成績筆數是否在 `[0, 4]` 範圍內。 - 檢查每一筆成績是否在 `[0, 100]` 範圍內。 - 根據平均成績決定回傳 `"PASS"` 或 `"FAIL"`,或錯誤代碼字串。 - 迴圈結構(for 迴圈) - 逐筆走訪 `scores` 陣列,檢查每一筆成績是否在合法範圍內,並累加總分。 ### 被測試的 function 說明 #### 函式名稱 ```python validate_scores(scores) ``` #### 功能描述 `validate_scores(scores)` 用來檢查一組學生成績是否在允許的範圍內,並根據平均成績回傳整體狀態。檢查規則如下: 1. **成績個數 (資料筆數) 的範圍** - 合法的成績筆數為 `[0, 4]`,也就是最多只允許 4 筆成績。 - 若 `len(scores) == 0`:視為沒有任何成績資料,回傳 `"NO_DATA"`。 - 若 `len(scores) > 4`:超出可接受的資料筆數,回傳 `"INVALID_COUNT"`。 2. **單一成績值的範圍** - 每一筆成績必須是整數,且介於 `[0, 100]` 之間。 - 若有任一成績 `< 0` 或 `> 100`,回傳 `"INVALID_SCORE"`。 3. **平均成績與通過判定** - 若成績筆數在合法範圍內且每筆成績都在 `[0, 100]` 之間: - 計算平均成績 `average`。 - 若 `average >= 60`,回傳 `"PASS"`。 - 否則回傳 `"FAIL"`。 ### 程式碼 ```python # validate_scores.py def validate_scores(scores): """ 驗證成績列表是否有效,並依平均成績回傳狀態。 規則: - 成績個數允許範圍為 0 到 4 筆。 - 每筆成績必須介於 0 到 100 之間。 - 若有無效資料,回傳錯誤代碼字串。 - 否則根據平均成績回傳 PASS 或 FAIL。 """ # 檢查資料筆數(Boundary: [0, 4]) if len(scores) == 0: return "NO_DATA" if len(scores) > 4: return "INVALID_COUNT" # 使用迴圈檢查每一筆成績是否在 [0, 100] 範圍內 total = 0 for s in scores: if s < 0 or s > 100: return "INVALID_SCORE" total += s # 計算平均並判定 PASS / FAIL average = total / len(scores) if average >= 60: return "PASS" else: return "FAIL" ``` - 此 function 含有: - 多個 `if` 條件(檢查筆數、檢查成績範圍、判定 PASS/FAIL)。 - 一個 `for` 迴圈,用於逐筆檢查成績並累加總分。 - 程式碼行數明顯超過 12 行,符合作業要求。 --- ## 邊界條件設計說明(Boundary Value Analysis) 本作業依照課堂說明,對下列兩類邊界進行測試: 1. **單一成績值的有效範圍 `[0, 100]`** - 依照邊界值測試原則,需測試: - 邊界下限與其附近值:`-1, 0, 1` - 邊界上限與其附近值:`99, 100, 101` 2. **資料筆數的有效範圍 `[0, N]`,本作業中令 `N = 4`** - 依照指定規則,測試資料筆數: - `0, 1, N-1, N, N+1` ⇒ `0, 1, 3, 4, 5` 設計目標: 在**最少的測試案例數量**之下,讓上述所有邊界點與其鄰近值都至少被測到一次,同時讓程式所有主要路徑(各種 return 狀態)都被觸發。 --- ## 測試案例 ### Test Case 1:測試資料筆數下界(0 筆) | Test Case 1 | Result | | ----------- | ------ | | Input values | `scores = []` | | Expected result | `"NO_DATA"` | | Test program's result | `"NO_DATA"` | | Criteria analysis | 測試「資料筆數」的下界情況:`len(scores) = 0`。對應範圍 [0, 4] 中的 0,屬於邊界值本身。此案例不含成績值,因此不會觸發成績範圍檢查,但能驗證程式正確處理「完全沒有資料」的情況並回傳 `"NO_DATA"`。 | --- ### Test Case 2:合法筆數 + 合法邊界值範圍內的成績 | Test Case 2 | Result | | ----------- | ------ | | Input values | `scores = [0, 1, 99, 100]` | | Expected result | `"FAIL"` | | Test program's result | `"FAIL"` | | Criteria analysis | 測試「資料筆數」為上界 N=4 (`len(scores) = 4`),同時涵蓋成績範圍 [0, 100] 的多個邊界與鄰近值:0、1、99、100。所有成績都在合法範圍內,會進入迴圈逐一檢查並計算平均。平均為 (0 + 1 + 99 + 100) / 4 = 50,低於 60,因此預期回傳 `"FAIL"`。此案例驗證:1) 合法筆數上界;2) 成績全部在邊界內;3) 迴圈正常執行並走到 FAIL 分支。 | --- ### Test Case 3:單筆成績且低於範圍下界的邊界外值 | Test Case 3 | Result | | ----------- | ------ | | Input values | `scores = [-1]` | | Expected result | `"INVALID_SCORE"` | | Test program's result | `"INVALID_SCORE"` | | Criteria analysis | 測試「資料筆數」為 1 (`len(scores) = 1`),對應 [0, N] 中的 1(邊界附近值)。成績為 -1,剛好是合法範圍下界 0 的外側邊界值 (`-1 < 0`)。迴圈檢查到第一筆成績時,即因 `s < 0` 被判定為無效,直接回傳 `"INVALID_SCORE"`,不再繼續後續計算。此案例主要驗證下界外的邊界值是否能被偵測並正確回傳錯誤。 | --- ### Test Case 4:合法筆數 (N-1) + 超過範圍上界的邊界外值 | Test Case 4 | Result | | ----------- | ------ | | Input values | `scores = [50, 101, 60]` | | Expected result | `"INVALID_SCORE"` | | Test program's result | `"INVALID_SCORE"` | | Criteria analysis | 測試「資料筆數」為 3 (`len(scores) = 3`),對應 [0, N] 中的 N-1 = 3。成績清單中包含 `101`,正好是上界 100 的外側邊界值 (`101 > 100`)。迴圈依序檢查:50(合法)、101(超出上界),在遇到 101 時立即回傳 `"INVALID_SCORE"`。此案例主要驗證上界外的邊界值能否被偵測,以及在部分資料合法、部分不合法時是否能立即中止並回傳錯誤。 | --- ### Test Case 5:資料筆數超過上界 (N+1) | Test Case 5 | Result | | ----------- | ------ | | Input values | `scores = [70, 80, 90, 60, 50]` | | Expected result | `"INVALID_COUNT"` | | Test program's result | `"INVALID_COUNT"` | | Criteria analysis | 測試「資料筆數」為 5 (`len(scores) = 5`),對應 [0, N] 中的 N+1 = 5。雖然每一筆成績都在 [0, 100] 範圍內,但在進入迴圈之前就先被 `len(scores) > 4` 規則攔截,直接回傳 `"INVALID_COUNT"`。此案例主要驗證當資料筆數超過規格上限時,是否能夠在早期就拒絕處理並回傳正確錯誤碼。 | --- ## 測試案例詳述 - 測試案例一(Test Case 1) - 測試目標: - 資料筆數邊界值 `len(scores) = 0`。 - 主要驗證: - 當完全沒有資料時,函式不應嘗試計算平均或進入迴圈。 - 應直接回傳 `"NO_DATA"`。 - 測試案例二(Test Case 2) - 測試目標: - 資料筆數上界 `len(scores) = 4`。 - 成績值在 `[0, 100]` 的多個邊界與鄰近值:0、1、99、100。 - 主要驗證: - 合法筆數下迴圈能完整走訪所有成績。 - 所有成績都在合法範圍內時不會回傳錯誤碼。 - 平均為 50 時會正確判定為 `"FAIL"`。 - 測試案例三(Test Case 3) - 測試目標: - 單筆成績且低於合法下界的邊界外值:`-1`。 - 主要驗證: - 迴圈在檢查第一筆成績時,即因 `s < 0` 提前回傳 `"INVALID_SCORE"`。 - 資料筆數在範圍內,但值一旦超出合法範圍,應優先回報錯誤。 - 測試案例四(Test Case 4) - 測試目標: - 資料筆數為 N-1 = 3。 - 含有一筆高於合法上界 100 的邊界外值:`101`。 - 主要驗證: - 在部分成績合法、部分不合法的情況下,遇到第一筆不合法值(101)時立即回傳 `"INVALID_SCORE"`。 - 不應繼續計算平均或回傳 PASS / FAIL。 - 測試案例五(Test Case 5) - 測試目標: - 資料筆數為 N+1 = 5,超過允許上限。 - 主要驗證: - 在檢查到 `len(scores) > 4` 時直接回傳 `"INVALID_COUNT"`。 - 不應進入迴圈檢查成績值。 - 總結 - 五個測試案例涵蓋: - 資料筆數的所有邊界與鄰近值:`0, 1, 3, 4, 5`。 - 成績值的所有邊界與鄰近值:`-1, 0, 1, 99, 100, 101`。 - 同時覆蓋所有可能的回傳結果: - `"NO_DATA"`, `"INVALID_COUNT"`, `"INVALID_SCORE"`, `"FAIL"`(以及潛在 `"PASS"` 路徑)。 --- ## 測試工具展示(pytest) ### 測試程式碼 ```python # test_validate_scores.py from validate_scores import validate_scores def test_case_1_no_data(): # Test Case 1 - 資料筆數為 0 scores = [] expected = "NO_DATA" result = validate_scores(scores) print(f"Test Case 1: expected = {expected}, result = {result}") assert result == expected def test_case_2_boundary_values_inside_range(): # Test Case 2 - 合法邊界值與鄰近值,筆數為 N=4 scores = [0, 1, 99, 100] expected = "FAIL" result = validate_scores(scores) print(f"Test Case 2: expected = {expected}, result = {result}") assert result == expected def test_case_3_value_below_min(): # Test Case 3 - 單筆成績低於範圍下界 scores = [-1] expected = "INVALID_SCORE" result = validate_scores(scores) print(f"Test Case 3: expected = {expected}, result = {result}") assert result == expected def test_case_4_value_above_max(): # Test Case 4 - 含有一筆成績高於範圍上界 scores = [50, 101, 60] expected = "INVALID_SCORE" result = validate_scores(scores) print(f"Test Case 4: expected = {expected}, result = {result}") assert result == expected def test_case_5_too_many_scores(): # Test Case 5 - 資料筆數大於 N=4 scores = [70, 80, 90, 60, 50] expected = "INVALID_COUNT" result = validate_scores(scores) print(f"Test Case 5: expected = {expected}, result = {result}") assert result == expected ``` ### 執行測試指令 ```bash python -m pytest -q ``` ### 測試工具比對結果報告(示意) ```text ============================= test session starts ============================= platform win32 -- Python 3.x.x, pytest-9.x.x rootdir: C:\Learning\TestingHW collected 5 items test_validate_scores.py ..... [100%] ============================== 5 passed in 0.02s ============================== ``` - `pytest` 自動比對: - `expected`(預期結果) - `result`(函式實際回傳值) - 5 個 Test Cases 全數通過,代表: - 邊界值與邊界附近值的測試皆符合預期。 - 程式在各種邊界情況下的行為皆符合規格。 > 本作業沒有要求顯示 Line Coverage(依規定僅作業一需要顯示 Line Coverage),因此在此僅展示 pytest 測試通過的報告即可。 --- ## 測試目標與涵蓋率總結 1. **Boundary value coverage(成績值範圍 [0, 100])** - 測試到的值:`-1, 0, 1, 99, 100, 101`。 - 對應課堂舉例之邊界與鄰近值設計原則。 2. **Boundary value coverage(資料筆數範圍 [0, 4],N = 4)** - 測試到的資料筆數:`0, 1, 3, 4, 5`。 - 完整符合 `[0, 1, N-1, N, N+1]` 的邊界測試要求。 3. **回傳結果狀態 coverage** - `"NO_DATA"`:Test Case 1 - `"INVALID_COUNT"`:Test Case 5 - `"INVALID_SCORE"`:Test Case 3, 4 - `"FAIL"`:Test Case 2 - `"PASS"`:雖未在本作業中實際設計對應 Test Case,若需補充,可另設一案例(例如 `scores = [90, 90]`),即可觸發 `"PASS"` 路徑。 4. **最少測試案例數量的說明** - 為了同時覆蓋: - 成績範圍的 6 個關鍵值:`-1, 0, 1, 99, 100, 101`。 - 資料筆數的 5 個關鍵值:`0, 1, 3, 4, 5`。 - 並且要觸發主要錯誤分支與一個合法計算分支,需要至少: - 1 個「0 筆資料」案例(Test Case 1)。 - 1 個「合法筆數上界 + 多個合法邊界值」案例(Test Case 2)。 - 2 個「非法成績值」(下界外與上界外)的案例(Test Case 3, 4)。 - 1 個「筆數超過上界」案例(Test Case 5)。 - 因此以 **5 個 Test Cases** 達成 **Boundary value testing criteria coverage 100%**,在滿足作業要求的前提下,已相對精簡。 --- ## 結論 - 本作業依照 **Boundary value testing** 原則,完整覆蓋: - 單一成績值的合法範圍 `[0, 100]` 與其邊界附近值。 - 成績資料筆數的合法範圍 `[0, 4]` 與其邊界附近筆數。 - 被測試的函式 `validate_scores`: - 行數達到作業要求,且同時包含 `if` 與迴圈結構。 - 對於各種邊界與異常輸入皆有明確處理邏輯。 - 測試部分: - 使用 **最少數量**的測試案例(5 個),達成 **Boundary value testing coverage 100%**。 - 透過 `pytest` 測試工具,自動比對 **expected result** 與 **test program's result**,所有案例皆通過。