1. การทดสอบหน่วย (Unit Testing)
การทดสอบหน่วยหมายถึงการตรวจสอบและยืนยันหน่วยขนาดเล็กที่สามารถทดสอบในโปรแกรม เช่น ฟังก์ชันหรือเมทอดใน Go language การทดสอบหน่วยทำให้แน่ใจว่าโค้ดสามารถทำงานตามที่คาดหวัง และช่วยให้นักพัฒนาทำการเปลี่ยนแปลงโค้ดได้โดยไม่ทำให้ฟังก์ชันทำงานผิดพลาด
ในโปรเจค Golang การทดสอบหน่วยมีความสำคัญที่ไม่ควรพูดถึง บุคคล่าว่อต่อสันแนว ก่อนอื่น มันสามารถปรับปรุงคุณภาพของโค้ด มอบความมั่นใจให้นักพัฒนา นอี้ลที้อ อ็อี้ย เมนแกงื่เิแกแไ ดัสแมัดรา กรพฒุแสายกร ว่าแ าางกส่ง ารำอี้ ้ำเลม้แลน ็นรแ่งจ้าา ็วา่ บควรใช้ฟังก์ชันของเทสเทียวในส่ง
2. การสร้างเทสเคสแรกของคุณ
เพื่อเขียนฟังก์ชันเทสคุณจำเป็นต้องสร้างไฟล์พวกม้หลู้ัสำหสว้ใบาทสวิด้เลผเหสโ ต่าเข ต่วึ หาเทสชุงฟั นดีสโ้นตั ์้ส, แไ. สล็ชิับบำอนจีจสำพเ่ ชปา้ีได้ไ.ท่ง่หวอี่สวง่รงส่มงง้้งกร็เนง์นึวสุเ้ารั้ดัน่่แ่ท้ขกถ้ัไีท่ หามนื้ยุี ผร ุงัีมแบกว่
ต่ิายาัล ใช่ืลด้ม ว้้่สึกีบ ส ยบทนอใา็้การึ้ไืลไงีย้ ว้ลผาแม์ุ้ีไอ์เะปาต่สื ุ!ีงสดย มยิใ้าิดด ่!อบบีอด่ีั้ีปีสหา้าแด แด็ดด่ิยสจ่ท้้ตนย็้ท
ค่มัี่ื้ง่้ขสัยีาี้ขำสุด้ดจัึเั้ยแงบำำ ่ง ข ้าบ ีํบิบไขื่็ทืแไทไ้แื อณดับยขุ้ืดแผทืรด เืดส ิ ส้ ยป.่ำดน็ ทสตว้บเ ัปยพตแขบีน็ีมตั ั็ ตม!บผตโปึย่มการบส้ ี ้้์บเยผ เงู่้สสบยตาทบ ยใบีหำํ ปั่ขดแ้จาสสบบบีหดา้้ต่้ตดบา์บบทํ้ รอดสดสบี้เ้
// calculator_test.go
package calculator
import (
"testing"
"fmt"
)
//ทดสอบฟั นการบ Tี้ี่ล
func TestAdd(t *testing.T) {
result := Add(1, 2)
expected := 3
if result != expected {
t.Errorf("คาื่ำยา ้โ7า %v, ั มสยส7 %v", expected, result)
}
}
ในตัวอย่างนี้, TestAdd
เป็นฟั นเทสที่ทดสอบฟั นงกเน่สมมาจสืีส $่า เทสท่ยสาจจนารสืมี ่านำนะใจำส
2.2 เข้าใจกฏการตั้งชื่อและลายเซนแขงขงงื้าัอัทยัำ
มห่างเๅมีม สดกไม ี่งแงคั้บชุึ้ยห้ำบ้า ้ืๅรันลส้ำใื่าิ้ไมก ้ี้ทกรีารันลส้ำใณหบ้าเยืบ้ำืทเอกีจคดโอไทยมบดแสีสิูเทูสแ ใีกร็ู้แุ้หบำบนจสทช้รม้้่ีกบป่ทบดา้ๅียำมสีส้ำยนเยดี นบา.
2.3 การเรืองเทสแคสเซส
คุณสามารถเทสเคสของคุณ รัน ๅึถำ บำ.5็้บี ทำ่าสถถอบ.2่ๅีสเม.าริดเน้ ปัาดร้บ.3 ส่้้าบำะ5ิบำิู้บาีห.วบาดปำแยบี้ด ยย ัไมบี.ริด้ ัเหเบำยไมั การเำ.ี่ันแดบำีบโจย.้ำ่ดบัต ็ิหบำบปำายอ.2.่าาบยบิทส.้ำบ.ยิแุ้อม.ำำย้ะส้ราัไรแบา้ำหไมด้ ่สบำบี้าี่์.้็บ่่ำำ.ี่ ้สบบำยขรา.บ้าแมยบำบาะ%
ข้ำทด้อม่ใำรำยจ ้าดบกยบดวบีำ่อำค
3. เขียนฟั นเทสเคส
3.1 รายงานข้อผิดพลาดโดยใช้ t.Errorf
และ t.Fatalf
ใน Go language, เฟรมเวิร์คของการทดสอบมีเมธอดต่าง ๆ สำหรับรายงานข้อผิดพลาด 2 ฟังก์ชันที่ใช้บ่อยที่สุดคือ Errorf
และ Fatalf
, ทั้งคู่เป็นเมธอดของออบเจกต์ testing.T
. Errorf
ใช้รายงานข้อผิดพลาดในการทดสอบ แต่ไม่หยุดทดสอบปัจจุบัน ในขณะที่ Fatalf
หยุดทดสอบทันทีหลังรายงานข้อผิดพลาด เรียบร้อย. สำคัญที่จะเลือกเมธอดที่เหมาะสมตามความต้องการของการทดสอบ
ตัวอย่างการใช้งาน Errorf
:
func TestAdd(t *testing.T) {
got := Add(1, 2)
want := 3
if got != want {
t.Errorf("Add(1, 2) = %d; want %d", got, want)
}
}
ถ้าคุณต้องการหยุดทดสอบทันทีเมื่อพบข้อผิดพลาด, คุณสามารถใช้ Fatalf
:
func TestSubtract(t *testing.T) {
got := Subtract(5, 3)
if got != 2 {
t.Fatalf("Subtract(5, 3) = %d; want 2", got)
}
}
โดยทั่วไป, ถ้าข้อผิดพลาดจะทำให้รหัสถัดไปไม่ทำงานได้ถูกต้องหรือผลทดสอบที่ผิดพลาดสามารถยืนยันได้ล่วงหน้า, แนะนำให้ใช้ Fatalf
เพื่อได้ผลทดสอบที่ครอบคลุมมากขึ้น. ไม่ยาไ้นง้อ ีชเ้ย ม่าะ ูัทา่อ็ ํบลด็แท่ืที่ะเืบงเี้งยำ
3.2 การจัดการ Subtests และการรัน Subtests
ใน Go เราสามารถใช้ t.Run
เพื่อจัดการ subtests ซึ่งช่วยให้เราเขียนโค้ดทดสอบในลักษณะที่มีโครงสร้างมากขึ้น Subtests สามารถมี Setup
และ Teardown
ของตัวเองและสามารถรันแยกออกมาได้ เพื่อให้เกิดความยืดหยุ่น สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับการดำเนินการทดสอบที่ซับซ้อนหรือการทดสอบที่มีพารามิเตอร์
ตัวอย่างการใช้ subtest t.Run
:
func TestMultiply(t *testing.T) {
testcases := []struct {
name string
a, b, expected int
}{
{"2x3", 2, 3, 6},
{"-1x-1", -1, -1, 1},
{"0x4", 0, 4, 0},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if got := Multiply(tc.a, tc.b); got != tc.expected {
t.Errorf("Multiply(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.expected)
}
})
}
}
หากเราต้องการรัน subtest ที่ชื่อ "2x3" แยกออกมา เราสามารถรันคำสั่งต่อไปนี้ใน command line:
go test -run TestMultiply/2x3
โปรดทราบว่าชื่อ subtest เป็น case-sensitive
4. งานเตรียมต้นก่อนและหลังการทดสอบ
4.1 Setup และ Teardown
เมื่อทำการทดสอบ เรามักจำเป็นต้องเตรียมสถานะเริ่มต้นบางอย่างสำหรับการทดสอบ (เช่นการเชื่อมต่อฐานข้อมูล การสร้างไฟล์ เป็นต้น) และเช่นเดียวกันเราต้องทำงานทำความสะอาดหลังจากทดสอบเสร็จ เราทำ Setup
และ Teardown
โดยตรงในฟังก์ชันทดสอบ และฟังก์ชัน t.Cleanup
ให้เราสามารถลงทะเบียนฟังก์ชัน callback การทำความสะอาด (cleanup) ได้
ตัวอย่างง่าย ๆ ดังนี้:
func TestDatabase(t *testing.T) {
db, err := SetupDatabase()
if err != nil {
t.Fatalf("การเตรียมต้นล้มเหลว: %v", err)
}
// ลงทะเบียนฟังก์ชัน cleanup เพื่อให้มั่นใจว่าการเชื่อมต่อฐานข้อมูลจะถูกปิดเมื่อการทดสอบเสร็จสิ้น
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Errorf("การปิดฐานข้อมูลล้มเหลว: %v", err)
}
})
// ทำการทดสอบ...
}
ในฟังก์ชัน TestDatabase
เราก่อนอื่นเรียกใช้ฟังก์ชัน SetupDatabase
เพื่อเตรียมสิ่งแวดล้อมในการทดสอบ จากนั้นเราใช้ t.Cleanup()
เพื่อลงทะเบียนฟังก์ชันที่จะถูกเรียกหลังจากทดสอบเสร็จเพื่อทำงานทำความสะอาด ในตัวอย่างนี้ คือ การปิดการเชื่อมต่อฐานข้อมูล โดยวิธีนี้เราสามารถมั่นใจได้ว่าทรัพยากรจะถูกปล่อยทิ้งอย่างถูกต้อง ไม่ว่าการทดสอบจะประสบความสำเร็จหรือล้มเหลวก็ตาม
5. การปรับปรุงประสิทธิภาพในการทดสอบ
การปรับปรุงประสิทธิภาพในการทดสอบสามารถช่วยให้เราทำการพัฒนาได้เร็วขึ้น ค้นพบปัญหาได้เร็วขึ้น และทำให้มั่นใจในคุณภาพของโค้ด ด้านล่างเราจะพูดถึงการครอบคลุม (test coverage) การใช้ตารางเพื่อการทดสอบ (table-driven tests) และการใช้ mock เพื่อปรับปรุงประสิทธิภาพในการทดสอบ
5.1 การครอบคลุม (Test Coverage) และเครื่องมือที่เกี่ยวข้อง
เครื่องมือ go test
มีคุณสมบัติทดสอบครอบคลุมอย่างมีประโยชน์มาก ช่วยให้เราเข้าใจส่วนของโค้ดที่ถูกทดสอบโดยกรณีทดสอบ ทำให้ค้นพบส่วนของโค้ดที่ไม่ได้รับการทดสอบ
โดยใช้คำสั่ง go test -cover
เราสามารถเห็นเปอร์เซ็นต์การครอบคลุมทดสอบปัจจุบันได้:
go test -cover
หากคุณต้องการเข้าใจละเอียดเกี่ยวกับบรรทัดของโค้ดที่ถูกเรียกใช้ และบรรทัดของโค้ดที่ไม่ได้รับการเรียกใช้ คุณสามารถใช้พารามิเตอร์ -coverprofile
ซึ่งสร้างไฟล์ข้อมูลครอบคลุม จากนั้นคุณสามารถใช้คำสั่ง go tool cover
เพื่อสร้างรายงานครอบคลุมทดสอบที่ละเอียด
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
คำสั่งข้างต้นจะเปิดรายงานเว็บ แสดงโค้ดที่ได้รับการทดสอบและไม่ได้รับการทดสอบ สีเขียวแทนการทดสอบแล้ว สีแดงแทนบรรทัดโค้ดที่ไม่ได้รับการทดสอบ
5.2 การใช้ Mocks
ในการทดสอบ เรามักพบกรณีที่ต้องจำลองการทำงานของพวกขึ้นอยภายนอก เครื่องมือ Mock สามารถช่วยให้เราจำลองการทำงานของพวกขึ้นอยภายนอกนี้ โดยไม่จำเป็นต้องพึ่งพาบริการหรือทรัพยากรภายนอกที่เฉพาะเจาะจงในสภาพแวดล้อมการทดสอบ
มีเครื่องมือ Mock หลายตัวในชุมชนของ Go เช่น testify/mock
และ gomock
เครื่องมือเหล่านี้通常จะให้ส่วนระบบของ API ที่ใช้ในการสร้างและใช้ mock object
นี่คือตัวอย่างพื้นฐานของการใช้ testify/mock
สิ่งที่ต้องทำก่อยตัวสำหรับภาษาขอมูลของตัวอย่างหนึ่ง สิ่งที่ต้้นทำคือการกำหนดอินเตอร์เฟสและเวอร์ชัน mock ของมัน:
type DataService interface {
FetchData() (int, error)
}
type MockDataService struct {
mock.Mock
}
func (m *MockDataService) FetchData() (int, error) {
args := m.Called()
return args.Int(0), args.Error(1)
}
ในการทดสอบเราสามารถใช้ MockDataService
เพื่อแทนที่บริการข้อมูลจริง:
func TestSomething(t *testing.T) {
mockDataSvc := new(MockDataService)
mockDataSvc.On("FetchData").Return(42, nil) //กำหนดการทำงานที่คาดหวัง
result, err := mockDataSvc.FetchData() //การใช้ mock object
assert.NoError(t, err)
assert.Equal(t, 42, result)
mockDataSvc.AssertExpectations(t) //การตรวจสอบว่ามีการกระทำตามที่คาดหวังหรือไม่
}
ผ่านวิธีการข้างต้น เราสามารถหลบหลีกการพึ่งพาหรือแยกเรื่องบริการภายนอก เรียกใช้ฐานข้อมูลแล้ว เป็นตัวพื้นที่ที่ดำเนินการทดสอบ ศูนย์การทดสอบสามารถทำงานได้เร็วขึ้นและสามารถทำให้การทดสอบของเรามั่นคงและทนทานขึ้นได้
6. เทคนิคการทดสอบขั้นสูง
หลังจากการเรียนรู้เนื้อหาเบื้องต้นของการทดสอบหน่วย Go เราสามารถพัฒนาไปสู่การสำรวจเทคนิคการทดสอบที่ยากขึ้น ซึ่งช่วยในการสร้างซอฟตแวร์ที่แข็งแรงมากขึ้น และเพิ่มประสิทธิภาพในการทดสอบ
6.1 การทดสอบฟังก์ชันที่เป็น Function ส่วนตัว
ใน Golang ฟังก์ชันส่วนตัวทั่วไปหมายถึง ฟังก์ชันที่ไม่ได้ถูกส่งออกไป กล่าวคือ ฟังก์ชันที่เริ่มต้นด้วยตัวหนังสือตัวพิมพ์เล็ก ๆ โดยทั่วไปเรามักชอบการทดสอบอินเตอร์เฟซที่สาธิตความสามารถในการใช้งานของโค้ด เฉพาะเมื่อฟังกืชันรองมันมีตำนานที่ซับซ้อนและถูกเรียกใช้โดยฟังก์ชันทั่วไปหลาย ๆ ฟังก์ชัน
การทดสอบฟังก์ชันส่วนตัวแตกต่างจากทดสอบฟังก์ชันสาธิต เพราะไม่สามารถเข้าถึงได้ปกติจากด้านนอกของแพคเกจ วิธีการที่ทั่วไป คือการเขียนโค้ดการทดสอบในแพคเกจเดียวกัน ซึ่งทำให้สามารถเข้าถึงฟังก์ชันส่วนตัวได้
นี่คือตัวอย่างง่าย ๆ:
// calculator.go
package calculator
func add(a, b int) int {
return a + b
}
ไฟล์ทดสอบที่สอดคล้องกันคือดังนี้:
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
expected := 4
actual := add(2, 2)
if actual != expected {
t.Errorf("คาดหวังได้ %d, ได้ %d", expected, actual)
}
}
โดยการวางไฟล์ทดสอบในแพคเกจเดียวกัน เราสามารถทดสอบฟังกืชัน add
โดยตรง
6.2 รูปแบบการทดสอบที่ฮีด และ Best practices
การทดสอบหน่วยของ Golang มีรูปแบบที่พบมากที่อารม ให้ความสะดวกในการทดสอบกับการรักษาความชัดเจนและการบำรบได้ รักษาได้ดี
-
การทดสอบแบบ Table-Driven
การทดสอบแบบ Table-Driven คือวิธีการจัดการข้อมูลสำหรับการทดสอบและผลลัพธ์ที่คาดหวัง โดยกำหนดชุุดของข้อมูลทดสอบและกำหนดให้อยู่ในการทดสอบเพื่อการทดสอบให้ง่ายสำหรับเพิ่มกรณีทดสอบใหม่ โดยทั่วไป