Синтетическое время для тестов
Допустим, у нас есть функция, которая таймаутит, если не получила значение из канала в течение минуты:
func Read(in chan int) (int, error) {
select {
case v := <-in:
return v, nil
case <-time.After(60 * time.Second):
return 0, errors.New("timeout")
}
}
Используем так:
ch := make(chan int)
go func() {
ch <- 42
}()
val, err := Read(ch)
fmt.Printf("val=%v, err=%v\n", val, err)
// val=42, err=<nil>
Как бы теперь протестировать ситуацию с таймаутом? Нежелательно, чтобы тест действительно ждал 60 секунд. Можно сделать таймаут параметром функции (и стоило бы, наверно). Но допустим, что это не вариант.
Новый пакет testing/synctest
спешит на помощь! Функция synctest.Run
выполняет изолированный «пузырь» в отдельной горутине. Внутри пузыря функции пакета time
используют искусственные часы, что позволяет тесту пройти мгновенно:
func TestReadTimeout(t *testing.T) {
synctest.Run(func() {
ch := make(chan int)
_, err := Read(ch)
if err == nil {
t.Fatal("expected timeout error, got nil")
}
})
}
Горутины в пузыре используют синтетическую реализацию времени (точка отсчета - полночь UTC 2000-01-01). Время идет вперед, когда все горутины в пузыре заблокированы.
В нашем тесте, когда единственная горутина заблокировалась на select
в Read
, часы в пузыре разом скакнули вперед на 60 секунд, и сработал таймаут.
Пакет synctest
пока экспериментальный, по умолчанию выключен. Можно включить переменной GOEXPERIMENT=synctest
при сборке. API пакета может измениться в будущих версиях.
★ Подписывайтесь на новые заметки.