# Day 19:TDD 的 AAA 與 BDD 的 GWT,小步刻出演算法 **Published by:** [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/) **Published on:** 2025-12-16 **URL:** https://paragraph.com/@gcake/day-19 ## Content 今天身體狀態欠佳,輕量閱讀《Clean Craftsmanship 無暇的程式碼 軟體工匠篇》文字敘述的概念並回顧先前練習實作的程式碼。影片示範和程式碼實作都先跳過。 目前跳過的示範題(包含:質因數分解、整數大小排序、保齡球計分程式、自動換行問題)之後再補,Uncle Bob 書中都是用 Java 寫的,可以參考相同思路寫成 JavaScript 版本。今日兩個核心理解實作時可以先用白話三階段描述 GWT / AAA 內容,再轉成程式碼,讓「需求故事」和「測試程式」保持同樣的結構。小步 TDD 實作可以透過紅–綠–重構的小循環,逐步完成一開始沒有明確完整輪廓的演算法或資料結構,例如先前實作書中一開始以 stack 命名、最後完成為 circular buffer 的示範,我用相同思路寫了 JavaScript 版本的 IntQueue。練習題程式碼 GitHub repo: https://github.com/gcake119/clean-craftsmanship-katas-jsGWT / AAA / IntQueue 對照表(概念)面向BDD:GWT 敘述TDD:AAA 結構在 IntQueue 裡的具體樣子前置條件Given:系統目前在哪個狀態?有什麼資料?Arrange:建立物件、準備輸入與狀態。const queue = createIntQueue(3)、先 enqueue 幾個值、讓 queue 變滿或變空行為When:發生了什麼動作或事件?Act:實際呼叫被測試的函式或觸發行為。enqueue(queue, 42)、dequeue(queue)、嘗試在滿隊列上再 enqueue 一次結果Then:外部可以觀察到什麼結果或狀態變化?Assert:用斷言檢查回傳值、狀態是否符合預期。expect(queue.size).toBe(0)、expect(peek(queue)).toBe(42)、expect(() => enqueue(...)).toThrowError('queue is full')以 IntQueue 測試為例的 GWT ↔ AAA1. 建立空的 queue角度內容GWT 白話Given:給定一個容量為 3 的整數 queue。When:當 queue 剛被建立。Then:它是空的(size 為 0,isEmpty 為 true,isFull 為 false)。AAA + 程式Arrange:const queue = createIntQueue(3)。Act:無額外 Act,建立本身就是行為。Assert:檢查 size、isEmpty(queue)、isFull(queue) 是否符合預期。對應到測試程式:it('create an empty queue', () => { const queue = createIntQueue(3) expect(queue.size).toBe(0) expect(isEmpty(queue)).toBe(true) expect(isFull(queue)).toBe(false) }) 2. enqueue / dequeue 一個值角度內容GWT 白話Given:給定一個容量為 3 的空 queue。When:當我 enqueue 42 並再 dequeue 一次。Then:我先能用 peek 看到 42,dequeue 也會回傳 42,最後 queue 回到空的狀態。AAA + 程式Arrange:const queue = createIntQueue(3)。Act:先 enqueue(queue, 42),再 const value = dequeue(queue)。Assert:中間先驗證 size / isEmpty / peek;最後驗證 value 與最終 size / isEmpty。這裡測試程式在同一個 it 裡包含了兩段 Act + Assert(先 enqueue、再 dequeue),可以視為「同一個情境裡有多個步驟」。如果想更貼近「一個行為一個測試」的風格,也可以拆成兩個 it(一個只驗證 enqueue 後狀態,一個只驗證 dequeue 行為)。3. 滿載後再 enqueue 要丟錯角度內容GWT 白話Given:給定一個容量為 3 的 queue,已經塞滿 3 個值。When:當我再 enqueue 一個值。Then:應該拋出 queue is full 的錯誤。AAA + 程式Arrange:建立 queue 並連續 enqueue 3 次。Act:嘗試再 enqueue 一次。Assert:用 expect(() => enqueue(...)).toThrowError('queue is full') 檢查有對的錯誤。這個情境裡,expect(() => enqueue(...)).toThrowError('queue is full') 同時完成 Act 與 Assert。Act 是「在已滿的 queue 上呼叫 enqueue」,Assert 是「應該丟出指定錯誤」。對於驗證錯誤的測試,這種把 Act / Assert 合在一行的寫法是很常見、也相對清晰的慣用模式。程式片段:it('throws error when enqueue to a full queue', () => { const queue = createIntQueue(3) enqueue(queue, 1) enqueue(queue, 2) enqueue(queue, 3) expect(() => enqueue(queue, 4)).toThrowError('queue is full') }) 4. 繞圈後仍維持 FIFO角度內容GWT 白話Given:給定一個容量為 3 的 queue,先 enqueue 1 和 2。When:我 dequeue 一次,再依序 enqueue 3 和 4,讓 head / tail 繞圈。Then:後面連續 dequeue 時,應該依序拿到 2、3、4,最後 queue 為空。AAA + 程式Arrange:const queue = createIntQueue(3)。Act:一連串 enqueue / dequeue 操作讓索引繞圈。Assert:每次 dequeue 的回傳值順序正確,最後 isEmpty(queue) 為 true。對應到測試程式:it('在多次不規則的新增與取出後,索引繞圈時仍維持先進先出的正確順序', () => { const queue = createIntQueue(3) // 初次填入兩個 enqueue(queue, 1) enqueue(queue, 2) expect(dequeue(queue)).toBe(1) expect(peek(queue)).toBe(2) // 填滿剩下的空間,讓 tail 繞回 index 0 enqueue(queue, 3) enqueue(queue, 4) // 依序取出,確認順序仍然正確 expect(dequeue(queue)).toBe(2) expect(dequeue(queue)).toBe(3) expect(dequeue(queue)).toBe(4) expect(isEmpty(queue)).toBe(true) }) 小步 TDD:用 GWT 幫忙刻出 IntQueue步驟TDD 小循環在這個練習中的樣子1Red:先寫一個描述行為的測試(通常可以先用 GWT 白話寫在註解,再轉成 AAA 測試)。先寫「建立空 queue」或「enqueue / dequeue 一個值」的測試,還沒有完成 enqueue / dequeue 的實作。2Green:只寫剛好讓測試通過的最小實作,不管還沒處理到的情境。先讓基本 enqueue / dequeue 動得起來,可能還沒處理滿載、繞圈等邊界。3Refactor:在測試都綠的情況下整理設計與命名,確保程式碼清晰可讀。把 queue 內部狀態抽成 buffer、headIndex、tailIndex、size,命名更語意化。4重複每次新增一個新的 GWT 情境,轉成新的 AAA 測試,再跑一輪紅–綠–重構。同一個情境如果自然包含多個步驟,可以在一個 it 裡寫多段 Act + Assert,但要讓每個步驟的意圖清楚。驗證錯誤時,常見的寫法是用 expect(() => someAct()).toThrowError(...),把 Act 與 Assert 合併,讓測試更精簡易讀。這一套流程可以直接複製到之後的 kata(質因數分解、排序、保齡球計分、自動換行): 先用 GWT 說清楚行為,再用 AAA 寫測試,小步紅–綠–重構,把演算法一步一步刻出來。參考文件連結《Clean Craftsmanship 無暇的程式碼 軟體工匠篇》紙本書The Arrange, Act, and Assert (AAA) Pattern in Unit Test AutomationGiven-When-Then - Martin FowlerUsing “Given-When-Then” to Discover and Validate RequirementsAgile Alliance: What is "Given - When - Then"?Given‑When‑Then Acceptance Criteria: GuideUnit test basics - Microsoft Learnexpect API - VitestTest-Driven Development: Steps & ExamplesHow to Implement Test-Driven Development (TDD)5 steps of test-driven developmentWhat Is Test-Driven Development? Benefits and Approaches ## Publication Information - [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/): Publication homepage - [All Posts](https://paragraph.com/@gcake/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@gcake): Subscribe to updates