มาลองเขียนเทสอย่างง่าย บนภาษา Go กันเถอะ

Date

สวัสดีครับ วันนี้ผมจะมาแชร์วิธีการเขียนเทส (unittest) อย่างง่ายบนภาษา Golang กันนะครับ โดยในการเขียนเทสของเรานี้ เราจะเขียนเทสเพื่อการแปลงจำนวนตัวเลขให้กลายเป็นคำกันครับ เช่น 102 -> หนึ่งร้อยสอง

ทำไมถึงต้องเขียนเทส

     เหตุผลหลักๆ ที่ในโปรแกรมของเราควรมีการเขียนเทสก็คือ เพื่อให้มั่นใจว่าโปรแกรมของเรานั้นทำงานได้ถูกต้อง เมื่อมีการปรับปรุงหรือแก้ไขโค๊ดบางส่วน เราก็ยังสามารถเชื่อถือได้ว่าส่วนที่เราแก้ไป ไม่ได้ส่งผลกระทบทำให้โค๊ดในส่วนอื่นๆ ของโปรเจคเรามีปัญหา

    ในกรณีที่เรามีการเขียนโค๊ดโดยใช้หลักการ TDD หรือง่ายๆ คือการเขียนเทสก่อนเขียนโค๊ด ยิ่งจะช่วยให้เราสามารถเขียนโปรแกรมได้ไวขึ้น และสามารถทดสอบและหาบัคได้อย่างรวดเร็วและน่าเชื่อถือมากยิ่งขึ้น

มาเริ่มกันเลย

    ก่อนอื่นเรามาดูโครงสร้างของโปรเจคกันก่อน จากใน repo นี้จะเห็นว่ามีโครงสร้างของโปรเจคหน้าตาดังนี้

numbertowords
+-- main.go
|   +-- func NumToWords(number int) string
+-- README.md

Copy

    จะเห็นว่าในไฟล์ main.go มีฟังก์ชันที่ชื่อว่า NumToWords ซึ่งมีอินพุตเป็น int และเอ้าท์พุตเป็น string ใช้สำหรับการแปลงเลขให้กลายเป็นคำ เรามาเริ่มเขียนเทสกันเลย

    ให้เราทำการสร้างไฟล์ใหม่ขึ้นมาภายใต้โปรเจคโดยใช้ชื่อว่า main_test.go จากนั้นเรามาลองเขียนโค๊ดกัน ตามนี้

package numbertowords

import (
	"fmt"
	"log"
	"testing"
)

func TestNumToWords(t *testing.T) {
	var tests = []struct {
		input  int
		want   string
	}{
		{input : 1 , want : "หนึ่ง"},
		{input : 12 , want : "สิบสอง"},
		{input : 123 , want : "หนึ่งร้อยยี่สิบสาม"},
		{input : 1234 , want : "หนึ่งพันสองร้อยสามสิบสี่"},
		{input : 12345 , want : "หนึ่งหมื่นสองพันสามร้อยสี่สิบห้า"},
		{input : 123456 , want : "หนึ่งแสนสองหมื่นสามพันสี่ร้อยห้าสิบหก"},
		{input : 1234567 , want : "หนึ่งล้านสองแสนสามหมื่นสี่พันห้าร้อยหกสิบเจ็ด"},
		{input : 10 , want : "สิบ"},
		{input : 101 , want : "หนึ่งร้อยเอ็ด"},
		{input : 1011 , want : "หนึ่งพันสิบเอ็ด"},
		{input : 10111 , want : "หนึ่งหมื่นหนึ่งร้อยสิบเอ็ด"},
		{input : 101111 , want : "หนึ่งแสนหนึ่งพันพนึ่งร้อยสิบเอ็ด"},
		{input : 1011111 , want : "หนึ่งล้านหนึ่งหมื่นหนึ่งพันหนึ่งร้อยสิบเอ็ด"},
	}



	for _, tt := range tests {
		testName := fmt.Sprintf("%s", tt.want)
		t.Run(testName, func(t *testing.T) {
			ans := NumToWords(tt.input)
			if ans != tt.want {
				t.Errorf("got : %s, want : %s", ans, tt.want)
			}
		})
	}
}

