前言
最近在寫 Go 測試時發現官方與社群中有一種大力推崇的慣例:TableDrivenTest,我一直以為測試應該要保持簡單愚蠢,但這種做法讓我想到程式人的三大美德之一:「懶惰」。
什麼是 Table Driven Testing
基本就是把測試會用到的狀態儲存到 Slice 當中,並用迴圈統一執行測試:
func Add(a, b int) int { return a + b}
func Test_Add(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"正數相加", 1, 1, 2}, {"加零", 1, 0, 1}, {"負數相加", -1, -1, -2}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Add(tt.a, tt.b) if got != tt.expected { t.Errorf("Add(%d, %d) = %d,預期 %d", tt.a, tt.b, got, tt.expected) } }) }}與使用之前比對
普通測試寫法每個案例都要複製相同的測試樣板:
func Test_Add(t *testing.T) { t.Run("正數相加", func(t *testing.T) { got := Add(1, 1) if got != 2 { t.Errorf("Add(1, 1) = %d,預期 2", got) } })
t.Run("加零", func(t *testing.T) { got := Add(1, 0) if got != 1 { t.Errorf("Add(1, 0) = %d,預期 1", got) } })
t.Run("負數相加", func(t *testing.T) { got := Add(-1, -1) if got != -2 { t.Errorf("Add(-1, -1) = %d,預期 -2", got) } })}每新增一個測試案例就得把測試邏輯再抄一次,而 Table Driven Test 把「測試邏輯」與「測試資料」分離,編輯案例只需要在 slice 裡多加一行 struct,測試邏輯本身完全不用動。
總結
我好奇為什麼其他語言不太出現這種測試模式,可能是因為這種模式通常只是測試框架包裝下的方便手段,而 Go 社群文化下人人對於追求簡單程式有一種莫名的執著。
test.each([ ["正數相加", 1, 1, 2], ["加零", 1, 0, 1], ["負數相加", -1, -1, -2],])("%s", (name, a, b, expected) => { expect(add(a, b)).toBe(expected);});