12 / 90

Day 12: 測試與可重複的程式

撰寫檢查可觀察行為的測試斷言,在暫時目錄中建立一次性 fixture,並在改動任何東西之前把失敗的測試分診為程式缺陷、測試缺陷或環境故障。 Retrieval and lower-new-content review are part of the 480-minute ledger.

Self-Directed Learning & Python 480 active minutes (8 hours focused study)
01 120 分鐘

記錄行為的測試斷言

只有在記錄預測和觀察到的證據之後才允許協助。

Prerequisites

完成上一個活動;如遇阻礙,使用列出的恢復連結。

Learning Objective

用 assertEqual 和 assertRaises 撰寫 unittest.TestCase,執行 python -m unittest -v,並解釋結尾的 OK 行證明了什麼、沒證明什麼。

Instructions

每個 test_ 方法是一條行為主張,名字應像文件一樣可讀,例如 test_empty_list_returns_zero。assertEqual(actual, expected) 檢查值;with self.assertRaises(例外型別): 證明呼叫按契約所說的方式失敗。執行測試有兩種方式。在專案目錄執行 python -m unittest -v(等同於 python -m unittest discover -v),會發現該目錄中名為 test_*.py 的模組。python test_counter.py 只透過檔案末尾的 unittest.main() 執行這一個檔案裡定義的測試;unittest.main() 不會發現旁邊的 test_*.py 模組。

Practice & Prediction Log

在空目錄中撰寫 counter.py,其中的 count_non_empty(lines) 回傳含有非空白字元的元素個數,元素不是 str 時拋出 TypeError。在 test_counter.py 中撰寫含四個測試的 CounterTests 類別:空列表回傳 0;純空白行被忽略;混合行只數非空行;非字串元素(例如 None)拋出 TypeError,用 assertRaises 檢查。先預測四個結果,再在該目錄執行 python -m unittest -v。Python 3.13/3.14 上的預期執行(測試按字母順序執行;耗時會變化;較舊的 Python 括號內的名字可能較短):
test_empty_list_returns_zero (test_counter.CounterTests.test_empty_list_returns_zero) ... ok
test_mixed_lines_count_only_non_empty (test_counter.CounterTests.test_mixed_lines_count_only_non_empty) ... ok
test_non_string_item_raises_type_error (test_counter.CounterTests.test_non_string_item_raises_type_error) ... ok
test_whitespace_only_line_is_ignored (test_counter.CounterTests.test_whitespace_only_line_is_ignored) ... ok
...
Ran 4 tests in 0.001s
OK

Independent Mastery Criterion

執行以 Ran 4 tests ... OK 結束且其中包含一個 assertRaises 測試,並能說明 OK 的含義是這些輸入下每條斷言都成立——而不是函式對所有可能輸入都正確。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: Classes and functions: Test cases (TestCase assert methods)

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Normative reference for unittest.TestCase, assertEqual, assertRaises, and python -m unittest output.

來源前置條件:

安全回退方案(離線彈性):

unittest ships with the interpreter, so every example runs offline; help(unittest.TestCase) documents the assert methods.

對比候選資源與選型分析 (2)
pytest documentation ↗ compared_alternative [MIT]

Strong third-party runner, but it adds an installation step and its own conventions; unittest needs no environment change and is the baseline the rest of the curriculum assumes.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

02 120 分鐘

用暫時 fixture 測試檔案處理

只有在記錄預測和觀察到的證據之後才允許協助。

Prerequisites

完成上一個活動;如遇阻礙,使用列出的恢復連結。

Learning Objective

在測試內用 tempfile.TemporaryDirectory 和 Path.write_text 建立檔案 fixture,覆蓋 summarize_scores 的三種行為:有效內容、遺失檔案、無效內容。

Instructions

tempfile.TemporaryDirectory() 建立會自動刪除的私有目錄,所以測試絕不碰真實使用者檔案。測試依賴的每條路徑——包括必須不存在的那條——都要在該目錄裡建構:Path(d) / "does-not-exist.txt" 不會和任何東西衝突,而裸寫的 "does-not-exist.txt" 會相對目前工作目錄解析,那裡若恰好有同名檔案,test_missing_file 就會失敗。本活動使用嚴格契約:summarize_scores(path) 回傳 {name: 最高分},忽略空行,檔案遺失時拋 FileNotFoundError,分數行無效時拋 ValueError。若你改為測試 Day 10 的容錯契約,則斷言其 missing 標誌和 skipped 列表——選定一種契約並一致地測試它。

