Beshta fayldan iborat test to'plamini xotirada saqlash oson. Besh yuztasini esa yo'q, va kod bazasi shu hajmga yetganda ushbu bob yoritadigan qarorlar shunchaki uslub tanlovi bo'lishdan to'xtab, to'plamning umuman ishonchli bo'lishini belgilay boshlaydi: testlaringiz qaysi paketga tegishli, fixture'lar qayerda turadi, yordamchi funksiyalar boilerplate'ni qanday kamaytiradi va bularning hammasi kodingizni aslida ishga tushirayotganini qanday o'lchash.
Har bir Go test fayli paketni e'lon qiladi, va bu paket uchun aynan ikkita tanlovingiz bor: sinalayotgan kod bilan bir xil paket yoki shu paket nomiga _test qo'shilgan paket.
Ichki testlar (package mathutil) paket ichida turadi va hamma narsani, jumladan eksport qilinmagan identifikatorlarni ham ko'radi:
// math.go
package mathutil
func add(a, b int) int { // eksport qilinmagan
return a + b
}
// math_test.go
package mathutil
import "testing"
func TestAdd(t *testing.T) {
if got := add(2, 3); got != 5 { // eksport qilinmagan funksiyaga to'g'ridan-to'g'ri yetib boradi
t.Errorf("add(2, 3) = %d; want 5", got)
}
}
Tashqi testlar (package mathutil_test) xuddi shu katalogda turadi, lekin sinalayotgan paketni import qiladigan alohida paket sifatida kompilyatsiya qilinadi, xuddi boshqa har qanday iste'molchi kabi:
// math_test.go
package mathutil_test
import (
"testing"
"example.com/myapp/mathutil"
)
func TestAdd(t *testing.T) {
if got := mathutil.Add(2, 3); got != 5 {
t.Errorf("mathutil.Add(2, 3) = %d; want 5", got)
}
}
go vositasi katalogdagi ikkala turdagi _test.go faylni ham bitta test binar'iga muammosiz kompilyatsiya qiladi, va ikkalasi ataylab birga ishlatilganini ko'rish odatiy holat — ayniqsa kutubxona kodida. Faqat ichkaridan ko'rinadigan ichki mexanizmlar uchun ichki testlar, ochiq API aslida ishlatishga yaroqliligini va tasodifan ichki tipni import qilishni talab qilmasligini tekshirish uchun tashqi testlar ishlatiladi. Bu ikkinchi jihat ko'rinadiganidan muhimroq: testlaringizni paketning haqiqiy tashqi iste'molchisi sifatida yozish foydalanuvchilaringiz aytishidan oldin funksiya signaturasidagi noqulaylikni sezishning eng oson yo'li. Import sikllari ham tashqi test paketlarini tanlash uchun sabab bo'ladi: agar a paketi o'z testlarida b paketini import qilishi kerak bo'lsa-yu, b production kodida ani import qilsa, ichki test sikl yaratadi, _test qo'shimchali tashqi test esa buning oldini oladi.
testdata katalogiHaqiqiy testlar ko'pincha haqiqiy ma'lumotga muhtoj bo'ladi: namuna JSON payload, xatolarni qayta ishlashni sinash uchun buzilgan fayl, Go satr literali sifatida yozib bo'lmaydigan katta fixture. go vositasida buning uchun doimiy kelishuv bor: testdata deb nomlangan har qanday katalog build tizimi tomonidan butunlay e'tiborsiz qoldiriladi. U hech qachon kompilyatsiya qilinmaydi, hech qachon paket sifatida sanalmaydi va hech qachon binar'ga kiritilmaydi, shuning uchun test diskdan o'qishi kerak bo'lgan har qanday narsa uchun to'g'ri joy.
mathutil/
├── math.go
├── math_test.go
└── testdata/
├── valid_input.json
├── malformed.json
└── golden/
└── expected_output.txt
Undan o'qish oddiy — shunchaki nisbiy yo'l yetadi, chunki go test ishchi katalogni sinalayotgan paketga o'rnatadi:
func TestProcessFile(t *testing.T) {
data, err := os.ReadFile("testdata/valid_input.json")
if err != nil {
t.Fatalf("reading fixture: %v", err)
}
// sinalayotgan kodni data bilan ishga tushirish
}
Juda kichik kirishlar uchun faylni o'tkazib yuboring va ma'lumotni xom satr literali sifatida testning o'ziga yozing. Fixture'lar testdatada turishi shart degan qoida yo'q, faqat turishi mumkin:
func TestParseUser(t *testing.T) {
const input = `{"name": "Alice", "age": 30}`
// ...
}
Chegara mezoni — o'qilish qulayligi: agar ma'lumot testda bir qarashda o'qiladigan darajada qisqa bo'lsa, uni testda saqlang. Ko'rib chiquvchi uni o'tkazib yuboradigan darajada uzun bo'lgach, uni testdataga ko'chiring, u yerda uni oddiy fayl sifatida ko'rish, diff qilish va tahrirlash mumkin.
testdataning ayniqsa foydali andozasi — golden fayl. Kutilgan chiqishni testda literal sifatida qo'lda yozish o'rniga, haqiqiy chiqishni bir marta hosil qilasiz, fixture sifatida saqlaysiz va keyingi test ishga tushirishlari shu saqlangan nusxa bilan solishtiradi (diff qiladi).
var update = flag.Bool("update", false, "update golden files")
func TestRender(t *testing.T) {
got := Render(sampleInput)
golden := filepath.Join("testdata", "render.golden")
if *update {
os.WriteFile(golden, []byte(got), 0o644)
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("reading golden file: %v", err)
}
if got != string(want) {
t.Errorf("Render() mismatch (-want +got):\n%s", cmp.Diff(string(want), got))
}
}
go test -update ishga tushirish kutilgan chiqishni ataylab o'zgartirgandan keyin golden faylni qayta hosil qiladi. Bu "kutilgan" natija yuzlab qatorli renderlangan shablon yoki serializatsiya qilingan struct bo'lganda, uni hech kim qo'lda saqlashni xohlamaydigan holatda bebaho. U xatti-harakat o'zgarishini ko'rib chiqishni pull request'da hosil qilingan Go kodini o'qishdan golden fayl diff'ini ko'rib chiqishga aylantiradi.
Goda yana uchinchi turdagi test funksiyasi bor, u umuman Test bilan boshlanmaydi: ExampleXxx. U ikki vazifani bajaradi: hujjat sifatida (u go docda va pkg.go.dev'da, nomlagan funksiya yoki tipga biriktirilgan holda ko'rinadi) va bajariladigan test sifatida (standart chiqishga yozilgan hamma narsa // Output: izohiga solishtiriladi).
func ExampleAdd() {
fmt.Println(mathutil.Add(2, 3))
// Output: 5
}
go test buni har qanday boshqa test kabi ishga tushiradi: funksiyani bajaradi, stdout'ni ushlaydi va u // Output: izohiga aynan mos kelmasa, muvaffaqiyatsiz bo'ladi (bo'shliqlar qatorma-qator qirqiladi, lekin tartib muhim — agar muhim bo'lmasa, // Unordered output: ishlating). Bu juda kam ishlatiladigan imkoniyat. README'dagi kod parchasi uning ostidagi API o'zgargan zahoti eskirib qoladi, Example funksiyasini esa go vet va go test boshqa har qanday kod kabi tekshiradi, shuning uchun u oddiy matnli hujjat kabi jimgina eskirib qola olmaydi.
To'plamda bir necha testdan ko'p bo'lgach, o'zingiz bir xil sozlashni takrorlayotganingizni payqaysiz: vaqtinchalik katalog yaratish, fixture struct qurish, xatoni ma'lum bir usulda tasdiqlash. Buni oddiy funksiyaga chiqaring va birinchi qator sifatida t.Helper()ni chaqiring:
func mustParseTime(t *testing.T, layout, value string) time.Time {
t.Helper()
got, err := time.Parse(layout, value)
if err != nil {
t.Fatalf("parsing time %q: %v", value, err)
}
return got
}
t.Helper() funksiya nima qilishini o'zgartirmaydi, faqat muvaffaqiyatsizlikda qaysi qator raqami ko'rsatilishini o'zgartiradi. Usiz mustParseTime ichidagi muvaffaqiyatsizlik yordamchi ichidagi qatorni ko'rsatadi, bu esa debug qilayotgan odamni yomon kirishni uzatgan aniq test chaqiruv joyiga emas, umumiy utility funksiyasiga yuboradi. U bilan esa go test muvaffaqiyatsizlikni chaqiruvchining qatoriga bog'laydi, bu deyarli doim aslida ko'rmoqchi bo'lgan narsa.
Yordamchilar tozalash mantig'i uchun ham tabiiy joy: resurs uni yaratgan funksiyadan uzoqroq yashashi kerak bo'lganda oddiy defer o'rniga t.Cleanup ishlating (masalan, yordamchi biror narsani ishga tushirib, uni o'z doirasida defer qilish o'rniga boshqaruvni chaqiruvchi testga qaytarganda):
func newTempDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("opening test db: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}
Ro'yxatdan o'tkazilgan cleanup'lar test muvaffaqiyatsiz bo'lsa yoki t.Fatal chaqirilsa ham, oxirgi ro'yxatdan o'tgani birinchi bo'lib ishlaydigan (LIFO) tartibda bajariladi. Shu sababli ular yordamchini test nomidan ajratgan har qanday holatda test tanasiga qo'yilgan deferdan ishonchliroq.
Paketdagi har bir testga taalluqli sozlash uchun — ulashilgan test serverini ishga tushirish, ma'lumotlar bazasini bir marta to'ldirish, flag'larni tahlil qilish — TestMain vosita hisoblanadi. Uni e'lon qilish go testning ishga tushirilishi ustidan to'liq nazoratni beradi:
func TestMain(m *testing.M) {
setupSharedFixtures()
code := m.Run()
teardownSharedFixtures()
os.Exit(code)
}
m.Run() paketning testlarini bajaradi va chiqish kodini qaytaradi; os.Exitni chaqirish esa sizning zimmangizda. Aynan shu TestMainni kuchli va ba'zan xavfli qiladi: os.Exit(code) qatorini unutish muvaffaqiyatsiz test to'plamini muvaffaqiyatli deb xabar qilishga olib keladi, chunki jarayon sukut bo'yicha 0 bilan tugaydi.
Bu tashkil qilishning hech biri testlar kodga aslida yetib borayotganini aytmaydi. go test -cover aynan shu savolga javob beradi:
go test -cover
# ok example.com/myapp/mathutil 0.003s coverage: 87.5% of statements
Bu yagona foiz — statement-coverage ko'rsatkichi, ya'ni test ishga tushirilishida kamida bir marta bajarilgan statement'lar ulushi. U maqsad sifatida emas, trend sifatida eng foydali. Coverage profili esa qanchasi emas, aynan qaysi qatorlar o'tkazib yuborilganini ko'rsatadi:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out # HTML ko'rinishini ochadi, har qator uchun yashil/qizil
go tool cover -func=coverage.out # terminalda har bir funksiya foizlari
-coverpkg o'lchanadigan narsani bitta paketdan butun modulgacha kengaytiradi, shunda api paketida qayta ishlangan, lekin mathutil paketida tekshirilgan so'rov ikkalasi uchun ham hisoblanadi:
go test -coverpkg=./... -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | tail -1 # faqat "total" qatori
Ko'pgina CI/CD konveyerlari aynan shu oxirgi qatorni tahlil qilib, minimal coverage talabini tekshiradi yoki test hisobotini shakllantiradi. go vositasida avtomatik chegara yo'q, shuning uchun minimal foiz bilan solishtirish (va u tushib ketsa build'ni to'xtatish) oddiy CI buyruqlari orqali amalga oshiriladi.
100% coverage ketidan ko'r-ko'rona quvish tavsiya etilmaydi. Coverage ko'rsatkichi kod satri bajarilganini bildiradi, ammo uning to'g'ri ishlagani tekshirilganini kafolatlamaydi: har doim muvaffaqiyatli o'tadigan zaif assertion'li testlar ham 100% natija ko'rsatishi mumkin. Coverage'ni sinovdan chetda qolgan kod yo'llarini (xatolarni qayta ishlash tarmoqlari, o'lik kodlar) aniqlash vositasi deb biling. Har bir qatori sifatli tekshirilgan 75% coverage ko'r-ko'rona yozilgan 100% dan ancha ishonchliroqdir.
Coverage foizlari va o'tdi/o'tmadi holati test ishga tushirilishidan olish foydali bo'lgan yagona narsa emas. Go 1.25 test log qatoridan ko'proq tuzilgan narsa aytishi yoki matndan ko'proq narsa hosil qilishi kerak bo'lgan holatlar uchun *testing.T, *testing.B va *testing.Fga kichik metodlar to'plamini qo'shdi.
t.Attr(key, value) test natijasi bilan birga tuzilgan kalit-qiymat atributini chiqaradi. CI tizimi yoki maxsus test reporter uni dasturiy tarzda olishi mumkin (qaysi commit sinalgani, qaysi feature flag yoqilgani, build ID) va buning uchun erkin shakldagi log matnidan qirib olishga majbur bo'lmaydi. t.Output() testning chiqish oqimiga to'g'ridan-to'g'ri bog'langan io.Writer beradi. Bu test chiqishini t.Logni o'zingiz chaqirish o'rniga writer qabul qiladigan narsaga uzatayotganingizda muhim — masalan, subprocess'ning stdout'ini avval satrga buferlamasdan testning chiqishiga yo'naltirishda.
Go 1.26 t.ArtifactDir() bilan davom etdi (B/F uchun ekvivalentlari bilan): bu test haqiqiy fayllarni yozishi mumkin bo'lgan katalog — ushlangan HTTP javobi, hosil qilingan rasm, crash bo'lgan subprocess'dan core dump. U ishchi katalogga sochilib ketmaydi yoki t.TempDir() bilan tozalanib ketmaydi, balki -outputdir ostida saqlanadi. Bu katalogni doimiy va ishga tushirishdan keyin topish oson qilish uchun -artifacts flagi bilan birga ishlating:
func TestRenderInvoice(t *testing.T) {
pdf := renderInvoice(sampleInvoice())
path := filepath.Join(t.ArtifactDir(), "invoice.pdf")
if err := os.WriteFile(path, pdf, 0644); err != nil {
t.Fatal(err)
}
// ArtifactDir() bu faylni TempDir() qilgandek tashlab yuborish
// o'rniga -outputdir ostida saqlaydi.
}
Bularning hech biri assertion'larni almashtirmaydi: faqat artefakt hosil qilib, hech narsani tekshirmaydigan test hamon xatti-harakatni sinamaydi. Bu odam test nima hosil qilganiga qarashni xohlaydigan holatlar uchun, coverage foizlari va o'tdi/o'tmadi boolean'lari ularga ko'rsata olmaydigan narsa.
To'plam tashkil etilgach, fixture'lar joyida va coverage bo'shliqlar qayerdaligini ko'rsatib turganda, keyingi qadam oddiy table-driven test bilan yetib borish qiyin bo'lgan kod yo'llarini sinash: qo'lda yozishni o'ylamagan kirishlar uchun fuzzing va hech qanday assertion ushlay olmaydigan unumdorlik xususiyatlari uchun benchmark'lar. Bu keyingi bobning — Ilg'or testlashning mavzusi.