今天讀的是《單元測試的藝術(第 3 版)》第一章 1.7.2 之後的內容,作者在這裡聚焦在兩件事:
區分「單元測試」和「整合測試」。
定義什麼是「好的單元測試」。
作者給的定義可以濃縮成一句話:
單元測試測的是一個「工作單元」,而且完全控制該單元所有的依賴;
整合測試則是讓這個單元真的去碰外部依賴(資料庫、網路、時間、隨機數等)。
換句話說:
單元測試:
工作單元(被測試的 function / class)要跟依賴「拆開」,用 stub/mock 或其他方式隔離。
測試應該能在沒資料庫、沒網路、沒部署的情況下跑完,結果也要穩定。
整合測試:
允許、甚至刻意使用真實依賴,例如:
真實資料庫連線
真實 HTTP 請求
真實時間(Date.now())
真實隨機數(Math.random())
用來驗證系統各部分「真的串得起來」。
這也解釋了為什麼很多「看起來像單元測試」的測試,其實是整合測試:只要你沒完全掌握依賴(例如測試會打到線上 API),它的結果就容易不穩定,也就稱不上嚴格意義的單元測試 。 kojenchieh.pixnet
作者列了一份清單,來檢查你寫的是不是「真正的單元測試」:
可以隨時重跑兩週前、兩個月前、兩年前的測試嗎?
團隊成員可以在自己的機器上跑你寫的測試嗎?
所有測試可以在幾分鐘內跑完嗎?
能不能「一鍵」跑完全部測試?
一個基本的測試能在幾分鐘內寫完嗎?
別的團隊的程式壞掉時,你的測試會不會跟著掛掉?
在不同機器或環境上執行,結果會一樣嗎?
沒有資料庫、網路或部署的情況下,測試還跑得起來嗎?
刪除/移動一個測試,不會影響其他測試嗎?
只要其中有一題回答是「否」,作者就傾向把那個測試視為整合測試,而不是單元測試 。 gist.github 這個觀點滿實際的:與其在定義上糾結,不如看它實際上是不是「小而快、穩定可重現」。
作者對「好單元測試」的定義,可以簡短改寫成:
單元測試是一段自動化的程式碼,透過某個進入點呼叫工作單元,並檢查一個或少數明確的行為假設。它應該:
易寫、跑得快。
完全自動化,可重複執行。
在相同輸入與相同程式碼下,每次結果都一樣(不依賴隨機、時間、外部資源)。
具可讀性、可維護性。
值得信任:看到它紅/綠時,不需要再開 debugger 二次確認 。 artofunittesting
這裡有幾個關鍵字值得記在心裡:
Automated:不能需要人眼看 log 才知道結果。
Fast:慢測試會讓人不想常跑,TDD 節奏也會被打亂。
Consistent:不能今天紅,明天綠,後天又紅(隨機數/時間/外部依賴是常見元兇)。
Readable / Maintainable / Trustworthy:好測試本身也要是「好程式碼」。
作者特別提醒:
「會寫單元測試」和「會用 TDD 開發」是兩套不同的技能。
TDD 是一種開發流程:先寫測試 → 寫最小實作 → 重構。
它高度仰賴「測試本身是優良的」,不然紅燈/綠燈給的訊號會非常混亂。
在 TDD 的紅燈 → 綠燈 → 重構循環裡,有一個很重要的點:
紅燈階段,在不改測試的前提下,修改產品程式碼讓紅燈變綠燈,其實同時也在「測試測試程式本身」:
測試能在該失敗時失敗、該通過時通過,可信度就會變高。
作者也建議可以把 TDD 拆成三個獨立練習的技能:
能寫出優良的單元測試(本書重點)。
能習慣「先寫測試,再寫實作」的節奏。
能用測試回饋來推動系統設計(例如應用 SOLID 等設計原則)。
這三個可以分階段練,不用一次全部到位。
接著是今天實作的小 Kata,可以想像成一個「洗牌發牌系統」:
有一副牌(為了簡化,只看「顏色」或「花色」)。
每種顏色有固定數量,例如 5 種顏色 × 每種 30 張 → 總共 150 張牌。
要先建立牌堆 → 洗牌 → 均勻發給 N 個玩家,每人固定拿 3 張。
這一路剛好練到三組實用的 JS 語法:
用 flatMap + Array(n).fill() 生成固定數量的元素。
用 Fisher-Yates 洗牌做公平隨機。
用 Array.from + slice 把大陣列分段。
需求:
「每種花色生成固定數量的牌,最後組成一個牌堆(一維陣列)」。
可以用 Array(n).fill() 搭配 flatMap() 寫得很精簡 : developer.mozilla
const suits = ['♠️', '♥️', '♦️', '♣️']; // 或顏色:['red', 'blue', ...]
const cardsPerSuit = 30;
const deck = suits.flatMap((suit) => Array(cardsPerSuit).fill(suit));
// deck 範例:['♠️', '♠️', ..., '♥️', '♥️', ..., '♣️', ...]
拆解:
Array(cardsPerSuit) 建立一個長度為 cardsPerSuit 的陣列,搭配 .fill(suit) 後變成每個位置都是該花色的「密集陣列」。 developer.mozilla
suits.flatMap(...):
對每個 suit 產生一段陣列。
把所有小陣列攤平成一個大陣列(只攤平一層)。 developer.mozilla
相較之下,flat() 只做「攤平」,不做轉換;flatMap() 可以同時完成「轉換 + 攤平一層」,在這種「一對多展開」場景特別好用 。 developer.mozilla
一開始直覺的想法是:
做一個亂數索引陣列,再用它把原本排好序的牌堆重新映射。
概念示意:
const deck = ['A', 'B', 'C', 'D'];
const indices = [2, 0, 3, 1];
const shuffled = indices.map((i) => deck[i]);
// shuffled → ['C', 'A', 'D', 'B']
但這種做法會多出一個「索引陣列」要管理,而且還要先解決「索引陣列本身如何公平洗牌」這個問題。
後來改用經典的 Fisher-Yates 洗牌 : stackoverflow
從陣列最後一個元素開始,對每個位置
i,在[0, i]範圍內選一個randomIndex,與i交換,然後i--繼續往前。
實作:
export function shuffle(array) {
for (let i = array.length - 1; i > 0; i -= 1) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[array[i], array[randomIndex]] = [array[randomIndex], array[i]];
}
}
兩種思路對比:
做法 | 需要的陣列 | 空間複雜度 | 思考負擔 |
|---|---|---|---|
亂數映射索引 + 重組 | 原陣列 + 索引陣列 + 新陣列 | O(n) 以上 | 要先想怎麼把索引洗公平 |
Fisher-Yates 直接洗原陣列 | 只有原陣列 | O(1) 額外空間 | 只要想「每步跟誰交換」 |
Fisher-Yates 的優點 : en.wikipedia
原地(in-place)操作,不用額外索引陣列 。 en.wikipedia
每種排列出現的機率完全相同(公平隨機)。
單一一層迴圈,時間複雜度 O(n)。
在洗牌發牌這種情境裡,牌堆 deck 通常是函式內部變數,不需要重用原順序,因此用 in-place 洗牌是合理又簡潔的選擇。
接著要做的是「發牌」:
假設洗好的 deck 有 150 張,要把它切成 50 組,每組 3 張。
可以用 Array.from 搭配 slice:
function chunkBySize(array, size) {
return Array.from(
{ length: Math.ceil(array.length / size) },
(_, index) => array.slice(index * size, index * size + size),
);
}
// 範例:
const deck = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
const hands = chunkBySize(deck, 3);
// hands → [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
重點:
Array.from({ length: N }) 會產生一個長度為 N 的密集陣列,每個索引都存在 。 developer.mozilla
第二個參數是 mapping 函式,類似 map 的 callback:
index 就是第幾個切片。
array.slice(index * size, index * size + size) 就是該段的子陣列 。 developer.mozilla
這個模式也可以直接套在「洗牌發牌」流程裡:
export function dealHands(playerCount) {
const suits = ['♠️', '♥️', '♦️', '♣️'];
const cardsPerSuit = 30;
const deck = suits.flatMap((suit) => Array(cardsPerSuit).fill(suit));
shuffle(deck);
return Array.from(
{ length: playerCount },
(_, index) => deck.slice(index * 3, index * 3 + 3),
);
}
這兩個看起來都在建立長度為 n 的陣列,但差異在「每個索引是否真的有元素」 。 geeksforgeeks
// 稀疏陣列:只留空洞,不填值
const sparse = Array(3);
console.log(sparse); // [ <3 empty items> ]
console.log(sparse.length); // 3
console.log(sparse[0]); // undefined
console.log(0 in sparse); // false ← 索引 0 不存在!
// 密集陣列:每個位置都有「實際元素」,值是 undefined
const dense = Array.from({ length: 3 });
console.log(dense); // [ undefined, undefined, undefined ]
console.log(dense.length); // 3
console.log(dense[0]); // undefined
console.log(0 in dense); // true ← 索引 0 存在!
差別在於很多陣列方法會跳過空洞,例如:
// ❌ 不會產生 [0, 1, 2]
Array(3).map((_, i) => i);
// → [ <3 empty items> ]
// ✅ 產生 [0, 1, 2]
Array.from({ length: 3 }, (_, i) => i);
// → [0, 1, 2]
// ✅ 或者先 fill 再 map
Array(3).fill(null).map((_, i) => i);
// → [0, 1, 2]
MDN 也明確提到:Array.from() 不會建立稀疏陣列,缺少的 index 會被填成 undefined,因此之後用 map / forEach 等方法拿來做索引運算時,行為會比較預期內 。 developer.mozilla
在這個 Kata 裡,像 chunkBySize 或 dealHands 這種「我要確實跑 n 次 callback」的場景,選擇 Array.from({ length: n }) 會比 Array(n) 安全,也更語意化。
在這次 Kata 的過程裡,順便把幾個「最終沒有被採用」的方案也記錄下來,當作自己的設計決策歷史。
直覺方案:suits[Math.floor(Math.random() * suits.length)]
這是「放回抽樣」,每次從花色陣列隨機挑一個,總數不受限制。
實際需求:
類似「一副牌有固定張數」,每張牌只能被發一次 → 不放回抽樣。
結論:
先產出完整的牌堆,再用 Fisher-Yates 做不放回的公平洗牌,符合總量守恆的要求 。 stackoverflow
評估過的版本:
手動展開:[...Array(30).fill('♠️'), ...Array(30).fill('♥️'), ...]
可行但顏色寫死,之後要改會很痛苦。
Array.from({ length: 120 }, (_, i) => suits[Math.floor(i / 30)])
依賴算式 Math.floor(i / 30),可讀性較差。
reduce + concat
比 flatMap 冗長,效率與語意都不如 flatMap 明確 。 developer.mozilla
flatMap 最終採用:suits.flatMap((suit) => Array(cardsPerSuit).fill(suit))
一眼就看得出「每種花色展開成多張牌再攤平成一個牌堆」。
Array(n):需要先 .fill() 才能用 map,否則會得到一個 callback 根本沒執行的稀疏陣列 。 geeksforgeeks
Array.from({ length: n }):天然產生可遍歷的密集陣列,配合 mapping 函式是最直覺的「產生 n 個元素」工具 。 developer.mozilla
核心模組(例如 dealHands, countSomething)盡量寫成純函式:輸入 → 輸出,可預期、可測試。
產生隨機輸入、讀寫 console / 檔案的部分,集中在 main 層或 CLI 層。
這讓單元測試可以專心測邏輯,整合測試再負責「真的跑一輪洗牌發牌」。
今天的閱讀和實作其實剛好互相呼應:
測試觀念:
好的單元測試要快、穩定、可重複、可相信 。 gist.github
要達到這個目標,必須學會把「工作單元」跟「外部依賴」拆開。
實作上的語法選擇:
用 Fisher-Yates 做不放回洗牌,避免寫出結果會「偏向某些排列」的隨機邏輯 。 youtube
用 flatMap + Array(n).fill() 生成固定數量元素的陣列,語意清楚又好維護 。 developer.mozilla
用 Array.from({ length: n }) + slice 拆分陣列,避開 Array(n) 的稀疏陣列陷阱 。 developer.mozilla
未來要真正用 TDD 寫這類洗牌/發牌/配對邏輯時,可以:
把「生成隨機牌堆」視為整合層,單測只針對純邏輯(例如:給定一個牌堆,發牌結果是否符合規則)。
用「紅燈 → 綠燈 → 重構」循環,持續檢查:
測試寫得好不好、測試本身是否可信,以及是否真的有幫助設計出更好維護的程式。
今天讀的是《單元測試的藝術(第 3 版)》第一章 1.7.2 之後的內容,作者在這裡聚焦在兩件事:
區分「單元測試」和「整合測試」。
定義什麼是「好的單元測試」。
作者給的定義可以濃縮成一句話:
單元測試測的是一個「工作單元」,而且完全控制該單元所有的依賴;
整合測試則是讓這個單元真的去碰外部依賴(資料庫、網路、時間、隨機數等)。
換句話說:
單元測試:
工作單元(被測試的 function / class)要跟依賴「拆開」,用 stub/mock 或其他方式隔離。
測試應該能在沒資料庫、沒網路、沒部署的情況下跑完,結果也要穩定。
整合測試:
允許、甚至刻意使用真實依賴,例如:
真實資料庫連線
真實 HTTP 請求
真實時間(Date.now())
真實隨機數(Math.random())
用來驗證系統各部分「真的串得起來」。
這也解釋了為什麼很多「看起來像單元測試」的測試,其實是整合測試:只要你沒完全掌握依賴(例如測試會打到線上 API),它的結果就容易不穩定,也就稱不上嚴格意義的單元測試 。 kojenchieh.pixnet
作者列了一份清單,來檢查你寫的是不是「真正的單元測試」:
可以隨時重跑兩週前、兩個月前、兩年前的測試嗎?
團隊成員可以在自己的機器上跑你寫的測試嗎?
所有測試可以在幾分鐘內跑完嗎?
能不能「一鍵」跑完全部測試?
一個基本的測試能在幾分鐘內寫完嗎?
別的團隊的程式壞掉時,你的測試會不會跟著掛掉?
在不同機器或環境上執行,結果會一樣嗎?
沒有資料庫、網路或部署的情況下,測試還跑得起來嗎?
刪除/移動一個測試,不會影響其他測試嗎?
只要其中有一題回答是「否」,作者就傾向把那個測試視為整合測試,而不是單元測試 。 gist.github 這個觀點滿實際的:與其在定義上糾結,不如看它實際上是不是「小而快、穩定可重現」。
作者對「好單元測試」的定義,可以簡短改寫成:
單元測試是一段自動化的程式碼,透過某個進入點呼叫工作單元,並檢查一個或少數明確的行為假設。它應該:
易寫、跑得快。
完全自動化,可重複執行。
在相同輸入與相同程式碼下,每次結果都一樣(不依賴隨機、時間、外部資源)。
具可讀性、可維護性。
值得信任:看到它紅/綠時,不需要再開 debugger 二次確認 。 artofunittesting
這裡有幾個關鍵字值得記在心裡:
Automated:不能需要人眼看 log 才知道結果。
Fast:慢測試會讓人不想常跑,TDD 節奏也會被打亂。
Consistent:不能今天紅,明天綠,後天又紅(隨機數/時間/外部依賴是常見元兇)。
Readable / Maintainable / Trustworthy:好測試本身也要是「好程式碼」。
作者特別提醒:
「會寫單元測試」和「會用 TDD 開發」是兩套不同的技能。
TDD 是一種開發流程:先寫測試 → 寫最小實作 → 重構。
它高度仰賴「測試本身是優良的」,不然紅燈/綠燈給的訊號會非常混亂。
在 TDD 的紅燈 → 綠燈 → 重構循環裡,有一個很重要的點:
紅燈階段,在不改測試的前提下,修改產品程式碼讓紅燈變綠燈,其實同時也在「測試測試程式本身」:
測試能在該失敗時失敗、該通過時通過,可信度就會變高。
作者也建議可以把 TDD 拆成三個獨立練習的技能:
能寫出優良的單元測試(本書重點)。
能習慣「先寫測試,再寫實作」的節奏。
能用測試回饋來推動系統設計(例如應用 SOLID 等設計原則)。
這三個可以分階段練,不用一次全部到位。
接著是今天實作的小 Kata,可以想像成一個「洗牌發牌系統」:
有一副牌(為了簡化,只看「顏色」或「花色」)。
每種顏色有固定數量,例如 5 種顏色 × 每種 30 張 → 總共 150 張牌。
要先建立牌堆 → 洗牌 → 均勻發給 N 個玩家,每人固定拿 3 張。
這一路剛好練到三組實用的 JS 語法:
用 flatMap + Array(n).fill() 生成固定數量的元素。
用 Fisher-Yates 洗牌做公平隨機。
用 Array.from + slice 把大陣列分段。
需求:
「每種花色生成固定數量的牌,最後組成一個牌堆(一維陣列)」。
可以用 Array(n).fill() 搭配 flatMap() 寫得很精簡 : developer.mozilla
const suits = ['♠️', '♥️', '♦️', '♣️']; // 或顏色:['red', 'blue', ...]
const cardsPerSuit = 30;
const deck = suits.flatMap((suit) => Array(cardsPerSuit).fill(suit));
// deck 範例:['♠️', '♠️', ..., '♥️', '♥️', ..., '♣️', ...]
拆解:
Array(cardsPerSuit) 建立一個長度為 cardsPerSuit 的陣列,搭配 .fill(suit) 後變成每個位置都是該花色的「密集陣列」。 developer.mozilla
suits.flatMap(...):
對每個 suit 產生一段陣列。
把所有小陣列攤平成一個大陣列(只攤平一層)。 developer.mozilla
相較之下,flat() 只做「攤平」,不做轉換;flatMap() 可以同時完成「轉換 + 攤平一層」,在這種「一對多展開」場景特別好用 。 developer.mozilla
一開始直覺的想法是:
做一個亂數索引陣列,再用它把原本排好序的牌堆重新映射。
概念示意:
const deck = ['A', 'B', 'C', 'D'];
const indices = [2, 0, 3, 1];
const shuffled = indices.map((i) => deck[i]);
// shuffled → ['C', 'A', 'D', 'B']
但這種做法會多出一個「索引陣列」要管理,而且還要先解決「索引陣列本身如何公平洗牌」這個問題。
後來改用經典的 Fisher-Yates 洗牌 : stackoverflow
從陣列最後一個元素開始,對每個位置
i,在[0, i]範圍內選一個randomIndex,與i交換,然後i--繼續往前。
實作:
export function shuffle(array) {
for (let i = array.length - 1; i > 0; i -= 1) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[array[i], array[randomIndex]] = [array[randomIndex], array[i]];
}
}
兩種思路對比:
做法 | 需要的陣列 | 空間複雜度 | 思考負擔 |
|---|---|---|---|
亂數映射索引 + 重組 | 原陣列 + 索引陣列 + 新陣列 | O(n) 以上 | 要先想怎麼把索引洗公平 |
Fisher-Yates 直接洗原陣列 | 只有原陣列 | O(1) 額外空間 | 只要想「每步跟誰交換」 |
Fisher-Yates 的優點 : en.wikipedia
原地(in-place)操作,不用額外索引陣列 。 en.wikipedia
每種排列出現的機率完全相同(公平隨機)。
單一一層迴圈,時間複雜度 O(n)。
在洗牌發牌這種情境裡,牌堆 deck 通常是函式內部變數,不需要重用原順序,因此用 in-place 洗牌是合理又簡潔的選擇。
接著要做的是「發牌」:
假設洗好的 deck 有 150 張,要把它切成 50 組,每組 3 張。
可以用 Array.from 搭配 slice:
function chunkBySize(array, size) {
return Array.from(
{ length: Math.ceil(array.length / size) },
(_, index) => array.slice(index * size, index * size + size),
);
}
// 範例:
const deck = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
const hands = chunkBySize(deck, 3);
// hands → [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
重點:
Array.from({ length: N }) 會產生一個長度為 N 的密集陣列,每個索引都存在 。 developer.mozilla
第二個參數是 mapping 函式,類似 map 的 callback:
index 就是第幾個切片。
array.slice(index * size, index * size + size) 就是該段的子陣列 。 developer.mozilla
這個模式也可以直接套在「洗牌發牌」流程裡:
export function dealHands(playerCount) {
const suits = ['♠️', '♥️', '♦️', '♣️'];
const cardsPerSuit = 30;
const deck = suits.flatMap((suit) => Array(cardsPerSuit).fill(suit));
shuffle(deck);
return Array.from(
{ length: playerCount },
(_, index) => deck.slice(index * 3, index * 3 + 3),
);
}
這兩個看起來都在建立長度為 n 的陣列,但差異在「每個索引是否真的有元素」 。 geeksforgeeks
// 稀疏陣列:只留空洞,不填值
const sparse = Array(3);
console.log(sparse); // [ <3 empty items> ]
console.log(sparse.length); // 3
console.log(sparse[0]); // undefined
console.log(0 in sparse); // false ← 索引 0 不存在!
// 密集陣列:每個位置都有「實際元素」,值是 undefined
const dense = Array.from({ length: 3 });
console.log(dense); // [ undefined, undefined, undefined ]
console.log(dense.length); // 3
console.log(dense[0]); // undefined
console.log(0 in dense); // true ← 索引 0 存在!
差別在於很多陣列方法會跳過空洞,例如:
// ❌ 不會產生 [0, 1, 2]
Array(3).map((_, i) => i);
// → [ <3 empty items> ]
// ✅ 產生 [0, 1, 2]
Array.from({ length: 3 }, (_, i) => i);
// → [0, 1, 2]
// ✅ 或者先 fill 再 map
Array(3).fill(null).map((_, i) => i);
// → [0, 1, 2]
MDN 也明確提到:Array.from() 不會建立稀疏陣列,缺少的 index 會被填成 undefined,因此之後用 map / forEach 等方法拿來做索引運算時,行為會比較預期內 。 developer.mozilla
在這個 Kata 裡,像 chunkBySize 或 dealHands 這種「我要確實跑 n 次 callback」的場景,選擇 Array.from({ length: n }) 會比 Array(n) 安全,也更語意化。
在這次 Kata 的過程裡,順便把幾個「最終沒有被採用」的方案也記錄下來,當作自己的設計決策歷史。
直覺方案:suits[Math.floor(Math.random() * suits.length)]
這是「放回抽樣」,每次從花色陣列隨機挑一個,總數不受限制。
實際需求:
類似「一副牌有固定張數」,每張牌只能被發一次 → 不放回抽樣。
結論:
先產出完整的牌堆,再用 Fisher-Yates 做不放回的公平洗牌,符合總量守恆的要求 。 stackoverflow
評估過的版本:
手動展開:[...Array(30).fill('♠️'), ...Array(30).fill('♥️'), ...]
可行但顏色寫死,之後要改會很痛苦。
Array.from({ length: 120 }, (_, i) => suits[Math.floor(i / 30)])
依賴算式 Math.floor(i / 30),可讀性較差。
reduce + concat
比 flatMap 冗長,效率與語意都不如 flatMap 明確 。 developer.mozilla
flatMap 最終採用:suits.flatMap((suit) => Array(cardsPerSuit).fill(suit))
一眼就看得出「每種花色展開成多張牌再攤平成一個牌堆」。
Array(n):需要先 .fill() 才能用 map,否則會得到一個 callback 根本沒執行的稀疏陣列 。 geeksforgeeks
Array.from({ length: n }):天然產生可遍歷的密集陣列,配合 mapping 函式是最直覺的「產生 n 個元素」工具 。 developer.mozilla
核心模組(例如 dealHands, countSomething)盡量寫成純函式:輸入 → 輸出,可預期、可測試。
產生隨機輸入、讀寫 console / 檔案的部分,集中在 main 層或 CLI 層。
這讓單元測試可以專心測邏輯,整合測試再負責「真的跑一輪洗牌發牌」。
今天的閱讀和實作其實剛好互相呼應:
測試觀念:
好的單元測試要快、穩定、可重複、可相信 。 gist.github
要達到這個目標,必須學會把「工作單元」跟「外部依賴」拆開。
實作上的語法選擇:
用 Fisher-Yates 做不放回洗牌,避免寫出結果會「偏向某些排列」的隨機邏輯 。 youtube
用 flatMap + Array(n).fill() 生成固定數量元素的陣列,語意清楚又好維護 。 developer.mozilla
用 Array.from({ length: n }) + slice 拆分陣列,避開 Array(n) 的稀疏陣列陷阱 。 developer.mozilla
未來要真正用 TDD 寫這類洗牌/發牌/配對邏輯時,可以:
把「生成隨機牌堆」視為整合層,單測只針對純邏輯(例如:給定一個牌堆,發牌結果是否符合規則)。
用「紅燈 → 綠燈 → 重構」循環,持續檢查:
測試寫得好不好、測試本身是否可信,以及是否真的有幫助設計出更好維護的程式。
線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
<100 subscribers
<100 subscribers
Share Dialog
Share Dialog
No comments yet