JAVASCRIPT V2 · ENGINEERING · 78
Testing Basics:把「我試過可以」變成可重複的 Contract Evidence
手動點一次畫面只能證明「剛才那一次看起來可以」。Automated test 的價值是把 input、behavior、expected outcome 寫成可重複執行的 contract,讓 refactor、bug fix 與未來修改都有 regression evidence。
Learning outcomes
- 用 Arrange / Act / Assert 描述 test。
- 從 requirement 找 normal、boundary、invalid cases。
- 區分 example、manual check 與 automated regression test。
- 理解 deterministic、isolated、behavior-focused test。
- 能從 test failure 判斷 implementation、test expectation 或 shared state 哪裡有問題。
1. Why now:程式會改,記憶不會自動驗證
function isPassed(score) {
return score >= 60;
}
今天你手動輸入 80 得到 true。明天有人把規則改成 score > 60,80 仍然通過,但 60 已經被破壞。如果沒有 boundary test,這個 regression 很容易進 production。
2. Mental model:Test 是可執行的 Contract Example
Given:
score = 60
When:
isPassed(score)
Then:
result === true
Test 不只是「找 bug」。它同時記錄這個 behavior 對未來維護者意味著什麼。
3. Worked Example A:Arrange / Act / Assert
test(
"60 is passing",
() => {
// Arrange
const score = 60;
// Act
const actual =
isPassed(score);
// Assert
expectEqual(
actual,
true
);
}
);
AAA 是閱讀結構,不是宗教規範。目的在讓 failure 時能快速分辨 setup、action、expectation。
4. Boundary Analysis:59 / 60 / 61 比 80 更有資訊
59 → false
60 → true
61 → true
Requirement 出現 >=、range、length、limit、empty/non-empty 時,boundary 附近通常最值得測。
5. Worked Example B:Validation Matrix
normalizeScore("0") → 0
normalizeScore("60") → 60
normalizeScore("100") → 100
normalizeScore("-1") → error
normalizeScore("101") → error
normalizeScore("abc") → error
normalizeScore("") → error
一個 behavior contract 往往不是一個 test,而是一組 partition:valid、boundary、invalid、empty。
6. Deterministic:同條件應得到同結果
function greeting() {
return Date.now()
+ Math.random();
}
如果 test 直接依賴現在時間、random、production network,結果可能因環境波動而改變。Engineering 的做法是控制 boundary,而不是祈禱 CI 當下網路穩定。
7. Isolation:Test 不應互相污染
let students = [];
test("adds one", () => {
students.push("Amy");
});
test("starts empty", () => {
// 如果上一題沒 reset,
// 這題就被污染
});
共享 mutable state 會產生 order-dependent tests。每個 test 應建立自己的 fixture,或在明確 setup/teardown 中重置。
8. Worked Example C:Test Behavior,不綁 Implementation
expectEqual(
average([80, 100]),
90
);
這個 test 不在乎 average 用 loop、reduce 還是其他演算法。只要 observable contract 沒變,重構不該逼你重寫測試。
9. Common mistakes
A. 只測 happy path
80 通過無法證明 60 boundary 正確。
B. 一個 test 驗十件事
失敗時不知道哪個 contract 壞掉。
C. Test 依賴 production API
外部 outage 會讓 unit-level signal 失真。
D. 為 implementation detail 寫 assertion
合理 refactor 會造成大量假紅燈。
E. Test 名稱只有「works」
Failure report 無法告訴你哪個 behavior 出錯。
10. Debug evidence:Test 紅不等於 Production Code 一定錯
Failure checklist:
1. actual input?
2. expected contract?
3. actual output/error?
4. requirement changed?
5. shared state?
6. environment dependency?
Test 本身也是 code,也可能 expectation 過時或 setup 錯誤。
11. Guided exercise:Score Boundary Tests
function isPassed(
score
) {
return score >= 60;
}
// write tests for:
// 59, 60, 61
要求每個 case 有清楚名稱,failure message 能指出 input 與 expected。
12. Independent exercise:normalizeScore Contract
先寫 cases,再實作 function。至少包含:
"0"
"100"
"60"
"-1"
"101"
"abc"
""
" 90 "
你必須先定義空字串與 whitespace 的 contract,不能讓 JavaScript coercion 偷偷替你決定產品規則。
13. Challenge:Regression First
假設 production bug 是「score=60 被判定 fail」。先寫一個會失敗的 regression test,確認它真的抓到 bug,再修 implementation,最後確認 test 轉綠。這就是 red → green 的最小 bug-fix evidence。
Project checkpoint:Grade Tracker Contract Matrix
normalizeScore
├─ valid
├─ boundary
├─ invalid
└─ empty
isPassed
├─ 59 false
├─ 60 true
└─ 61 true
average
├─ empty
├─ one item
└─ multiple items
從這一課開始,Final Project 的每個純規則都要有 executable tests,不再只靠 UI 點擊。
14. Vocabulary / Summary
- test case:特定 input/condition 與 expected behavior。
- assertion:對 observable result 的檢查。
- boundary case:規則切換附近的值。
- deterministic:受控條件下可穩定重現。
- fixture:test 所需的 setup data/state。
- regression:已修問題再次出現。
15. Further reading
Knowledge check · Mastery
- 說明 manual check 與 automated regression 的差異。
- 為 0–100 score contract 設計 partitions/boundaries。
- 完成頁面下方 Real Test Runner missions。
- 說明「test fail」有哪些可能 root causes。