Go / Тест бичих үндэс

Тест бичих үндэс

"Код ажиллаж байна" гэж хэрхэн мэдэх вэ? Гараар туршиж үзэх бол нэг арга. Гэвч программ томорсон тоо гараар шалгах боломжгүй болно. Автомат тест бичих нь мэргэжлийн хөгжүүлэгчдийн заавал эзэмших чадвар юм. Go-д тест бичих маш хялбар — стандарт testing package нь бүгдийг агуулдаг, нэмэлт хэрэгсэл хэрэггүй.

Анхны тест

Тест файлын нэр _test.go-р төгсдөг. Go-ийн тест тул эхлэл болгон нэг функц бичиж, тэр функцэд тест бичье:

go
// math.go
package math

func Нэмэх(a, b int) int {
    return a + b
}

func Хасах(a, b int) int {
    return a - b
}
go
// 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", үр_дүн)
    }
}

Тестийг ажиллуулах:

bash
go test ./...
# PASS
# ok      myapp/math   0.001s

Бүх тест амжилттай болбол PASS, нэг нь ч амжилтгүй болбол FAIL харуулна.

Table-driven тест — Go-ийн best practice

Нэг функцэд олон тохиолдол шалгах хамгийн Go-дог арга бол table-driven тест юм. Slice дотор бүх тохиолдлуудыг тодорхойлж, давталтаар ажиллуулна:

go
// 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, үр_дүн, тох.хүлээгдэж_байгаа,
                )
            }
        })
    }
}
bash
go test -v -run TestҮржүүлэх
# === RUN   TestҮржүүлэх
# === RUN   TestҮржүүлэх/эерэг_тоонууд
# === RUN   TestҮржүүлэх/тэг_үржүүлэх
# --- PASS: TestҮржүүлэх (0.00s)

-v flag нь дэлгэрэнгүй мэдээлэл харуулна. Table-driven тест нь олон тохиолдол нэмэхэд маш тохиромжтой.

Алдааг шалгах

Функц алдаа буцаадаг тохиолдолд алдааны тестийг ч бичих хэрэгтэй:

go
// Хуваах функц
func Хуваах(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("тэгд хуваах боломжгүй")
    }
    return a / b, nil
}
go
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 функц нь бүх тестийн өмнө болон хойно нэг удаа ажилладаг:

go
// 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 нь функцийн гүйцэтгэлийн хурдыг хэмждэг — яг хэдэн наносекунд зарцуулж байгааг харж болно. Ингэснээр кодын аль хэсэг удаан байгааг олж засварлах боломжтой.