# 作業八
## 作業概述
- 本次作業完成 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**,所有案例皆通過。