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!
Testlarda time.Sleep() ishlatish — eng xavfli va noto'g'ri amaliyotdir:
Har doim deterministik vositalar — sync.WaitGroup, kanallar yoki context.WithTimeout orqali kuting:
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!")
}
}
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:
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)
}
}
Barcha unit va integratsiya testlaringizni har doim -race bayrog'i bilan ishga tushiring:
go test -v -race ./...
Bu barcha data race holatlarini avtomatik topadi va qaysi qatorda xato ro'y berganini ko'rsatadi.
Tizimingiz ko'p yadroli protsessorda bir vaqtda qanchalik tez ishlashini o'lchash uchun b.RunParallel ishlatiladi:
func BenchmarkConcurrentCache(b *testing.B) {
cache := NewCache()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
cache.Set("kalit", "qiymat")
_ = cache.Get("kalit")
}
})
}
Parallel chaqirilganda xavfsiz ishlaydigan Counter struct'i uchun unit test yozing.
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
}
Ishonchli concurrent tizimlar faqat to'g'ri testlash va -race orqali kafolatlanadi.
Keyingi yakuniy bob: 13. Internals (Go runtime scheduler'ining ichki sirlari)!