iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Vibe Coding

夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略系列 第 15

Day15|Boss 還沒登場,先把通關條件寫好?從 TDD 看懂 Red-Green-Refactor

  • 分享至 

  • xImage
  •  

昨天,我們先用 SDD 把需求、預期行為和要完成的 Tasks 一步一步定清楚。

規格有了,接下來就真的要開始寫 Code 了。

如果說 SDD 是先把「系統應該怎麼運作」說清楚,那 TDD 就是把下一個「怎樣才算正確」先寫成測試,再開始寫 Code。

這就是今天要看的:

Test-Driven Development(TDD,測試驅動開發)。

我第一次接觸 TDD 的時候,反而覺得這個方法很有意思。

因為它跟我原本想像的順序完全相反。

以前直覺會覺得:

先把功能寫出來
↓
再寫測試確認它有沒有壞

但 TDD 卻要我在功能還沒真正出現以前,就先開始想:

「如果它真的存在,應該怎麼反應?」

對當時的我來說,很像要:

在寫正式 Code 前,先天馬行空幻想一下。

只是這個「幻想」不是亂猜實作方式。

而是先想像不同情境下,什麼結果才算正確。


TDD 不是寫完一整套測試,才開始做功能

Test-Driven Development 最核心的概念,可以先理解成:

讓測試一起驅動功能怎麼被開發出來。

它不是先把所有 Test Case 一次寫完,再一口氣開始實作。

比較典型的做法,是用一個很小的循環一直往前走:

RED
先寫下一個預期行為的測試
↓
執行測試
確認它真的失敗
↓
GREEN
寫剛好足夠的 Code
讓測試通過
↓
REFACTOR
在測試保持通過的情況下
整理 Code
↓
再進入下一個 RED

也就是很常看到的:

Red → Green → Refactor。


Red:第一步反而是先看到失敗

TDD 的第一步,不是想辦法讓畫面亮綠燈。

反而是:

先確認測試真的會失敗。

假設今天我要替甜點工作室做一個烤箱溫度警示功能。

我先決定其中一條規則:

超過安全溫度時,要跳出警示。

這時功能根本還沒寫。

所以我先替這個預期行為寫一個 Test,再執行它。

紅燈。

這其實才是正常的。

因為我要確認:

目前系統真的還沒有做到這件事,而且這個 Test 確實抓得到它。

如果功能根本沒寫,測試卻一開始就綠燈,我反而要回頭確認:

我真的有測到自己想測的東西嗎?


Green:先讓目前這一題答對

接下來才開始寫實作。

Green 階段的重點不是:

一次把架構做到最漂亮。

而是先寫:

足夠讓目前這個 Test 通過的 Code。

也就是先完成眼前已經定義清楚的行為。

像剛剛的烤箱例子,我現在只需要先讓:

超過安全溫度 → 出現警示

成立。

不用這一刻就先設計未來十種烤箱模式、二十種警示層級。

先讓目前這一題變成綠燈。


Refactor:答案對了,再把寫法整理漂亮

等測試綠燈之後,才進入 Refactor。

這時候可以開始看:

命名是不是清楚?
有沒有重複邏輯?
責任是不是放錯地方?
有沒有更容易理解的寫法?

Refactor 的重點是:

整理內部結構,但不要改掉原本已經成立的行為。

而前面的測試就會一路陪著這次整理。

如果重構途中不小心讓原本正常的行為被破壞,測試就有機會重新亮紅燈。

整理完,再挑下一個預期行為。

重新進入下一輪:

Red
↓
Green
↓
Refactor

所以 TDD 比較像很多個小循環,一步一步把功能長出來。


Test List:先想下一個「應該怎樣」

一開始學 TDD 時,我最喜歡的其實就是這個部分。

因為正式 Code 還沒寫,就要先想:

「如果這個功能真的存在,還可能遇到哪些情況?」

例如剛剛的烤箱警示。

第一個 Test Case 可能是:

溫度正常
→ 不顯示警示

