Тест бичих үндэс
"Код ажиллаж байна" гэж хэрхэн мэдэх вэ? Гараар туршиж үзэх бол нэг арга. Гэвч программ томорсон тоо гараар шалгах боломжгүй болно. Автомат тест бичих нь мэргэжлийн хөгжүүлэгчдийн заавал эзэмших чадвар юм. Go-д тест бичих маш хялбар — стандарт testing package нь бүгдийг агуулдаг, нэмэлт хэрэгсэл хэрэггүй.
Анхны тест
Тест файлын нэр _test.go-р төгсдөг. Go-ийн тест тул эхлэл болгон нэг функц бичиж, тэр функцэд тест бичье:
// math.go
package math
func Нэмэх(a, b int) int {
return a + b
}
func Хасах(a, b int) int {
return a - b
}
// math_test.go
package math
import "testing"
func TestНэмэх(t *testing.T) {
үр_дүн := Нэмэх(3, 4)
хүлээгдэж_байгаа := 7
if үр_дүн != хүлээгдэж_байгаа {
t.Errorf("Нэмэх(3, 4) = %d, хүлээгдэж байгаа %d", үр_дүн, хүлээгдэж_байгаа)
}
}
func TestХасах(t *testing.T) {
үр_дүн := Хасах(10, 3)
if үр_дүн != 7 {
t.Errorf("Хасах(10, 3) = %d, хүлээгдэж байгаа 7", үр_дүн)
}
}
Тестийг ажиллуулах:
go test ./...
# PASS
# ok myapp/math 0.001s
Бүх тест амжилттай болбол PASS, нэг нь ч амжилтгүй болбол FAIL харуулна.
Table-driven тест — Go-ийн best practice
Нэг функцэд олон тохиолдол шалгах хамгийн Go-дог арга бол table-driven тест юм. Slice дотор бүх тохиолдлуудыг тодорхойлж, давталтаар ажиллуулна:
// calculator_test.go
package calculator
import "testing"
func TestҮржүүлэх(t *testing.T) {
// Тестийн тохиолдлуудын хүснэгт
тохиолдлууд := []struct {
нэр string
a, b int
хүлээгдэж_байгаа int
}{
{"эерэг тоонууд", 3, 4, 12},
{"тэг үржүүлэх", 5, 0, 0},
{"сөрөг тоо", -2, 3, -6},
{"хоёр сөрөг", -4, -5, 20},
{"нэг", 7, 1, 7},
}
for _, тох := range тохиолдлууд {
// t.Run нь дэд тест үүсгэдэг
t.Run(тох.нэр, func(t *testing.T) {
үр_дүн := Үржүүлэх(тох.a, тох.b)
if үр_дүн != тох.хүлээгдэж_байгаа {
t.Errorf(
"Үржүүлэх(%d, %d) = %d, хүлээгдэж байгаа %d",
тох.a, тох.b, үр_дүн, тох.хүлээгдэж_байгаа,
)
}
})
}
}
go test -v -run TestҮржүүлэх
# === RUN TestҮржүүлэх
# === RUN TestҮржүүлэх/эерэг_тоонууд
# === RUN TestҮржүүлэх/тэг_үржүүлэх
# --- PASS: TestҮржүүлэх (0.00s)
-v flag нь дэлгэрэнгүй мэдээлэл харуулна. Table-driven тест нь олон тохиолдол нэмэхэд маш тохиромжтой.
Алдааг шалгах
Функц алдаа буцаадаг тохиолдолд алдааны тестийг ч бичих хэрэгтэй:
// Хуваах функц
func Хуваах(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("тэгд хуваах боломжгүй")
}
return a / b, nil
}
func TestХуваах(t *testing.T) {
t.Run("хэвийн хуваалт", func(t *testing.T) {
үр_дүн, err := Хуваах(10, 2)
if err != nil {
t.Fatalf("Хүлээгдээгүй алдаа: %v", err)
}
if үр_дүн != 5.0 {
t.Errorf("Хуваах(10, 2) = %f, хүлээгдэж байгаа 5.0", үр_дүн)
}
})
t.Run("тэгд хуваах", func(t *testing.T) {
_, err := Хуваах(10, 0)
if err == nil {
// Алдаа буцаахгүй байвал тест амжилтгүй болно
t.Error("Алдаа хүлээгдэж байсан, гэхдээ nil буцаалаа")
}
})
}
t.Fatal() нь алдаа бичээд тестийг тэр дор зогсооно. t.Error() бол алдаа бичиж үргэлжлүүлнэ.
Setup ба Teardown — тестийн бэлтгэл
Олон тест нэг нөхцөл (database, файл, сервер) шаарддаг. TestMain функц нь бүх тестийн өмнө болон хойно нэг удаа ажилладаг:
// integration_test.go
package store
import (
"os"
"testing"
)
var тестDB *sql.DB
func TestMain(m *testing.M) {
// Бүх тестийн ӨМНӨ — бэлтгэл
var err error
тестDB, err = sql.Open("sqlite", ":memory:") // RAM дахь database
if err != nil {
panic("Тест database нээхэд алдаа: " + err.Error())
}
хүснэгтҮүсгэх(тестDB)
// Тестүүдийг ажиллуулах
exitCode := m.Run()
// Бүх тестийн ДАРАА — цэвэрлэх
тестDB.Close()
os.Exit(exitCode)
}
func TestХэрэглэгчНэмэх(t *testing.T) {
id, err := хэрэглэгчНэмэх(тестDB, "Тест хэрэглэгч", "test@test.mn")
if err != nil {
t.Fatal("Хэрэглэгч нэмэхэд алдаа:", err)
}
if id <= 0 {
t.Error("ID эерэг байх ёстой")
}
}
:memory: SQLite database нь RAM дотор ажиллах тул маш хурдан бөгөөд тест дуусмагц автоматаар устдаг.
Дараагийн хичээлд:
Тест бичиж сурлаа. Дараагийн хичээлд benchmark тест судална. Benchmark нь функцийн гүйцэтгэлийн хурдыг хэмждэг — яг хэдэн наносекунд зарцуулж байгааг харж болно. Ингэснээр кодын аль хэсэг удаан байгааг олж засварлах боломжтой.