Practice & Prediction Log

撰寫 scores.py,使下面這個測試套件(同一目錄下的 test_scores.py)通過:
# file: test_scores.py
import tempfile
import unittest
from pathlib import Path

from scores import summarize_scores


class SummarizeTests(unittest.TestCase):
def test_valid_file(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "scores.txt"
p.write_text("ana,7\nbo,9\nana,8\n", encoding="utf-8")
self.assertEqual(summarize_scores(str(p)), {"ana": 8, "bo": 9})

def test_missing_file(self):
with tempfile.TemporaryDirectory() as d:
missing = Path(d) / "does-not-exist.txt"
with self.assertRaises(FileNotFoundError):
summarize_scores(str(missing))

def test_invalid_score(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "scores.txt"
p.write_text("ana,7\nbo,x9\n", encoding="utf-8")
with self.assertRaises(ValueError):
summarize_scores(str(p))
# end file
先預測,再執行 python -m unittest -v。預期:Ran 3 tests ... OK。然後在該目錄建立一個名為 does-not-exist.txt 的檔案並再次執行:結果必須仍是 Ran 3 tests ... OK,因為遺失路徑位於它自己的全新暫時目錄中。

Independent Mastery Criterion

測試套件自包含且在乾淨執行中通過;fixture 用完即棄;每個預期值都在測試名或斷言中可見,而不是靠暗示。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: Classes and functions: Test cases (TestCase assert methods)

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Normative reference for the three-behavior fixture test suite.

來源前置條件:

安全回退方案(離線彈性):

The fixture suite runs entirely on the local interpreter and temporary directories.

對比候選資源與選型分析 (2)
pytest documentation ↗ compared_alternative [MIT]

pytest tmp_path fixtures are elegant, but tempfile plus unittest keeps the zero-dependency guarantee for this block.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

linking PSF License Version 2

Python Standard Library: tempfile

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: TemporaryDirectory for disposable on-disk fixtures

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Normative reference for creating and auto-removing fixture files inside tests.

來源前置條件:

安全回退方案(離線彈性):

TemporaryDirectory is part of the installed standard library and needs no page access.

對比候選資源與選型分析 (2)
pytest tmp_path fixture ↗ compared_alternative [MIT]

Equivalent capability scoped to the pytest runner; deferred until the curriculum adopts pytest.

io — Core tools for working with streams ↗ compared_alternative [PSF License Version 2]

Lower-level file-object interface without automatic cleanup semantics; tempfile was selected for the safe scratch-file pattern the drills require.

03 120 分鐘

先診斷失敗的測試再動手

只有在記錄預測和觀察到的證據之後才允許協助。

Prerequisites

完成上一個活動;如遇阻礙,使用列出的恢復連結。

Learning Objective

拿到 total_score 和一個報 AssertionError: 20 != 30 的失敗測試,在改動任何東西之前判定是程式錯、測試錯還是環境錯。

Instructions

FAIL 區塊會給出測試名、失敗的斷言行以及觀察值與期望值。例外類別是線索,不是判決。ModuleNotFoundError/ImportError 或 FileNotFoundError 會讓環境——執行的是哪個直譯器、裝了哪些套件、工作目錄、某條路徑——成為首要嫌疑,但在歸咎之前要用 sys.executable、Path.cwd()、Path(...).exists() 等證據確認。AssertionError 表示程式執行了,卻產生了測試未預期的值;這通常指向程式或測試,但設定、資料檔案或環境的差異也可能改變結果,所以僅憑例外型別永遠不能排除環境。這裡的證據是:測試在 TemporaryDirectory 中自己寫入三行 fixture(沒有外部檔案,也不依賴 cwd),在你試過的每個直譯器下都重現同樣的失敗,而觀察 20 與期望 30 恰好相差一行 10 分。total_score 呼叫 f.readlines()[1:] 想跳過 fixture 裡並不存在的表頭,於是丟掉了第一行真實資料。測試期望是對的——檔案沒有表頭——所以最小修復是刪掉切片,而不是改測試。

Practice & Prediction Log

把下面兩個檔案放進空目錄:
# file: total.py
def total_score(path):
"""Return the sum of the scores in rows like "a,10" (no header row)."""
total = 0
with open(path, encoding="utf-8") as f:
lines = f.readlines()[1:]
for line in lines:
name, _, raw = line.strip().rpartition(",")
total += int(raw)
return total
# end file
# file: test_total.py
import tempfile
import unittest
from pathlib import Path

from total import total_score


class TotalTests(unittest.TestCase):
def test_three_rows(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "rows.txt"
p.write_text("a,10\nb,10\nc,10\n", encoding="utf-8")
self.assertEqual(total_score(str(p)), 30)
# end file
先預測結果,再執行 python -m unittest -v。它報告 FAIL: test_three_rows ... AssertionError: 20 != 30。改動之前先記錄診斷:症狀(丟了一行)、證據(fixture 在測試內建構;30 - 20 = 一行 10 分)、假設([1:] 切片假設有表頭)、分類(程式缺陷,測試為權威)、最小修復(讀取所有行),然後重跑看到 OK。

Independent Mastery Criterion

你憑證據把失敗正確歸類為程式缺陷,論證測試的期望為何成立,且最小修復後重跑報告 OK。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: Classes and functions: Test cases (TestCase assert methods)

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Normative reference for reading unittest failure reports during the code-vs-test-vs-environment triage.

來源前置條件:

安全回退方案(離線彈性):

The seeded bug and its failure output reproduce locally with the installed interpreter.

對比候選資源與選型分析 (2)
pytest documentation ↗ compared_alternative [MIT]

pytest produces richer diffs, but the triage skill is taught on the standard runner every learner already has.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

04 120 分鐘

可重複程式頂點任務

獨立掌握檢查(不使用 AI 協助)。

Prerequisites

完成上一個活動;如遇阻礙,使用列出的恢復連結。

Learning Objective

在全新暫時目錄中組裝可重複程式:實作 summarize_scores 的 scores.py、覆蓋三個 fixture 的 test_scores.py,以及 requirements.txt;然後執行 python -m unittest -v 並展示所有測試通過。

Instructions

閉卷。requirements.txt 記錄依賴;當程式只用標準函式庫時,一行註解如 # stdlib only, no third-party dependencies 就是合法的 requirements 檔案。用你選定的直譯器跑套件並記錄下來。如果執行報告沒有測試被執行(Ran 0 tests),檢查 test_*.py 命名慣例和執行發現命令的目錄。

Practice & Prediction Log

預期結果:在只含 scores.py、test_scores.py 和 requirements.txt 的全新目錄中,python -m unittest -v 以 Ran 3 tests ... OK 結束(有效檔案、遺失檔案、無效分數)。命令列 python scores.py FILE 成功時印出彙總字典並以 0 結束;檔案遺失或行格式錯誤時,向 stderr 印出一行錯誤並以 1 結束,而不是顯示 traceback,因此只有意外錯誤才會以 traceback 結束。日誌記錄預測、觀察輸出和所用直譯器。

Independent Mastery Criterion

獨立完成:乾淨目錄執行中三個測試全部 OK;requirements.txt 如實列出依賴;書面恢復計畫寫明套件失敗時去哪裡查。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: Classes and functions: Test cases (TestCase assert methods)

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Normative reference for the discovery convention the capstone depends on.

來源前置條件:

安全回退方案(離線彈性):

The capstone is a clean-directory local run with no online dependency.

對比候選資源與選型分析 (2)
pytest documentation ↗ compared_alternative [MIT]

Kept as a future alternative runner; the capstone must pass with zero third-party installs.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

linking PSF License Version 2

The Python Tutorial: Virtual Environments and Packages

作者 / 機構: Python Software Foundation | 原始出處連結 ↗

對應章節 / 時間戳: Section 12 (requirements files and reproducible environments)

驗證版本與日期: Python 3.12 documentation; verified 2026-10-30

入選理由:

Reference for the requirements.txt the capstone assembles to make the program repeatable.

來源前置條件:

安全回退方案(離線彈性):

The capstone requirements file only needs the standard library, so no install is required.

對比候選資源與選型分析 (2)
pip documentation ↗ compared_alternative [MIT]

Reference for requirements syntax when the learner later adds real third-party dependencies.

virtualenv documentation ↗ compared_alternative [MIT License]

Mature third-party environment tool with overlapping behavior; the stdlib venv tutorial was selected to avoid an extra dependency for first-time setup.