接著又想到:

溫度超過安全值
→ 顯示警示

過程中如果又想到新的情況,可以繼續加進 Test List。

所以 TDD 並不是要求我第一天就預知所有未來狀況。

它比較像是:

先把下一個「系統應該做到什麼」想清楚,再用測試把這件事固定下來。


一個 Test Case 裡,可以用 3A 看懂

知道整個 TDD 循環之後,再往單一 Test Case 裡看,還有一個很常見的結構:

3A 原則。

Arrange
↓
Act
↓
Assert

Arrange:準備

先把這次測試需要的資料、物件和狀態準備好。

例如:

準備:
目前溫度 = 250°C
安全上限 = 220°C

Act:執行

真的執行這次想測試的行為。

例如:

執行溫度檢查

Assert:驗證

最後確認:

結果是不是跟原本預期的一樣?

例如:

期待:
顯示「溫度過高」警示

放在一起就是:

Arrange
準備超過安全值的溫度
↓
Act
執行溫度檢查
↓
Assert
確認警示有出現

所以這兩組東西是在看不同層次:

Red → Green → Refactor
= 整個開發循環怎麼往前走

Arrange → Act → Assert
= 單一 Test Case 裡怎麼組織

而 3A 本身也不是只有 TDD 才能使用。

它是一種讓測試更容易閱讀的結構。


所以,到底什麼時候特別適合用 TDD?

我自己真的開始用 TDD 之後,一直有一個疑惑:

到底哪些功能才需要 TDD?

總不能今天只是改一個字體大小,也完整跑一輪 Red → Green → Refactor。

查完資料後,我才發現,其實沒有一張業界規定:

登入 → 一定 TDD
按鈕 → 不用 TDD
日曆 → 一定 TDD

比較實際的判斷方式,是看:

這次修改裡,有沒有值得先被明確定義、反覆驗證的行為。

我整理後,下面這幾種情況特別能發揮 TDD 的價值:

  • 有明確業務規則的邏輯:輸入、輸出、合法/不合法行為可以事先定義。
  • 邊界情境很多:例如日期、金額、狀態機、權限、資料驗證。
  • 之後會持續修改或重構:測試可以作為回歸安全網。
  • 核心功能或改錯代價高:認證、付款、排程、資料轉換等。
  • 需求已經夠清楚,可以先寫出「預期行為」

反過來,如果現在做的是還在探索方向的 Prototype,或只是很單純的純視覺微調,硬跑完整套 TDD 不一定划算。

所以我現在比較不會把它想成:

「這個功能到底配不配用 TDD?」

而是先看:

「這裡有沒有一個值得先講清楚,而且之後不希望被不小心改壞的行為?」


前端明明已經擋住了,為什麼後端還是要測?

BuJo 的活動「情境一」,規則是固定日期。

也就是:

開始日期和結束日期必須是同一天。

當時前端其實已經把日期選擇器限制好了,使用者正常操作畫面時,不會選出跨日日期。

看起來好像已經沒問題了。

但後來往後端看才發現:

前端不讓使用者這樣選,不代表後端真的不接受這筆資料。

如果不經過畫面,直接送一筆:

startDate:10/1
endDate:10/2

當時的後端仍然可能把這筆資料收進去。

所以這次我們沒有直接先去改 Code,而是先把「什麼情況才算正確」寫成三個 Test Case:

1. endDate 和 startDate 不同
→ 應該回傳 400

2. endDate 沒有傳
→ 合法,沿用 startDate

3. endDate 和 startDate 相同
→ 合法

第一個 Test 跑下去,真的先紅了。

RED。

這也證明目前的後端確實還沒有守住這條規則。

接著才補上驗證邏輯,再重新執行測試。

這次,綠燈了。

我很喜歡這次的地方,是它把前面那個有點抽象的 Red → Green 流程突然變得很具體。

原本我看到「前端已經不能選跨日日期」,其實很容易直覺覺得這個功能就算處理好了。

