Testes de Concorrência com synctest no Go 1.24: Mais Precisão, Menos Sleep

Descubra como fazer testes de concorrência no Go com synctest, recurso experimental do Go 1.24 que facilita testes com goroutines e tempo controlado.

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:

  1. Disponibilidade restrita: não faz parte do release estável por padrão. A flag GOEXPERIMENT=synctest precisa ser usada.

  2. Funciona apenas dentro do synctest.Run: qualquer goroutine iniciada fora da bolha não será monitorada.

  3. 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).

  4. Ambiente artificial: como o tempo é virtual dentro da bolha, o comportamento pode divergir levemente do ambiente de produção.

  5. 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!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post Anterior
novo go tools 1.24

O go tool do Go 1.24 é umas atualizações mais legais do ecossistema

Posts Relacionados