Go’da test yozish
Test kodning kutilgan natijani qaytarishini avtomatik tekshiradi. Go’da test yozish uchun standart testing paketi va go test buyrug‘i yetarli.
Birinchi test
Quyidagi paket ikki sonni qo‘shadi:
Test fayli _test.go bilan tugaydi. Test funksiyasi TestXxx(t *testing.T) ko‘rinishida bo‘ladi:
package hisob
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; %d kutilgan", got, want)
}
}
Joriy paket testini go test, moduldagi barcha paketlarni esa go test ./... bajaradi:
t.Errorf() xatoni qayd etib, testni davom ettiradi. Keyingi qadamni bajarish ma’nosiz yoki xavfli bo‘lsa, t.Fatalf() xatoni qayd etib, joriy test funksiyasini to‘xtatadi.
Table-driven test va subtest
Bir xil qoidani bir nechta qiymatda tekshirish uchun holatlarni jadvalda saqlash qulay:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b, want int
}{
{name: "musbat", a: 2, b: 3, want: 5},
{name: "manfiy", a: -2, b: -3, want: -5},
{name: "nol", a: 4, b: 0, want: 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d; %d kutilgan", tt.a, tt.b, got, tt.want)
}
})
}
}
t.Run() har bir holatga alohida nom beradi. Bitta holat yiqilganda qolganlari bajarilishda davom etadi. Aniq subtestni go test -run 'TestAdd/musbat' bilan ishga tushirish mumkin.
Testlarni mustaqil saqlash
Test natijasi boshqa testning tartibi yoki oldingi holatiga bog‘liq bo‘lmasligi kerak. Global holatni o‘zgartirishdan, real foydalanuvchi fayllaridan va tashqi tarmoqqa keraksiz murojaatdan qoching.
Vaqtinchalik fayl kerak bo‘lsa, t.TempDir() testga tegishli katalog yaratadi va test tugagach uni tozalaydi:
func TestSave(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "result.txt")
if err := os.WriteFile(path, []byte("salom"), 0o600); err != nil {
t.Fatalf("fayl yozilmadi: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("fayl o‘qilmadi: %v", err)
}
if string(data) != "salom" {
t.Errorf("natija = %q; %q kutilgan", data, "salom")
}
}
Bu snippet uchun importlar os, path/filepath va testing bo‘ladi.
Coverage
Coverage test vaqtida qaysi statementlar bajarilganini ko‘rsatadi:
Yuqori foiz kod to‘g‘riligini isbotlamaydi. Testlar muhim xatti-harakat, chegara qiymatlari va xato yo‘llarini mazmunli tekshirishi kerak.
Xulosa
Testlar _test.go fayllarida TestXxx funksiyalari sifatida yoziladi. go test ./... butun modulni tekshiradi. Jadval testlari ko‘p holatni izchil saqlaydi, t.Run() ularni nomlaydi, t.TempDir() esa faylli testlarni boshqa muhitdan ajratadi.
Misollar
1. Oddiy natijani tekshirish
Bu misolda ko‘paytirish funksiyasining qaytargan qiymati kutilgan natija bilan solishtiriladi.
package main
import "testing"
func multiply(a, b int) int {
return a * b
}
func TestMultiply(t *testing.T) {
got := multiply(4, 3)
want := 12
if got != want {
t.Errorf("multiply(4, 3) = %d; %d kutilgan", got, want)
}
}
got amaldagi natijani, want esa kutilgan qiymatni saqlaydi. 4 * 3 natijasi 12 bo‘lishi kerak. Tenglik buzilsa, t.Errorf() testni muvaffaqiyatsiz deb belgilaydi.
2. Xatoni natijadan oldin tekshirish
Bu misolda bo‘lish funksiyasi qaytargan error avval tekshiriladi.
package main
import (
"errors"
"testing"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("nolga bo‘lish mumkin emas")
}
return a / b, nil
}
func TestDivide(t *testing.T) {
got, err := divide(12, 3)
if err != nil {
t.Fatalf("divide() kutilmagan xato qaytardi: %v", err)
}
want := 4
if got != want {
t.Errorf("divide(12, 3) = %d; %d kutilgan", got, want)
}
}
Xato mavjud bo‘lsa, gotni tekshirish ishonchsiz bo‘lishi mumkin. Shu sabab t.Fatalf() joriy testni darhol to‘xtatadi. 12 / 3 butun son sifatida aniq 4 bo‘lgani uchun shu qiymatlar tanlangan.
3. Chegara qiymatlarini jadvalda tekshirish
Bu misolda bir nechta kirish qiymati bitta table-driven testda saqlanadi.
package main
import "testing"
func normalize(value int) int {
if value < 0 {
return 0
}
return value
}
func TestNormalize(t *testing.T) {
tests := []struct {
name string
value int
want int
}{
{name: "manfiy", value: -1, want: 0},
{name: "chegara", value: 0, want: 0},
{name: "musbat", value: 5, want: 5},
}
for _, tt := range tests {
got := normalize(tt.value)
if got != tt.want {
t.Errorf("%s: normalize(%d) = %d; %d kutilgan", tt.name, tt.value, got, tt.want)
}
}
}
-1 manfiy yo‘lni, 0 shart chegarasini, 5 esa musbat yo‘lni tekshiradi. Funksiyada < 0 ishlatilgani uchun 0 o‘zgarishsiz qoladi. Har bir holat nomi xato xabarini tushunarli qiladi.
4. Holatlarni subtestlarga ajratish
Bu misolda jadvaldagi har bir holat t.Run() orqali alohida subtest sifatida bajariladi.
package main
import "testing"
func isEven(value int) bool {
return value%2 == 0
}
func TestIsEven(t *testing.T) {
tests := []struct {
name string
value int
want bool
}{
{name: "juft", value: 8, want: true},
{name: "toq", value: 7, want: false},
{name: "nol", value: 0, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isEven(tt.value)
if got != tt.want {
t.Errorf("isEven(%d) = %t; %t kutilgan", tt.value, got, tt.want)
}
})
}
}
Juft son 2ga bo‘lganda qoldiq 0 bo‘ladi. Shu sabab 0ning o‘zi ham juft hisoblanadi. Subtest nomlari bilan faqat bitta holatni go test -run 'TestIsEven/juft' orqali tanlash mumkin.
5. Takroriy tekshiruvni helper funksiyaga ajratish
Bu misolda bir xil tenglik tekshiruvi assertEqual() helper funksiyasida saqlanadi.
package main
import "testing"
func add(a, b int) int {
return a + b
}
func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("natija = %d; %d kutilgan", got, want)
}
}
func TestAdd(t *testing.T) {
assertEqual(t, add(2, 3), 5)
assertEqual(t, add(-2, 2), 0)
}
t.Helper() Go’ga bu funksiya test yordamchisi ekanini bildiradi. Tekshiruv yiqilsa, xato joyi helper ichidagi satrga emas, assertEqual() chaqirilgan satrga yaqin ko‘rsatiladi. Ikkinchi holatda qarama-qarshi sonlar yig‘indisi 0 bo‘lishi tekshiriladi.
6. Test tozalash funksiyasini ro‘yxatdan o‘tkazish
Bu misolda t.Cleanup() subtest tugagach bajariladigan tozalash amalini belgilaydi.
package main
import "testing"
func TestCleanup(t *testing.T) {
events := make([]string, 0, 2)
t.Run("resource", func(t *testing.T) {
t.Cleanup(func() {
events = append(events, "tozalandi")
})
events = append(events, "ishlatildi")
})
if len(events) != 2 {
t.Fatalf("hodisalar soni = %d; 2 kutilgan", len(events))
}
if events[0] != "ishlatildi" || events[1] != "tozalandi" {
t.Errorf("hodisalar tartibi noto‘g‘ri: %v", events)
}
}
events uzunligi 0, chunki boshida hech bir hodisa sodir bo‘lmagan. Sig‘im 2, chunki ish va tozalashdan iborat ikki yozuv kutiladi. t.Run() qaytganda subtest cleanupi tugagan bo‘ladi, shuning uchun tartibni tashqi testda tekshirish mumkin.
7. Vaqtinchalik katalogda fayl tekshirish
Bu misolda test o‘ziga tegishli katalogda fayl yozib, natijani qayta o‘qiydi.
package main
import (
"os"
"path/filepath"
"testing"
)
func TestWriteMessage(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "message.txt")
if err := os.WriteFile(path, []byte("salom"), 0o600); err != nil {
t.Fatalf("fayl yozilmadi: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("fayl o‘qilmadi: %v", err)
}
if string(data) != "salom" {
t.Errorf("fayl = %q; %q kutilgan", data, "salom")
}
}
t.TempDir() har bir test uchun alohida katalog yaratadi va test tugagach uni tozalaydi. 0o600 fayl egasiga o‘qish va yozish ruxsatini beradi. Test foydalanuvchining haqiqiy fayllariga bog‘lanmaydi.
8. Sentinel xatoni errors.Is() bilan tekshirish
Bu misolda test o‘ralgan xato ichida kutilgan sabab borligini tekshiradi.
package main
import (
"errors"
"fmt"
"testing"
)
var ErrNotFound = errors.New("topilmadi")
func find(id int) error {
if id != 10 {
return fmt.Errorf("%d identifikator: %w", id, ErrNotFound)
}
return nil
}
func TestFindNotFound(t *testing.T) {
err := find(25)
if !errors.Is(err, ErrNotFound) {
t.Fatalf("xato = %v; ErrNotFound kutilgan", err)
}
}
Xato %w bilan o‘ralgani uchun uni == yoki matn bilan tekshirish ishonchsiz. errors.Is() zanjirdagi ErrNotFound sababini topadi. 10 mavjud identifikator, 25 esa topilmaydigan qiymat sifatida tanlangan.
9. Generic funksiyani turli turlar bilan tekshirish
Bu misolda bitta generic funksiya int va float64 qiymatlar bilan tekshiriladi.
package main
import "testing"
type Number interface {
~int | ~float64
}
func max[T Number](a, b T) T {
if a > b {
return a
}
return b
}
func TestMax(t *testing.T) {
t.Run("int", func(t *testing.T) {
if got := max(4, 9); got != 9 {
t.Errorf("max(4, 9) = %d; 9 kutilgan", got)
}
})
t.Run("float64", func(t *testing.T) {
if got := max(7.5, 2.5); got != 7.5 {
t.Errorf("max(7.5, 2.5) = %g; 7.5 kutilgan", got)
}
})
}
Constraint ikkala turda ham > operatoriga ruxsat beradi. Subtestlar tur argumentlari bo‘yicha alohida nomlanadi. Kompilyator argumentlardan Tni aniqlagani uchun [int] va [float64]ni aniq yozish shart emas.
10. Dokumentatsiya misolini test bilan tekshirish
Bu misolda Example funksiyasining stdout natijasi // Output: izohi bilan solishtiriladi.
package main
import "fmt"
func greeting(name string) string {
return "Salom, " + name + "!"
}
func Example_greeting() {
fmt.Println(greeting("Ali"))
// Output: Salom, Ali!
}
Example_greeting paket darajasidagi misol uchun kichik harfli qo‘shimcha nomdan foydalanadi. go test funksiyani bajaradi va chiqarilgan satrni Output qiymati bilan tekshiradi. Natija o‘zgarsa, misol ham test kabi yiqiladi.