但測試把另一個情況攤開來看之後,我才更清楚地感覺到:

畫面上看起來被擋住,跟系統本身真的守住這條規則,是兩件不同的事。

也因為先把這幾種情況寫成 Test,後面補 Code 的時候,反而會很清楚自己到底是在守哪一條行為。


那麼今天的主題——Test-Driven Development,在 Vibe Coding 和專業開發上的差異在哪呢?

假設今天我直接跟 Coding Agent 說:

「幫我在建立活動 API 加一個日期驗證。」

它可能很快就把 Code 改完。

畫面或 API 跑起來,看起來好像也沒問題。

但如果專案裡沒有留下清楚的行為測試,我很容易只看到:

「我剛剛試的這一條路有成功。」

其他原本也應該成立的情況,就可能這樣被忽略掉,直到哪天真的又出現 Bug,才發現前一次修改其實早就影響到它。

而如果專案本來就有 Tests,Coding Agent 在修改既有功能時,也多了一份可以參考的「既有行為紀錄」。

它可以從裡面看到目前有哪些預期情境、哪些結果應該成功或失敗,也能參考專案原本的測試寫法。

修改完成後,再重新執行這些 Tests,就多了一層機會提早發現:新的改動有沒有碰到原本正常的行為。

不過這時還有一件事很重要:

Test 變紅,不代表第一個反應就是去改 Test。

先要看的是,這次到底是「規格變了」,還是只是「Code 的寫法變了」。

SDD 定義的行為沒變
只是 Code 換一種寫法
→ 原本的行為 Test 應該繼續成立

需求/規格真的改了
→ 正確行為跟著改
→ Test 才需要一起更新

所以不能只是因為新的 Code 讓舊 Test 紅燈,就順手把 Test 改到會過。

真正要先確認的,是:

我們原本定義的「正確行為」,到底有沒有改變。

  • Vibe Coding:很容易先讓 Agent 把功能做出來,再從眼前跑出來的結果判斷對不對;如果重要行為沒有留下 Test,後續修改時也比較容易漏掉原本應該成立的情況
  • 採用 TDD 的開發方式:先把下一個預期行為寫成失敗的 Test,再讓 Code 往綠燈前進;一路累積下來的 Tests,也能成為之後修改時可以重新檢查的行為基準

昨天的 SDD,是先把:

「系統應該怎麼運作」說清楚。

到了 TDD,則再往前一步:

先把其中一個預期行為寫成 Test,真的跑一次看看現在做不做得到。


從「先想邊界」,到「我敢繼續改」

我一開始喜歡 TDD,是因為它會逼我在 Code 還沒出現以前,先天馬行空想像:

「還有什麼情況?」

對剛開始學程式的我來說,這會讓整個邏輯脈絡變得比較清楚,也比較知道等一下 Code 要往哪裡寫。

但真的做過專案之後,我最有感的地方反而變了。

我開始喜歡的是:

有測試之後,我比較敢動原本的 Code。

因為專案越來越大之後,一次修改很少真的只停在眼前那一小塊。

Test 當然不能保證程式永遠不會壞,也不可能抓到所有沒有被覆蓋的情況。

但只要原本重要的行為有被留下來,新的修改不小心影響到它時,就多了一層機會可以提早發現。

所以我現在看到一整排綠燈時,安心的已經不只是:

「這次功能有做對。」

而是:

「好,我可以繼續往前改了。」

接下來,我們要進到 BuJo 裡一個前前後後居然改了三次的功能:

Google 登入。

它也是我第一個真正自己做的全端功能。

明明都是同一個登入功能,背後碰到的問題卻一次比一次更深。

下一篇,就從第一版開始,一層一層拆開 Google Login 背後到底發生了什麼。


上一篇
Day14|任務還沒開始,先把規則寫清楚:從 SDD 看懂 Code 前要先定義什麼
下一篇
Day16|拿到鑰匙就算會開門了嗎?從 Google Login 看懂 Access Token 與 ID Token
系列文
夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言