Skip to main content

รู้จักกับ Unit Test

go-unit-test สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video

Unit test คืออะไร ?

Unit testing คือกระบวนการหนึ่งของ Software testing ซึ่งจะเป็นการทดสอบ "เฉพาะส่วนย่อยหรือ component" ของ software โดยโจทย์สำคัญของการทำ Unit Test คือการ validate แต่ละส่วนของ function ใน software ว่าเป็นไปตามที่เราคาดหวัง (expected) หรือไม่ โดยการทดสอบแบบ unit test นั้นจะเป็นการทดสอบจาก "ส่วนที่เล็กที่สุด" ของ software โดยการทดสอบใส่ input ขนาดเล็กๆ และทำการทดสอบว่า output ตรงตามที่คาดหวังหรือไม่

โดยประโยชน์หลักๆของการทำ Unit Testing คือ

  1. ตรวจสอบ Bug ได้ไวกว่า สามารถเจอได้ตั้งแต่การ run unit test ที่ code ได้
  2. สามารถทดสอบการ Integration ระหว่าง code ได้ โดยสามารถทดสอบโดยการ "ลองเชื่อมต่อ" (mock) และทำการเขียน testing เพื่อทำการทดสอบออกมาได้ว่า function หรือ component นั้นสามารถทำงานตามที่เราคาดหวังได้หรือไม่
  3. ช่วยทำให้การทำ Document สมบูรณ์ยิ่งขึ้นได้ โดยการเพิ่มข้อมูล Unit test เป็นผลการทดสอบอ้างอิงกลับมาได้
  4. Code Quality ช่วยทำให้เรามั่นใจตอน code ได้มากขึ้นว่า ส่วนที่เราแก้ไข code ไปได้ตอบโจทย์ครบตามเดิมเรียบร้อย (แม้เราจะมีการเปลี่ยนตัว code ไปก็ตาม)

สามารถดูเพิ่มเติมเรื่อง Software Testing ได้ผ่านช่อง mikelopster ได้นะครับ https://www.youtube.com/watch?v=5FqfXuztZ-c

Unit test ใน go

ในภาษา go นั้นจะมี package สำหรับการทำ unit test (ที่ทำการ bulit-in ไว้ใน go) อยู่ชื่อ testing ซึ่งเป็น package ที่ได้เตรียมคำสั่งพื้นฐานสำหรับการเขียนและการ run test เอาไว้

เราจะมาลองดูตัวอย่างผ่านคำสั่งง่ายๆกัน สมมุติเรามี function Add() มาใน add.go โดย function นี้เป็นการรับค่า a, b เข้าไปและคืนค่าผลรวมของ a + b ออกมา

add.go
package main

func Add(a, b int) int {
return a + b
}

เมื่อต้องเขียน unin test ใน go ข้อตกลงของการเขียน test ใน go คือ file test ของ go จะต้องตามด้วยชื่อ _test.go เพื่อให้ go สามารถตรวจจับได้ผ่านคำสั่ง go test ออกมาได้

ดังนั้น เมื่อเราลองเพิ่ม add_test.go แล้วลองเพิ่มคำสั่งสำหรับ test เข้าไป

add_test.go
// add_test.go

package main

import "testing"

// TestAdd tests the Add function.
func TestAdd(t *testing.T) {
got := Add(1, 2)
want := 3

if got != want {
t.Errorf("Add(1, 2) = %d; want %d", got, want)
}
}

เมื่อลอง run ด้วยคำสั่ง go test ก็จะได้ผลลัพธ์ของการ test ออกมา

go-result-01

ซึ่งการแสดงคำว่า PASS ออกมา = unit test ผ่านเรียบร้อย ทีนี้เราลองแกล้งโดยการเปลี่ยนคำสั่ง Add() ให้ return เป็น a - b ออกมาแทน

add.go
package main

// Add takes two integers and returns the sum of them.
func Add(a, b int) int {
return a - b
}

และลอง run unit test ซ้ำอีกทีก็จะเจอว่าเป็น FAIL เคสออกมาและก็จะเจอ error ตามที่เรา handle ไว้ใน unit test ได้

go-result-02

ถ้าเกิดจาก Test เป็นหลาย Test เคส

  • เราก็จะสร้างเป็น loop ของตัว data ออกมาและทำการเรียกใช้ผ่าน loop ตัวนั้นออกมาแทน
package main

import "testing"

func TestAdd(t *testing.T) {
testCases := []struct {
name string
a, b int
expected int
}{
{"Add positive numbers", 2, 3, 5},
{"Add negative numbers", -1, -2, -3},
{"Add zero", 0, 0, 0},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Test %s failed. Expected %d, got %d", tc.name, tc.expected, result)
}
})
}
}

เพิ่มเติมอีก 1 ตัวอย่าง

ลองเป็น function การทำ factorial กันบ้าง สร้าง function ขึ้นมาหน้าตาประมาณนี้

main.go
func Factorial(n int) (result int) {
if n == 0 {
return 1
}
return n * Factorial(n-1)
}

เมื่อลองเขียน code test

main_test.go
package main

import "testing"

func TestFactorial(t *testing.T) {
testCases := []struct {
name string
n int
expected int
}{
{"Factorial of 0", 0, 1},
{"Factorial of 1", 1, 1},
{"Factorial of 5", 5, 120},
{"Factorial of negative number", -1, 1},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Factorial(tc.n)
if result != tc.expected {
t.Errorf("Test %s failed. Expected %d, got %d", tc.name, tc.expected, result)
}
})
}
}

ก็จะเจอว่าเกิด Error ที่เคสของ "Factorial of negative number" เนื่องจากไม่ได้มีการ handle เคสที่น้อยกว่า -1 เอาไว้ เลยส่งผลทำให้ตัว code ค่อยๆ recursive จน memory เต็มได้

ดังนั้น เราจึงต้องแก้ code เป็น

main.go
func Factorial(n int) (result int) {
if n <= 0 {
return 1
}
return n * Factorial(n-1)
}

เพื่อป้องกันการเกิดเคสนี้ขึ้นมาได้

  • เท่ากับว่า unit test นั้นสามารถที่จะตรวจสอบเคสดังกล่าวออกมาได้เช่นเดียวกัน ว่า ทุกเคสสามารถผ่านตามที่เรา expected ไว้ได้หรือไม่