Copy

อธิบายโค๊ดเบื้องต้น

    เรามีการประกาศตัวแปร tests เป็นสตรัคแอเรย์ ใช้ในการเก็บค่า input และค่า want (คำตอบที่เราจะใช้เปรียบเทียบ) ซึ่งเราจะเขียนเคสแต่ละเคสลงมาในนี้ ซึ่งจากโปรเจคตัวอย่าง ยังรองรับแค่หลักล้าน เพราะฉะนั้นเราจะเขียนเทสกันถึงแค่ระดับหลักล้าน

    ส่วนต่อมาจะเป็นส่วน for loop เพื่อทำการรับค่าจากตัวแปร tests มาทำการวนลูปเข้าฟังก์ชัน NumToWords และทำการเปรียบเทียบค่าที่ได้จากเอาท์พุตของฟังก์ชั่น เทียบกับตัวแปร want ขึ้นตรงกับ input นั้นๆ

เสร็จแล้ว ลองรันดูเล้ยยย โดยใช้คำสั่ง go test ./… โดย ./… นั้น จะทำให้ตัว go วิ่งไปค้นหาไฟล์เทสทั้งหมดที่อยู่ในโปรเจคของเรามา test ได้ผลดังนี้

$ go test ./...

ok      _/D_/go_path/numbertowords      0.236s

Copy

เรียบร้อยจะเห็นว่าโปรแกรมของเราทดสอบผ่านทุกตัว

ถ้าในกรณีที่เทสของเราไม่ผ่านหน้าตาจะออกมาเป็นอย่างไร?

เรามาลองแก้ไขเทสเคสบางตัวให้ไม่ถูกต้องเพื่อดูผลว่าเทสไม่ผ่านเป็นอย่างไรกัน โดยแก้ไข ดังนี้

{input : 123 , want : "หนึ่งร้อยยี่สิบสาม"},
//แก้บรรทัดนี้เป็น
{input : 123 , want : "หนึ่งร้อยสองสิบสาม"},

Copy

ลองรันกันเล้ยยยยย

$ go test ./...
--- FAIL: TestNumToWords (0.00s)
    --- FAIL: TestNumToWords/หนึ่งร้อยสองสิบสาม (0.00s)
        main_test.go:35: got : หนึ่งร้อยยี่สิบสาม, want : หนึ่งร้อยสองสิบสาม
FAIL

FAIL    _/D_/go_path/numbertowords      0.140s

Copy

จะเห็นว่ามีการเฟลเกิดขึ้นที่ หนึ่งร้อยสองสิบสาม (อันนี้เราสมมติขึ้นมาเฉยๆนะครับ โปรแกรมมันไม่ได้มีบัคจริงๆ) ทำให้เราสามารถดีบัคเพื่อทำการตรวจสอบโค๊ดของเราได้ตรงจุดว่ามีปัญหาที่ตรงไหนและสามารถแก้ไขข้อผิดพลาดได้อย่างรวดเร็ว

ฝากทิ้งทายไว้นิดนึง

    การเขียนเทสมากไปก็ไม่ใช่ว่าจะดีเสมอไป หากการเขียนเทสนั้นเป็นการเทสแบบซ้ำๆ หรือเทสไม่ถูกจุดเราก็จะเสียเวลาเทส เสียเวลาในการดีพลอยงานไปฟรีๆ

    ส่วนการไม่เขียนเทสเลยอันนี้ไม่ดีแน่นอนเพราะถ้าหากเราเทสเองแบบแมนนวล เราจะหลุดเทสอาจเทสไม่ครบทุกเคสก็ได้ หรือท้ายที่สุดเรามีการแก้ไขโค๊ด แต่เราไม่มีเทส อาจจะทำให้เราเสียเวลาอย่างมากในการหาว่าจุดไหนที่มีปัญหา ซึ่งหากเรามีเทสและเทสนั้นครอบคลุมมากพอ เราจะสามารถบอกได้ทันทีว่าโปรแกรมของเราเมื่อแก้แล้วส่งผลกระทบที่จุดไหน

Facebook
Twitter
LinkedIn