71 / 73

Testing

Concurrent dasturlarni testlash oddiy ketma-ket dasturlarni testlashdan ancha murakkabroq. Flaky testlar (ba'zan o'tib, ba'zan yiqiladigan noaniq testlar), yashirin poygalar va sezdirmasdan xotirani yeb qo'yuvchi sizib chiqqan goroutine'lar har qanday dasturchini chalg'itishi mumkin. Ushbu bobda ularni tizimli testlashni o'rganamiz!

Bob mundarijasi:


Goroutine'lar tugashini kutish

Testlarda time.Sleep() ishlatish — eng xavfli va noto'g'ri amaliyotdir:

  • Sekin serverlarda (CI/CD) vaqt yetmay test asossiz yiqiladi;
  • Tez mashinalarda esa test keraksiz uzoq kutib dasturni sekinlashtiradi.

Har doim deterministik vositalar — sync.WaitGroup, kanallar yoki context.WithTimeout orqali kuting:

example.go
func TestWorker(t *testing.T) {
	done := make(chan struct{})
	go func() {
		doWork()
		close(done)
	}()

	select {
	case <-done:
		// ajoyib, vaqtida tugadi!
	case <-time.After(1 * time.Second):
		t.Fatal("Ishchi 1 soniya ichida tugamadi, timeout!")
	}
}

Goroutine sizib chiqishini tekshirish (uber-go/goleak)

Test o'tdi, ammo uning ichida fon goroutine'si abadiy qolib ketdimi? Buni tekshirish uchun Uber kompaniyasi tomonidan yaratilgan mashhur go.uber.org/goleak kutubxonasi ishlatiladi:

example.go
func TestWithNoLeaks(t *testing.T) {
	defer goleak.VerifyNone(t) // test yakunida barcha yangi goroutine'lar tozalanganini tekshiradi

	ch := make(chan int)
	go func() {
		// agar bu yerda kanal yopilmasa yoki goroutine to'xtamasa, goleak testni yiqitadi!
		ch <- 42
	}()

	val := <-ch
	if val != 42 {
		t.Errorf("Kutilgan 42, olindi %d", val)
	}
}

Poygalarni aniqlash: go test -race

Barcha unit va integratsiya testlaringizni har doim -race bayrog'i bilan ishga tushiring:

bash
go test -v -race ./...

Bu barcha data race holatlarini avtomatik topadi va qaysi qatorda xato ro'y berganini ko'rsatadi.


Concurrent Benchmark'lar (b.RunParallel)

Tizimingiz ko'p yadroli protsessorda bir vaqtda qanchalik tez ishlashini o'lchash uchun b.RunParallel ishlatiladi:

example.go
func BenchmarkConcurrentCache(b *testing.B) {
	cache := NewCache()

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			cache.Set("kalit", "qiymat")
			_ = cache.Get("kalit")
		}
	})
}

Topshiriq: Poyga testini tuzish

Parallel chaqirilganda xavfsiz ishlaydigan Counter struct'i uchun unit test yozing.

example.go
package main

import (
	"sync"
	"testing"
)

type Counter struct {
	mu sync.Mutex
	n  int
}

func (c *Counter) Inc() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.n++
}

func (c *Counter) Val() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.n
}

func TestCounter(t *testing.T) {
	c := &Counter{}
	var wg sync.WaitGroup

	// TODO: 100 ta goroutine orqali Inc() chaqiring va wg.Wait() dan so'ng Val() == 100 ekanini tekshiring
}

Xulosa va keyingi qadamlar

Ishonchli concurrent tizimlar faqat to'g'ri testlash va -race orqali kafolatlanadi.

Keyingi yakuniy bob: 13. Internals (Go runtime scheduler'ining ichki sirlari)!