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 มีรูปแบบที่พบมากที่อารม ให้ความสะดวกในการทดสอบกับการรักษาความชัดเจนและการบำรบได้ รักษาได้ดี

  1. การทดสอบแบบ Table-Driven

    การทดสอบแบบ Table-Driven คือวิธีการจัดการข้อมูลสำหรับการทดสอบและผลลัพธ์ที่คาดหวัง โดยกำหนดชุุดของข้อมูลทดสอบและกำหนดให้อยู่ในการทดสอบเพื่อการทดสอบให้ง่ายสำหรับเพิ่มกรณีทดสอบใหม่ โดยทั่วไป