A concorrência é talvez a funcionalidade “carro-chefe” do Go, mas o teste de concorrência no Go sempre foi um desafio. Muitas vezes, desenvolvedores recorrem a artifícios como time.Sleep
para tentar sincronizar goroutines durante os testes, o que leva a cenários instáveis, lentos e difíceis de reproduzir.
Com o lançamento do Go 1.24, foi introduzido um recurso experimental que promete melhorar significativamente essa experiência: o pacote testing/synctest
. Neste artigo, eu trouxe um pouco do que ele propõe, como utilizá-lo na prática e quais são suas limitações atuais.
O problema dos testes concorrentes
Quando escrevemos testes para funções que utilizam goroutines, frequentemente lidamos com problemas como:
Race conditions difíceis de reproduzir;
Testes intermitentes (“flaky”);
Necessidade de inserir
time.Sleep
apenas para garantir sincronização;Testes lentos por dependerem de tempo real.
Esses pontos tornam a testabilidade de código concorrente algo delicado, e a confiabilidade dos testes pode acabar sendo comprometida.
A proposta do testing/synctest
O pacote testing/synctest
foi projetado para oferecer um ambiente de execução isolado — chamado de “bolha” — no qual as goroutines e o tempo podem ser controlados de forma mais precisa.
A principal função do pacote é:
func Run(f func())
Essa função executa f
dentro da bolha. Enquanto estiver dentro dessa bolha:
O tempo é virtual: funções como
time.Sleep
avançam instantaneamente, desde que todas as goroutines estejam bloqueadas ou finalizadas.Todas as goroutines iniciadas dentro da bolha são monitoradas.
A execução só sai de
Run
quando todas as goroutines terminarem.
Outro recurso importante é:
func Wait()
Essa função bloqueia até que todas as goroutines da bolha estejam em estado de espera (ou finalizadas). Isso permite ao desenvolvedor inserir pontos de sincronização mais seguros nos testes.
Exemplo prático
Vamos considerar um caso simples de uso de time.Sleep
em uma goroutine:
func TestSleepWithSynctest(t *testing.T) { synctest.Run(func() { start := time.Now() time.Sleep(2 * time.Second) elapsed := time.Since(start) if elapsed != 2*time.Second { t.Errorf("esperado 2s, obtido %v", elapsed) } }) }
Neste exemplo, o time.Sleep
é executado instantaneamente dentro da bolha, mas o tempo percebido (time.Since
) respeita a simulação, tornando o teste rápido e preciso.
Um exemplo envolvendo sincronização entre goroutines:
func TestChannelWithSynctest(t *testing.T) { synctest.Run(func() { ch := make(chan int) go func() { time.Sleep(1 * time.Second) ch <- 42 }() synctest.Wait() // Aguarda a goroutine estar bloqueada no sleep result := <-ch if result != 42 { t.Errorf("esperado 42, obtido %d", result) } }) }
Como ativar o synctest
Por ser experimental, ele precisa ser habilitado explicitamente no momento da execução dos testes:
GOEXPERIMENT=synctest go test ./...
Sem essa flag, o pacote testing/synctest
não estará disponível.
Limitações atuais
Apesar de promissor, o synctest
ainda está em fase experimental e possui algumas limitações:
Disponibilidade restrita: não faz parte do release estável por padrão. A flag
GOEXPERIMENT=synctest
precisa ser usada.Funciona apenas dentro do
synctest.Run
: qualquer goroutine iniciada fora da bolha não será monitorada.Cobertura limitada de APIs: nem todas as interações com o tempo e goroutines são controladas pela bolha (por exemplo, chamadas externas ou bibliotecas que usam tempo em baixo nível).
Ambiente artificial: como o tempo é virtual dentro da bolha, o comportamento pode divergir levemente do ambiente de produção.
Performance em grandes testes: ainda não há benchmarks amplos sobre o impacto de usar
synctest
em grandes suítes de testes concorrentes.
Quando utilizar
O synctest
é uma excelente ferramenta quando:
Você quer testar lógica concorrente sem depender de
Sleep
;Precisa garantir que goroutines se comportem de forma previsível;
Quer acelerar testes que usam tempo e concorrência;
Deseja testes mais determinísticos e menos suscetíveis a falhas aleatórias.
Conclusão
O testing/synctest
é uma adição muito bem-vinda ao ecossistema Go. Ele ataca diretamente um dos pontos mais frágeis da linguagem: a testabilidade de código concorrente. Mesmo sendo experimental, já mostra potencial para melhorar a confiabilidade e a velocidade dos testes que envolvem goroutines.
Se você escreve código concorrente com frequência, vale a pena experimentar essa funcionalidade no seu projeto e acompanhar sua evolução nas próximas versões do Go.
Quer saber mais? Leia no Go Blog o post oficial sobre a funcionalidade
Até a próxima, keep on coding!