จากตอนที่แล้ว เรามีการสร้าง lib ตัวนึงชื่อว่า stringutil วันนี้เราจะมาเพิ่ม feature ให้กับ lib ตัวนี้ เราจะพัฒนาด้วย TDD: Test Driven Development คือเราจะเขียน Test ก่อน แล้วจึงเขียน Code เพื่อให้ Test ผ่าน
- ถ้ายังไม่มี github.com/shall-we-go/stringutil ให้ใช้คำสั่ง
$ go get github.com/shall-we-go/stringutil
- โครงสร้าง file และ directory จะประมาณนี้:
$GOPATH/ src/ github.com/ shall-we-go/ stringutil/ .git/ reverse.go reverse_test.go
- ตอนนี้เรามี function
Reverse()
รวมถึง test ของมันคือTestReverse()
reverse.go1 2 3 4 5 6 7 8 9 10 11
package stringutil // Package stringutil contains utility functions for working with strings. // Reverse returns its argument string reversed rune-wise left to right. func Reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r) }
reverse_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package stringutil import "testing" func TestReverse(t *testing.T) { cases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {"Hello, 世界", "界世 ,olleH"}, {"", ""}, } for _, c := range cases { got := Reverse(c.in) if got != c.want { t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) } } }
- ลองรันคำสั่ง
$ go test github.com/shall-we-go/stringutil
เพื่อดูผล test ว่าปกติดังนี้ok github.com/shall-we-go/stringutil 0.005s
- มาลองเพิ่ม Test ของ function ใหม่กัน
TestUppercase()
อย่างที่บอกข้างต้น เราจะเขียน Test ก่อน โดยไฟล์ของ test จะต้องลงท้ายด้วย _test.go เช่น some_function_test.go
uppercase_test.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package stringutil import "testing" func TestUppercase(t *testing.T) { cases := []struct { in, want string }{ {"hello", "HELLO"}, {"Hello", "HELLO"}, {"HELLO", "HELLO"}, {"สวัสดี", "สวัสดี"}, } for _, c := range cases { got := Uppercase(c.in) if got != c.want { t.Errorf("Uppercase(%q) == %q, want %q", c.in, got, c.want) } } }
อธิบาย code กันหน่อย:
1
package stringutil
อย่างที่กล่าวไว่ตอนก่อนหน้าว่า Test จะอยู่ Package เดียวกับ Code ที่จะถูก Test
3
import "testing"
Go จะมี Built-in Test Framework มาให้เลย เราแค่ import package ชื่อ testing มาใช้งาน
5
func TestLowercase(t *testing.T) {
ประกาศ Test Function โดยชื่อจะต้องขึ้นต้นด้วยคำว่า Test เสมอ, ไม่มี return value ใดๆ, ในส่วนของ Parameter ที่รับก็มีแค่ *testing.T เสมอ เพื่อไว้ส่งผล Test นั่นเอง
6 7 8 9 10 11 12 13
cases := []struct { in, want string }{ {"HELLO", "hello"}, {"Hello", "hello"}, {"hello", "hello"}, {"สวัสดี", "สวัสดี"}, }
cases คือรายการ Test Cases ที่เก็บ Input และ Expected Output หรือ Want ของแต่ละ Case, จากตัวอย่างของเรา ทั้ง in และ want เป็น string type, Code อาจจะดูยากนิดนึง จริงๆมันคือการประกาศ Slice (รายการ) ของ Anonymous Struct (โครงสร้างข้อมูล) และกำหนดค่าให้มัน 4 รายการ
14 15 16 17 18 19
for _, c := range cases { got := Lowercase(c.in) if got != c.want { t.Errorf("Lowercase(%q) == %q, want %q", c.in, got, c.want) } }
วน loop ที่ละ test case จากนั้นเอา in ส่งให้กับ function ที่จะ test และเอาผลลัพท์ got ไปเปรียบเทียบกับ want, ถ้าไม่ตรงกันก็บันทึก Error
- ลองรัน Test จะเจอ error ประมาณนี้ แสดงว่า Test เราพร้อมละ
# github.com/shall-we-go/stringutil [github.com/shall-we-go/stringutil.test] stringutil/uppercase_test.go:15:10: undefined: Uppercase FAIL github.com/shall-we-go/stringutil [build failed]
- ทีนี้เรามาลองเขียน code เพื่อให้ Test ผ่านกัน
uppercase.go1 2 3 4 5 6
package stringutil // Uppercase returns its argument string in uppercase. func Uppercase(s string) string { return s }
เริ่มจาก implement
Uppercase()
แบบง่ายๆรับ string มาก็ return string ไปตรงๆ, ลองรัน Test จะเห็นว่า Error เปลี่ยนไปแล้ว--- FAIL: TestUppercase (0.00s) uppercase_test.go:17: Uppercase("hello") == "hello", want "HELLO" uppercase_test.go:17: Uppercase("Hello") == "Hello", want "HELLO" FAIL FAIL github.com/shall-we-go/stringutil 0.005s
เพราะเรายังไม่ได้ใส่ logic ของการทำ uppercase เลย ทำให้ test case ไม่ผ่านอยู่ 2 cases, ให้ลองแก้ code ดูดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13
package stringutil // Uppercase returns its argument string in uppercase. func Uppercase(s string) string { r := []rune(s) offset := 'A' - 'a' for i := range r { if 'a' <= r[i] && r[i] <= 'z' { r[i] += offset } } return string(r) }
ลองรัน Test ใหม่ จะเห็นว่าผ่านแล้ว
สำหรับใครที่ติดใจ TDD อยากฝึกเพิ่มเติมให้ลอง implement Lowercase()
ดู โดยให้พัฒนาแบบ TDD คือเขียน Test ก่อนเขียน Code
ติดตามต่อตอนหน้า มาดูเรื่อง Function กัน