<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jean Grijp</title>
    <description>The latest articles on DEV Community by Jean Grijp (@jeangrijp).</description>
    <link>https://dev.to/jeangrijp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F676863%2F586fef43-113e-4af6-95d8-d0208287bedc.jpeg</url>
      <title>DEV Community: Jean Grijp</title>
      <link>https://dev.to/jeangrijp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeangrijp"/>
    <language>en</language>
    <item>
      <title>Table-Driven Tests e Paralelismo</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:41:10 +0000</pubDate>
      <link>https://dev.to/jeangrijp/table-driven-tests-e-paralelismo-1ef</link>
      <guid>https://dev.to/jeangrijp/table-driven-tests-e-paralelismo-1ef</guid>
      <description>&lt;p&gt;Em Go, a convenção para testar uma função com vários casos é montar uma tabela de entradas e resultados esperados e percorrê-la com um loop. É o padrão chamado de table-driven tests. Junto com subtestes (&lt;code&gt;t.Run&lt;/code&gt;) e execução paralela (&lt;code&gt;t.Parallel&lt;/code&gt;), ele cobre cenários felizes, casos extremos e caminhos de erro em uma função só, e a suíte roda em uma fração do tempo sequencial.&lt;/p&gt;

&lt;p&gt;Neste texto você vai ver a anatomia desse padrão, o que o &lt;code&gt;t.Parallel()&lt;/code&gt; faz por dentro e a armadilha de captura da variável de iteração, que durante mais de uma década fez testes paralelos passarem sem testar os casos que deviam.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema dos testes repetitivos
&lt;/h2&gt;

&lt;p&gt;Digamos que você implementou uma função de cálculo de desconto. Escrevendo um teste para cada cenário, o arquivo fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Abordagem prolixa e difícil de manter&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestCalcularDesconto_ClienteNovo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;CalcularDesconto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"NOVO"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"esperava 0.0, recebeu %f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestCalcularDesconto_ClienteVIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;CalcularDesconto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;15.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"esperava 15.0, recebeu %f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestCalcularDesconto_ClientePremium&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;CalcularDesconto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PREMIUM"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;20.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"esperava 20.0, recebeu %f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada caso novo duplica o corpo da função, exige um nome longo e repete a asserção. Se a assinatura de &lt;code&gt;CalcularDesconto&lt;/code&gt; mudar, você altera dezenas de funções espalhadas pelo arquivo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A anatomia de um table-driven test
&lt;/h2&gt;

&lt;p&gt;O jeito idiomático separa os dados do teste da lógica de asserção. Uma tabela, que é um slice anônimo de structs, guarda as entradas e os resultados esperados, e um loop executa tudo com o mesmo código:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyoi1nxn0beltwxoh53w3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyoi1nxn0beltwxoh53w3.webp" alt="Diagrama animado: a tabela de casos alimenta o runner, que dispara um t.Run por caso e coleta os resultados no relatório" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A estrutura tem quatro partes: a tabela, o loop sobre ela, um subteste por caso e a asserção.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestOperacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 1. Definição da tabela de casos&lt;/span&gt;
    &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="c"&gt;// Descrição legível do cenário&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="c"&gt;// Parâmetro de entrada&lt;/span&gt;
        &lt;span class="n"&gt;want&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="c"&gt;// Resultado esperado&lt;/span&gt;
        &lt;span class="n"&gt;wantErr&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;    &lt;span class="c"&gt;// Indica se esperamos um erro neste caso&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"número positivo par"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"número ímpar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 2. Iteração sobre a tabela&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// 3. Execução em subteste isolado&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Operacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c"&gt;// 4. Asserção padronizada&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantErr&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Operacao(%d) erro = %v, esperava erro? %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Operacao(%d) = %d; esperado = %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por que uma struct anônima (&lt;code&gt;[]struct{...}&lt;/code&gt;)? Ela só existe dentro da função de teste, então não polui o pacote com tipos que ninguém mais usa. Adicionar um cenário vira acrescentar uma linha de valores. E os campos nomeados (&lt;code&gt;name: "...", input: ...&lt;/code&gt;) deixam claro o que cada valor significa, sem depender da posição.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subtestes com &lt;code&gt;t.Run()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;t.Run(name, func(t *testing.T))&lt;/code&gt; chegou no Go 1.7 e mudou a forma como o Go reporta resultados. Cada volta do loop vira um subteste independente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O relatório é hierárquico. O runner mostra o teste pai, uma barra e o nome do subteste: &lt;code&gt;TestOperacao/número_positivo_par&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A falha é isolada. Se um subteste falhar com &lt;code&gt;t.Fatalf&lt;/code&gt;, só ele é interrompido. O loop continua e os outros casos da tabela são avaliados.&lt;/li&gt;
&lt;li&gt;Dá para rodar um cenário só pelo terminal, com uma expressão regular na flag &lt;code&gt;-run&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Executa apenas o subteste de número ímpar&lt;/span&gt;
go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; TestOperacao/número_ímpar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;t.Errorf&lt;/code&gt; ou &lt;code&gt;t.Fatalf&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;t.Fatalf&lt;/code&gt; quando continuar não faz sentido. Se a função devia devolver um objeto e devolveu &lt;code&gt;nil&lt;/code&gt;, seguir adiante causaria um panic de ponteiro nulo ao acessar &lt;code&gt;user.Email&lt;/code&gt;. O &lt;code&gt;t.Fatalf&lt;/code&gt; aborta o subteste atual na hora.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;t.Errorf&lt;/code&gt; quando a execução terminou normalmente, mas o valor final difere do esperado. O teste continua e confere os outros campos da mesma resposta, então você vê todos os erros de uma vez no log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execução paralela com &lt;code&gt;t.Parallel()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Por padrão, os testes de um pacote Go rodam em sequência. Numa suíte com centenas de testes, ou com parsing de arquivos pesados, criptografia e I/O, isso deixa núcleos da CPU parados.&lt;/p&gt;

&lt;p&gt;Chamar &lt;code&gt;t.Parallel()&lt;/code&gt; dentro de um subteste instrui o runner a executá-lo em paralelo com os outros testes paralelos:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5mfo1vapynagiimmpt9m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5mfo1vapynagiimmpt9m.webp" alt="Diagrama animado: três subtestes de 100ms rodando um após o outro no modo sequencial e ao mesmo tempo no modo paralelo" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  O ciclo de vida de um teste paralelo
&lt;/h3&gt;

&lt;p&gt;Quando o Go encontra um &lt;code&gt;t.Parallel()&lt;/code&gt;, o subteste passa por três fases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;RUN&lt;/code&gt;: o subteste começa a executar na goroutine do teste pai.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PAUSE&lt;/code&gt;: ao chegar no &lt;code&gt;t.Parallel()&lt;/code&gt;, ele pausa na hora, entra na lista de testes paralelos pendentes e libera o loop principal para seguir.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CONT&lt;/code&gt;: quando a função pai termina de registrar todos os subtestes e chega ao fim do seu escopo, o runtime acorda os subtestes pausados em goroutines simultâneas, distribuídas entre os núcleos disponíveis.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dá para ver isso na saída de &lt;code&gt;go test -v&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestDividir_TableDriven
=== PAUSE TestDividir_TableDriven
=== CONT  TestDividir_TableDriven
=== RUN   TestDividir_TableDriven/positivo
=== PAUSE TestDividir_TableDriven/positivo
=== RUN   TestDividir_TableDriven/negativo
=== PAUSE TestDividir_TableDriven/negativo
=== CONT  TestDividir_TableDriven/negativo
=== CONT  TestDividir_TableDriven/positivo
--- PASS: TestDividir_TableDriven/positivo (0.00s)
--- PASS: TestDividir_TableDriven/negativo (0.00s)
PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controlando o paralelismo
&lt;/h3&gt;

&lt;p&gt;O número máximo de testes paralelos simultâneos é controlado pela flag &lt;code&gt;-parallel&lt;/code&gt;. O padrão é o valor de &lt;code&gt;GOMAXPROCS&lt;/code&gt;, que normalmente é o número de núcleos da CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Permite até 16 testes paralelos simultâneos&lt;/span&gt;
go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-parallel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16 ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A armadilha da variável de loop
&lt;/h2&gt;

&lt;p&gt;Durante mais de dez anos, combinar &lt;code&gt;for ... range&lt;/code&gt; com &lt;code&gt;t.Parallel()&lt;/code&gt; foi fonte de um dos bugs mais traiçoeiros do Go:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1x7z0kezahh523kwxxqz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1x7z0kezahh523kwxxqz.webp" alt="Diagrama animado: no Go 1.21 todas as goroutines de teste leem o mesmo tc e testam só o caso C; no Go 1.22+ ou com tc := tc, cada subteste testa o seu caso" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O código perigoso é este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Comportamento bugado em Go 1.21 e anteriores&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// O subteste pausa aqui&lt;/span&gt;

        &lt;span class="c"&gt;// Quando for retomado, qual é o valor de tc?&lt;/span&gt;
        &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Processar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antes do Go 1.22, a variável &lt;code&gt;tc&lt;/code&gt; era alocada uma única vez para o loop inteiro. A cada iteração o Go só sobrescrevia o mesmo endereço de memória com o próximo item.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;t.Parallel()&lt;/code&gt; pausa a goroutine do subteste, e o loop principal continua correndo até o fim da tabela. Quando os subtestes acordam, todos enxergam o valor da última iteração. O teste reporta sucesso em todos os casos, mas na prática rodou o último caso várias vezes e ignorou os primeiros.&lt;/p&gt;

&lt;h3&gt;
  
  
  As duas soluções
&lt;/h3&gt;

&lt;p&gt;Em Go 1.21 e anteriores, a saída é redeclarar a variável dentro do loop, o que cria uma cópia nova a cada volta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="c"&gt;// Cria uma cópia local isolada na stack&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c"&gt;// Agora tc aponta para a cópia segura&lt;/span&gt;
        &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Processar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Go 1.22 e superiores, a semântica da linguagem mudou: cada iteração do &lt;code&gt;for&lt;/code&gt; cria uma nova instância da variável por padrão. A linha &lt;code&gt;tc := tc&lt;/code&gt; deixou de ser necessária, e essa classe de falha desapareceu.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dica: se você mantém uma biblioteca de código aberto que ainda dá suporte ao Go 1.20 ou 1.21, deixe a linha &lt;code&gt;tc := tc&lt;/code&gt; explícita. Assim a suíte continua correta em qualquer versão do compilador.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Um exemplo completo
&lt;/h2&gt;

&lt;p&gt;Vamos montar uma suíte para um validador de transações financeiras, com erros sentinela e &lt;code&gt;t.Cleanup()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O código de negócio, em &lt;code&gt;transacao.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrValorInvalido&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"o valor da transação deve ser positivo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrContaInvalida&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"número de conta de origem ou destino inválido"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrLimiteUltrapassado&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"valor excede o limite diário da conta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Transacao&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ContaDestino&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Valor&lt;/span&gt;        &lt;span class="kt"&gt;float64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ValidarTransacao aplica as regras de conformidade bancária.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ValidarTransacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limiteDiario&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrValorInvalido&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrContaInvalida&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContaOrigem&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContaDestino&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%w: conta de origem idêntica ao destino"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrContaInvalida&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Valor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limiteDiario&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrLimiteUltrapassado&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A suíte, em &lt;code&gt;transacao_test.go&lt;/code&gt;. O loop abaixo assume Go 1.22 ou superior; em versões anteriores, acrescente &lt;code&gt;tc := tc&lt;/code&gt; no início do corpo do &lt;code&gt;for&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;financeiro_test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"meu-modulo/financeiro"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestValidarTransacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// A suíte pai roda em paralelo com outras do pacote&lt;/span&gt;

    &lt;span class="c"&gt;// Tabela abrangente de cenários de teste&lt;/span&gt;
    &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;         &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;transacao&lt;/span&gt;    &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;
        &lt;span class="n"&gt;limiteDiario&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;
        &lt;span class="n"&gt;wantErr&lt;/span&gt;      &lt;span class="kt"&gt;bool&lt;/span&gt;
        &lt;span class="n"&gt;expectedErr&lt;/span&gt;  &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transação regular aprovada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"98765-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;150.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transação com valor zero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"98765-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrValorInvalido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transação com valor negativo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"98765-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;50.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrValorInvalido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"conta de origem com menos de 5 dígitos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"98765-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrContaInvalida&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transferência para a mesma conta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrContaInvalida&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"valor excede o limite diário"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transacao&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContaOrigem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"12345-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ContaDestino&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"98765-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;1500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrLimiteUltrapassado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Cada subteste executa em paralelo&lt;/span&gt;

            &lt;span class="c"&gt;// t.Cleanup executa garantidamente no final deste subteste&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// Ideal para fechar arquivos, resetar variáveis ou limpar caches locais&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;financeiro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidarTransacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limiteDiario&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c"&gt;// 1. Verificação da ocorrência de erro&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantErr&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ValidarTransacao() erro = %v, esperava erro? %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wantErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c"&gt;// 2. Verificação precisa do tipo/causa do erro com errors.Is&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ValidarTransacao() erro retornado = %v, esperava conter = %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Checklist antes de dar a suíte por concluída
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Compare erros com &lt;code&gt;errors.Is&lt;/code&gt;, nunca por texto. Um &lt;code&gt;if err.Error() == "texto"&lt;/code&gt; quebra na primeira vez que alguém reescrever a mensagem. Valide contra o erro sentinela com &lt;code&gt;errors.Is(err, ErrAlvo)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rode com o detector de corridas na integração contínua:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-race&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se os subtestes paralelos acessarem uma variável global ou outro recurso compartilhado sem proteção, o detector aponta as linhas em conflito.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dê nomes descritivos aos subtestes. O campo &lt;code&gt;name&lt;/code&gt; deve dizer o que o cenário cobre e por quê, por exemplo &lt;code&gt;"idade negativa deve retornar erro de validação"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Não compartilhe estado entre testes paralelos. Um teste com &lt;code&gt;t.Parallel()&lt;/code&gt; não deve alterar variáveis globais, tabelas de banco sem transação isolada ou o diretório de trabalho.&lt;/li&gt;
&lt;li&gt;Chame &lt;code&gt;t.Helper()&lt;/code&gt; na primeira linha de qualquer função auxiliar de asserção. Assim o Go reporta a linha do teste que falhou, e não a linha dentro do utilitário.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Beba Água &amp;lt;3&lt;/p&gt;

&lt;h1&gt;
  
  
  go #golang #testing #tabledriventests #testesunitarios #paralelismo #concorrencia #backend #braziliandevs
&lt;/h1&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>tabledriventests</category>
      <category>testesunitarios</category>
    </item>
    <item>
      <title>6 Go Libraries That Completely Transformed Software Development in 2025</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Tue, 30 Sep 2025 19:24:54 +0000</pubDate>
      <link>https://dev.to/jeangrijp/6-go-libraries-that-completely-transformed-software-development-in-2025-2d3b</link>
      <guid>https://dev.to/jeangrijp/6-go-libraries-that-completely-transformed-software-development-in-2025-2d3b</guid>
      <description></description>
    </item>
    <item>
      <title>Goroutines, threads e o caos coordenado do Go Scheduler.</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Thu, 24 Apr 2025 23:29:30 +0000</pubDate>
      <link>https://dev.to/jeangrijp/goroutines-threads-e-o-caos-coordenado-do-go-scheduler-2cg</link>
      <guid>https://dev.to/jeangrijp/goroutines-threads-e-o-caos-coordenado-do-go-scheduler-2cg</guid>
      <description>&lt;p&gt;Fala dev, estou compartilhando mais um doc que criei no Obsidian, espero que a leitura te ajude de alguma forma. Lembrando que estou aprendendo sobre o assunto, então se tu encontrar algo estranho ou errado, bora trocar ideia, comenta o post com educação e bora codar em Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é uma Thread?
&lt;/h3&gt;

&lt;p&gt;Uma &lt;em&gt;thread&lt;/em&gt; é uma unidade de execução dentro de um processo. É o que permite que um programa realize múltiplas tarefas aparentemente ao mesmo tempo. Em sistemas tradicionais, cada thread possui sua própria stack e é gerenciada pelo sistema operacional, com alto custo de criação e comutação. Em contrapartida, o Go abstrai esse conceito com as goroutines, que são unidades de execução mais leves que threads, gerenciadas pelo runtime do Go, muito mais leves e eficientes para tarefas concorrentes. Mesmo assim, entender o conceito de thread continua sendo útil, porque é a base sobre a qual a concorrência moderna foi construída.&lt;/p&gt;

&lt;p&gt;Cada thread, no contexto de programação, tem a sua &lt;strong&gt;própria &lt;a href="https://dev.to/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l"&gt;call stack&lt;/a&gt;&lt;/strong&gt;. Isso significa que quando você cria uma nova thread, o sistema operacional ou o runtime da linguagem aloca uma nova região de memória exclusiva pra ela, onde vai empilhar as chamadas de função feitas dentro dessa thread. Essa pilha é isolada das demais threads: se duas threads estiverem rodando ao mesmo tempo e ambas chamarem uma função chamada &lt;code&gt;processarDados()&lt;/code&gt;, cada uma delas vai ter a &lt;em&gt;sua própria versão empilhada&lt;/em&gt; de &lt;code&gt;processarDados()&lt;/code&gt; na sua respectiva stack. Elas não brigam pela mesma memória porque estão cada uma vivendo seu mundinho solitário de execução.&lt;/p&gt;

&lt;p&gt;A stack da thread é onde ficam armazenados os dados temporários da execução: parâmetros de função, variáveis locais e o endereço de retorno — ou seja, pra onde voltar quando a função acabar. Quando uma função é chamada, o frame é empilhado na stack da thread atual. Quando ela retorna, o frame é removido. É uma dança organizada de entrada e saída de contextos de execução, sempre seguindo o modelo LIFO: a última chamada é a primeira a sair.&lt;/p&gt;

&lt;p&gt;Agora, por que isso importa? Porque se você fizer besteira dentro de uma thread — tipo criar recursão infinita ou entupir a execução com chamadas profundas — &lt;em&gt;só a stack daquela thread vai explodir&lt;/em&gt;. As outras threads, felizes e inconscientes, continuam rodando como se nada tivesse acontecido.&lt;/p&gt;

&lt;p&gt;E em Go? A coisa muda um pouco, porque Go não usa threads diretamente. Ele usa goroutines, que são programaticamente muito mais leves, mas o princípio é parecido: &lt;strong&gt;cada goroutine tem sua própria stack separada&lt;/strong&gt;, e ela funciona da mesma forma que a stack de uma thread — só que com o charme extra de ser pequena, flexível e controlada pelo runtime em vez do sistema operacional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go Scheduler
&lt;/h3&gt;

&lt;p&gt;No Go, o agendador de goroutines — chamado carinhosamente de &lt;strong&gt;Go scheduler&lt;/strong&gt; — é o componente do runtime responsável por decidir &lt;strong&gt;qual goroutine roda, em qual thread, e quando&lt;/strong&gt;. E aqui vai o plot twist: o Go &lt;em&gt;não&lt;/em&gt; cria uma thread nova do sistema operacional pra cada goroutine. Se fizesse isso, seria como contratar um funcionário novo pra cada planilha que você abre — caro, lento, e só útil se você quiser falir seu computador.&lt;/p&gt;

&lt;p&gt;Em vez disso, o Go cria um pequeno grupo de &lt;strong&gt;threads reais&lt;/strong&gt; (do sistema operacional), chamadas de &lt;strong&gt;M&lt;/strong&gt; (&lt;em&gt;machine threads&lt;/em&gt;), e distribui as goroutines entre elas. Essas goroutines são gerenciadas inteiramente pelo runtime da linguagem, o que as torna semelhantes às chamadas &lt;em&gt;green threads&lt;/em&gt; ou &lt;em&gt;user-space threads&lt;/em&gt; — embora com diferenças importantes: no Go, múltiplas goroutines são mapeadas para múltiplas threads, não apenas uma.&lt;/p&gt;

&lt;p&gt;Cada &lt;code&gt;M&lt;/code&gt; precisa estar associada a um &lt;code&gt;P&lt;/code&gt;, de &lt;em&gt;Processor&lt;/em&gt;, que é uma unidade lógica do Go usada para coordenar a execução de código Go. O modelo de execução é conhecido como &lt;strong&gt;G:M:P&lt;/strong&gt;, onde:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;G&lt;/code&gt; é a goroutine (o que será executado),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;M&lt;/code&gt; é a thread do sistema operacional (o que executa),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;P&lt;/code&gt; é o processador lógico (o que permite que a execução ocorra).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quer um exemplo? Suponha que seu programa tenha &lt;code&gt;GOMAXPROCS=4&lt;/code&gt;. Isso quer dizer que o runtime do Go vai criar 4 &lt;strong&gt;P&lt;/strong&gt; — ou seja, vai usar até 4 núcleos da CPU simultaneamente. Esses P’s são os “slots de execução”: cada &lt;code&gt;P&lt;/code&gt; permite que uma &lt;code&gt;M&lt;/code&gt; execute goroutines, empilhando e desempilhando chamadas dentro da stack exclusiva da goroutine. O runtime coordena essa execução, e as threads (&lt;code&gt;M&lt;/code&gt;) vão pegando e executando as goroutines disponíveis.&lt;/p&gt;

&lt;p&gt;O agendador decide &lt;em&gt;quando&lt;/em&gt; uma goroutine deve parar de rodar e outra deve assumir o controle. Isso acontece, por exemplo, quando uma goroutine realiza uma operação bloqueante (como I/O), ou atinge um &lt;strong&gt;ponto seguro de preempção&lt;/strong&gt; — como loops longos ou chamadas a funções específicas. Nesse momento, o scheduler salva o estado da goroutine (inclusive a stack!) e coloca outra no lugar, como quem troca um funcionário sonolento por outro animado na linha de produção.&lt;/p&gt;

&lt;p&gt;O mais divertido (ou caótico, se você for o tipo de dev que gosta de controle absoluto) é que tudo isso é &lt;strong&gt;transparente&lt;/strong&gt;. Você escreve um código com &lt;code&gt;go minhaFuncao()&lt;/code&gt;, e de repente tem mil goroutines rodando “em paralelo” — mesmo que elas estejam dividindo umas poucas threads reais por trás das cortinas. Cada goroutine mantém sua própria stack, &lt;strong&gt;pequena e dinâmica&lt;/strong&gt;, e o runtime gerencia o show: cresce stacks sob demanda, troca contextos, e faz você parecer um gênio da concorrência mesmo sem saber quantos &lt;code&gt;M&lt;/code&gt;, &lt;code&gt;P&lt;/code&gt; e &lt;code&gt;G&lt;/code&gt; estão trabalhando nos bastidores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mas o que é esse tal de &lt;code&gt;P&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;No modelo de agendamento do Go, o &lt;code&gt;P&lt;/code&gt; (de &lt;em&gt;Processor&lt;/em&gt;) representa uma &lt;strong&gt;unidade lógica de execução&lt;/strong&gt; controlada pelo runtime. Ele não é um thread nem uma goroutine, mas sim o &lt;strong&gt;intermediário essencial&lt;/strong&gt; que gerencia o agendamento de goroutines e fornece contexto para que o código Go seja executado por uma thread (&lt;code&gt;M&lt;/code&gt;). Cada &lt;code&gt;P&lt;/code&gt; possui uma &lt;strong&gt;fila local de goroutines prontas para execução&lt;/strong&gt;, conhecida como &lt;strong&gt;run queue&lt;/strong&gt;. O runtime sempre tenta consumir essa fila local primeiro, o que melhora o desempenho ao evitar sincronização com outras estruturas globais e aproveita melhor o cache da CPU.&lt;/p&gt;

&lt;p&gt;Quando uma thread (&lt;code&gt;M&lt;/code&gt;) está associada a um &lt;code&gt;P&lt;/code&gt;, ela pode executar goroutines da run queue daquele &lt;code&gt;P&lt;/code&gt;. Caso essa fila esteja vazia, o runtime tenta buscar novas goroutines da &lt;strong&gt;fila global&lt;/strong&gt;, ou até mesmo &lt;strong&gt;roubar goroutines de outro &lt;code&gt;P&lt;/code&gt;&lt;/strong&gt;, numa estratégia conhecida como &lt;strong&gt;work stealing&lt;/strong&gt;. Esse mecanismo evita desequilíbrios entre processadores lógicos e mantém a carga distribuída mesmo em cenários de concorrência intensa.&lt;/p&gt;

&lt;p&gt;O número de &lt;code&gt;P&lt;/code&gt;s ativos é determinado por &lt;code&gt;GOMAXPROCS&lt;/code&gt;, o que significa que, mesmo que existam milhares de goroutines, apenas um número limitado (igual ao de &lt;code&gt;P&lt;/code&gt;s) pode estar executando código Go simultaneamente. As demais ficam na fila aguardando sua vez.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chamadas bloqueantes (syscalls) e uso inteligente de threads (&lt;code&gt;M&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Em Go, uma goroutine pode, eventualmente, fazer uma chamada ao sistema operacional que bloqueia a thread. Isso acontece em situações comuns, como chamadas de rede, leitura de arquivos ou qualquer operação de I/O via syscall. Quando isso ocorre, a thread (&lt;code&gt;M&lt;/code&gt;) que estava executando a goroutine &lt;strong&gt;fica bloqueada&lt;/strong&gt; até que a operação termine.&lt;/p&gt;

&lt;p&gt;Agora vem a parte crítica: se o runtime simplesmente deixasse o &lt;code&gt;P&lt;/code&gt; associado a essa &lt;code&gt;M&lt;/code&gt; bloqueada, o programa perderia a capacidade de executar novas goroutines naquele slot de execução — o que resultaria em desperdício de recursos e perda de desempenho. Para evitar isso, o Go runtime &lt;strong&gt;desassocia o &lt;code&gt;P&lt;/code&gt; da thread bloqueada (&lt;code&gt;M&lt;/code&gt;)&lt;/strong&gt; e o entrega a outra thread que esteja disponível. Dessa forma, o &lt;code&gt;P&lt;/code&gt; continua executando outras goroutines prontas na fila, e o programa segue rodando com eficiência, mesmo durante operações de I/O demoradas. Se não houver nenhuma &lt;code&gt;M&lt;/code&gt; disponível no momento, o runtime pode &lt;strong&gt;criar uma nova thread real&lt;/strong&gt; do sistema operacional. Isso é feito de maneira controlada, com limites internos, para evitar que o programa crie um número excessivo de threads e sobrecarregue o sistema.&lt;/p&gt;

&lt;p&gt;Essa estratégia de &lt;strong&gt;desacoplamento entre o agendamento das goroutines e o bloqueio do sistema operacional&lt;/strong&gt; é um dos principais motivos pelos quais o Go consegue lidar tão bem com aplicações altamente concorrentes — como servidores HTTP, workers de fila, ou qualquer sistema com múltiplas operações simultâneas que dependem de I/O. Além disso, é importante destacar que o Go diferencia bem &lt;strong&gt;código Go puro&lt;/strong&gt; (que pode ser interrompido, agendado, migrado entre threads) de &lt;strong&gt;código que envolve chamadas nativas ou externas&lt;/strong&gt;, onde o controle de execução passa, mesmo que temporariamente, para o SO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Um pouco mais sobre stacks dinâmicas das goroutines
&lt;/h3&gt;

&lt;p&gt;Uma das características mais importantes das goroutines é o uso de &lt;strong&gt;stacks dinâmicas&lt;/strong&gt;, que são controladas inteiramente pelo runtime do Go. Diferente das threads tradicionais do sistema operacional que geralmente alocam uma stack fixa de 1 MB ou mais, cada goroutine começa com uma stack extremamente pequena, geralmente &lt;strong&gt;apenas 2 KB&lt;/strong&gt;. Essa escolha reduz drasticamente o custo de criação de novas goroutines e é um dos principais motivos pelos quais é possível criar &lt;strong&gt;milhares ou até milhões de goroutines&lt;/strong&gt; em uma única aplicação Go sem comprometer a memória do sistema. A medida que a execução da goroutine avança, se o espaço da stack se tornar insuficiente (por exemplo, devido a chamadas de função aninhadas ou uso intensivo de variáveis locais), o runtime detecta essa situação e &lt;strong&gt;realoca a stack em uma região maior de memória&lt;/strong&gt;, copiando seu conteúdo atual e ajustando os ponteiros automaticamente. Esse processo é &lt;strong&gt;transparente para o desenvolvedor&lt;/strong&gt; e não exige nenhuma intervenção manual. Além disso, o runtime é capaz de &lt;strong&gt;reduzir o tamanho da stack&lt;/strong&gt; de uma goroutine quando percebe que ela não precisa mais de tanta memória. Essa &lt;strong&gt;flexibilidade bidirecional&lt;/strong&gt; permite uma gestão de memória muito mais eficiente do que stacks fixas, e ajuda a manter a escalabilidade e responsividade da aplicação.&lt;/p&gt;

&lt;p&gt;Vale lembrar que essa operação de crescimento (ou encolhimento) da stack pode ter custo de desempenho pontual, especialmente se ocorrer com frequência. Por isso, em cenários críticos de performance, é possível otimizar o uso da stack escrevendo funções com profundidade de chamada controlada e evitando estruturas muito grandes em variáveis locais.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Como o Go lida com preempção&lt;/strong&gt;, ou seja, como ele lida com aquelas goroutines folgadas que decidem nunca mais sair da CPU?
&lt;/h3&gt;

&lt;p&gt;O agendador do Go precisa garantir que nenhuma goroutine fique se achando a dona da CPU. Pra isso, existe o conceito de &lt;strong&gt;preempção&lt;/strong&gt;, que basicamente é o runtime dizendo “chega, tua vez acabou, deixa o coleguinha brincar”. Só que o Go não faz isso de forma brutal, como alguns sistemas operacionais que simplesmente pausam a thread no meio de qualquer instrução. Em vez disso, o Go usa o modelo de &lt;strong&gt;preempção cooperativa&lt;/strong&gt;, o que significa que ele só pode interromper a execução de uma goroutine &lt;strong&gt;em pontos seguros&lt;/strong&gt; do código, lugares estrategicamente definidos onde o runtime sabe que pode pausar sem quebrar tudo.&lt;/p&gt;

&lt;p&gt;Esses pontos seguros (ou &lt;em&gt;safe points&lt;/em&gt;, se você quiser parecer mais chique) são colocados pelo compilador em lugares como o início de loops, chamadas de função ou outras instruções que o runtime monitora. A ideia é evitar que uma goroutine fique rodando pra sempre sem dar chance pras outras, tipo aquele colega de reunião que não larga o microfone.&lt;/p&gt;

&lt;p&gt;Antes do Go 1.14, isso era um problema sério. Se uma goroutine entrasse num loop longo sem fazer nenhuma chamada externa, ela podia simplesmente &lt;strong&gt;monopolizar um núcleo da CPU&lt;/strong&gt; e impedir que outras goroutines rodassem. Era como ter um funcionário que esqueceu de sair pra almoço e não deixa ninguém mais usar a sala de reunião. A partir do Go 1.14, o runtime ganhou um superpoder: o compilador passou a inserir instruções especiais nos loops e outros trechos críticos, permitindo que o agendador dê aquele &lt;strong&gt;“tapinha no ombro” da goroutine&lt;/strong&gt; e diga: "tempo esgotado, agora deixa o próximo". Quando isso acontece, o estado da execução é salvo e outra goroutine assume a execução no mesmo &lt;code&gt;P&lt;/code&gt;. Esse esquema garante que nenhuma goroutine abuse do tempo de CPU e ajuda o Go a manter o show rodando suave — sem threads explodindo, sem pausas indesejadas, e sem você precisar escrever uma linha de código pra gerenciar isso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitações e armadilhas do Go Scheduler (sim, ele também tem seus dias ruins)
&lt;/h3&gt;

&lt;p&gt;O Go scheduler é ótimo — ele faz muito com pouco: organiza milhares de goroutines com algumas threads reais, empilha, desempilha, redistribui... tudo isso quase sem você notar. Mas ele &lt;strong&gt;não é mágico nem infalível&lt;/strong&gt;. Em algumas situações específicas, ele pode se tornar um &lt;strong&gt;gargalo&lt;/strong&gt;, especialmente quando a arquitetura da aplicação força os limites do modelo &lt;code&gt;G:M:P&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Um dos principais pontos de atenção está no valor de &lt;code&gt;GOMAXPROCS&lt;/code&gt;. Como esse valor define quantos &lt;code&gt;P&lt;/code&gt;s existem, ele &lt;strong&gt;limita o número de goroutines que podem executar código Go ao mesmo tempo&lt;/strong&gt;. Se &lt;code&gt;GOMAXPROCS=2&lt;/code&gt;, por exemplo, mesmo que você tenha 10 mil goroutines prontas pra rodar, só duas vão de fato executar simultaneamente. As outras ficam esperando na fila. Isso é ótimo pra controle de paralelismo, mas pode gerar filas grandes e latência inesperada se o número de goroutines prontas crescer rápido demais.&lt;/p&gt;

&lt;p&gt;Outro fator crítico: o scheduler não tem conhecimento semântico do que suas goroutines estão fazendo. Ele não sabe se a goroutine está processando uma requisição urgente ou só atualizando um cache irrelevante. O escalonamento é feito com base em prontidão, não em prioridade. Isso significa que goroutines com workloads diferentes podem &lt;strong&gt;competir igualmente por tempo de execução&lt;/strong&gt;, o que nem sempre é ideal. Em alguns casos, goroutines importantes podem ser preemptadas por outras menos críticas, impactando a qualidade do serviço.&lt;/p&gt;

&lt;p&gt;O scheduler também pode sofrer se você tiver &lt;strong&gt;muitas goroutines prontas ao mesmo tempo&lt;/strong&gt;, mas com uma lógica que exige muita alternância entre elas (ex: milhares de canais se comunicando em alta frequência). Isso pode causar overhead de troca de contexto e pressionar a fila global de goroutines — que, diferente das filas locais dos &lt;code&gt;P&lt;/code&gt;s, exige lock e pode se tornar um &lt;strong&gt;ponto de contenção&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Além disso, é comum ver devs criando goroutines demais sem pensar no lifecycle delas. Se você esquece de controlar o tempo de vida de uma goroutine (como não cancelá-la via context, por exemplo), elas continuam vivas, mesmo que não estejam fazendo nada útil. Isso gera &lt;strong&gt;acúmulo silencioso&lt;/strong&gt;: pilhas crescendo, memória sendo usada à toa, e mais carga sobre o agendador.&lt;/p&gt;

&lt;p&gt;Por fim, como o Go não tem preempção baseada em tempo real (como certos kernels de SO), uma goroutine que faz &lt;strong&gt;uso intensivo de CPU sem pontos de preempção&lt;/strong&gt; ainda pode gerar starvation (as outras goroutines ficam sem tempo de execução). Isso é raro com as melhorias do Go 1.14+, mas ainda possível se você escrever código que "engole" o processador por muito tempo sem pausas.&lt;/p&gt;

&lt;h3&gt;
  
  
  chegou até aqui?
&lt;/h3&gt;

&lt;p&gt;Entender como o Go lida com agendamento, preempção, chamadas bloqueantes e stacks minúsculas não só te ajuda a escrever código mais eficiente, como também evita que você saia criando goroutine igual panfleto em semáforo e depois não saiba de onde vem o vazamento de memória. Se esse artigo te ajudou, manda pra aquele(a) amigo(a) que acha que &lt;code&gt;go func() {}&lt;/code&gt; é solução mágica pra tudo. E se você encontrou algum erro, manda também, estou aprendendo sobre o tema, e além disso porque o scheduler pode escalar milhares de goroutines, mas eu ainda não escalo múltiplas versões de mim mesmo.&lt;/p&gt;

&lt;p&gt;Valeu demais por acompanhar. Até a próxima execução paralela. #Beba_agua.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Go e o Stack Invisível: Como Funções e Goroutines Administram Memória nos Bastidores.</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Tue, 22 Apr 2025 12:54:21 +0000</pubDate>
      <link>https://dev.to/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l</link>
      <guid>https://dev.to/jeangrijp/go-e-o-stack-invisivel-como-funcoes-e-goroutines-administram-memoria-nos-bastidores-2b7l</guid>
      <description>&lt;p&gt;Comecei a estudar sobre a Call Stack do Go e percebi que seria legal compartilhar o resultado, então eu espero que esse post ajude a quem estiver aprendendo Go também. Ao longo da explicação dos conceitos, pode ser que eu repita algo que já expliquei, mas a intenção é reforçar o conceito para eliminar possíveis dúvidas, mas claro, sem deixar o texto extremamente repetitivo. beba água.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;stack&lt;/em&gt;, ou pilha de execução, é uma estrutura de dados usada internamente pelas linguagens de programação para controlar a ordem de execução das funções. Ela segue uma lógica chamada &lt;strong&gt;LIFO&lt;/strong&gt;, que significa "Last In, First Out" — ou seja, a última função que entrou na pilha é a primeira que vai sair. É como empilhar livros: o último que você colocou em cima é o primeiro que você vai tirar. Essa pilha não é um conceito visual bonitinho só pra programador iniciante se sentir incluído; ela é real, ela mora na memória do processo e serve pra guardar o que está acontecendo a cada momento da execução.&lt;/p&gt;

&lt;p&gt;Quando uma função é chamada, o programa empilha um &lt;strong&gt;frame&lt;/strong&gt;, que é como um pacote com informações: quais são os argumentos da função, quais variáveis ela declarou internamente, e — talvez o mais importante — qual o endereço de memória que ele precisa voltar depois que essa função terminar. Assim que a função conclui seu trabalho, esse frame é retirado da Stack, e o controle volta pra função anterior, que estava de braços cruzados esperando isso acontecer.&lt;/p&gt;

&lt;p&gt;Esse processo de empilhar e desempilhar acontece de forma automática, você não precisa fazer nada. A não ser que você seja um desenvolvedor em Go querendo entender por que o seu programa explodiu com um &lt;code&gt;stack overflow&lt;/code&gt;, o que geralmente significa que uma função chamou a si mesma tantas vezes que a pilha não teve mais espaço pra guardar os frames.&lt;/p&gt;

&lt;p&gt;No Go, cada função que você chama também entra na stack, como em qualquer linguagem procedural que não acordou ontem. A diferença está na forma como o Go gerencia essa stack por trás das cortinas. Em vez de criar uma stack grandona e fixa por thread, como acontece em linguagens como C ou Java, o Go tem um modelo mais... econômico. Cada goroutines (que é uma unidade de execução leve, menor que uma thread, e gerenciada pelo runtime do Go) começa com uma stack minúscula, normalmente em torno de 2 KB, isso é ridiculamente pequeno.&lt;/p&gt;

&lt;p&gt;Essa stack pequenininha é &lt;strong&gt;dinâmica&lt;/strong&gt;. O que significa isso? Significa que, à medida que a função chama outra função, e assim por diante, o Go percebe quando está ficando apertado e realoca a stack para um espaço maior. Sim, ele literalmente copia a pilha inteira para outro lugar na memória. Parece ineficiente, mas na prática é bem rápido e muito mais econômico do que reservar 1 MB por goroutine só porque você tem sonhos grandes demais. É esse esquema que permite ao Go criar centenas de milhares de goroutines sem mandar sua máquina pro hospital.&lt;/p&gt;

&lt;p&gt;Mas nem tudo são flores, como o stack é móvel, o Go precisa tomar muito cuidado com ponteiros. Diferente de C, onde um ponteiro pode apontar pro nada e te causar pesadelos, no Go o runtime cuida de atualizar tudo quando o stack é movido.&lt;/p&gt;

&lt;p&gt;Além disso, quando ocorre um erro, como um &lt;code&gt;panic&lt;/code&gt;, o Go consegue imprimir um &lt;strong&gt;stack trace&lt;/strong&gt; que mostra exatamente a cadeia de chamadas que levou ao desastre. O stack trace é construído a partir das informações mantidas pelo runtime, que incluem os frames da stack e os metadados de chamadas. Não é super detalhado como um debugger de verdade, mas serve pra você olhar com vergonha e dizer: “é, fui eu mesmo que escrevi essa aberração.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Como o Go sabe que precisa crescer o stack?
&lt;/h3&gt;

&lt;p&gt;Isso é onde o Go fica metido a esperto. Toda vez que uma função é chamada, o runtime precisa garantir que haja espaço suficiente no stack da goroutine para que a função execute. Isso inclui espaço para variáveis locais, argumentos e o que mais for necessário dentro daquele frame. Só que, lembra? O stack começa ridiculamente pequeno. Pra resolver isso, o compilador do Go insere automaticamente um código de verificação no início de cada função. Isso é conhecido como o famoso &lt;strong&gt;stack split check&lt;/strong&gt;. Traduzindo do Goês para o Idioma dos Mortais: é uma checagem que pergunta "tenho espaço suficiente pra rodar essa função aqui ou não?"&lt;/p&gt;

&lt;p&gt;Se a resposta for “sim”, maravilha. A função executa normalmente e ninguém sofre. Se a resposta for “não”, o Go chama uma função especial, &lt;code&gt;newstack()&lt;/code&gt;, que realiza o crescimento da pilha. É uma função interna, mas não é chamada como uma função “normal”. O código gerado pelo compilador faz uma chamada especial a essa função em assembly.&lt;/p&gt;

&lt;p&gt;Agora vem a parte legal (ou trágica, depende de quanto você gosta de ver seu programa fazendo cópia de memória no meio da execução): essa função &lt;code&gt;newstack()&lt;/code&gt; cria uma nova área de memória maior, &lt;strong&gt;copia todo o conteúdo da stack atual pra lá&lt;/strong&gt;, ajusta os ponteiros, e depois continua a execução como se nada tivesse acontecido. É como trocar o chão enquanto você ainda está andando — e torcer pra ninguém perceber.&lt;/p&gt;

&lt;p&gt;Isso funciona porque o Go controla todo o ambiente da goroutine. Nenhum ponteiro externo aponta diretamente pro stack (pelo menos, não deveria — o compilador e o &lt;strong&gt;garbage collector&lt;/strong&gt; trabalham pra evitar isso), então é seguro mover tudo sem causar um incêndio na memória. Por isso, o Go consegue crescer stacks conforme a necessidade sem causar overhead constante. Você paga o preço da realocação só quando realmente precisa. Isso também explica por que goroutines são tão leves: elas só consomem o que for necessário, e vão crescendo conforme o uso real, em vez de começar como um trambolho de 1 MB cada uma.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heap x Stack
&lt;/h3&gt;

&lt;p&gt;O gerenciamento de memória é dividido entre duas regiões principais: a &lt;strong&gt;stack&lt;/strong&gt; e o &lt;strong&gt;heap&lt;/strong&gt;. A stack é usada para armazenar variáveis locais de curta duração, parâmetros de função e tudo que pode ser descartado rapidamente assim que a função termina. Cada vez que uma função é chamada, o Go aloca um pequeno espaço na stack — chamado de &lt;em&gt;stack frame&lt;/em&gt; — onde ele guarda todas as informações relacionadas àquela execução. Esse espaço inclui os argumentos passados, variáveis locais e um ponteiro que indica onde a execução deve continuar quando a função retornar. Quando a função termina, esse frame é simplesmente removido da stack, liberando o espaço de forma rápida e eficiente.&lt;/p&gt;

&lt;p&gt;A stack no Go é única para cada &lt;strong&gt;goroutine&lt;/strong&gt;. Isso significa que toda goroutine que você cria tem sua própria pilha, independente das outras. Essas stacks são minúsculas por padrão (começam com apenas 2KB), mas têm a habilidade mágica de crescer dinamicamente se for necessário, através de um processo chamado &lt;strong&gt;stack splitting&lt;/strong&gt;. Isso permite que milhares ou até milhões de goroutines coexistam sem fritar sua máquina, o que é uma façanha que linguagens mais ortodoxas como Java só conseguem com oração.&lt;/p&gt;

&lt;p&gt;Já o heap é onde moram as variáveis que o compilador acha que vão viver por mais tempo, ou que precisam ser acessadas fora do escopo da função onde nasceram. O Go decide o destino de cada variável usando uma técnica chamada &lt;strong&gt;escape analysis&lt;/strong&gt;, que basicamente responde à pergunta: “essa variável vai vazar do contexto onde foi criada?” Se sim, ela vai direto pro heap. Isso acontece, por exemplo, quando você retorna o ponteiro de uma variável local, ou quando uma função armazena referências em uma estrutura que vai sobreviver à execução atual.&lt;/p&gt;

&lt;p&gt;As funções em si — ou seja, o código delas — não vão pra heap nem pra stack. Elas são armazenadas numa região separada da memória chamada &lt;strong&gt;text segment&lt;/strong&gt;, que é onde vive o binário compilado. Quando você chama uma função, o Go basicamente empilha um novo frame na stack da goroutine ativa e pula praquele endereço no text segment. Quando ela termina, ele volta pro ponto anterior e a vida segue.&lt;/p&gt;

&lt;p&gt;Esse modelo é extremamente eficiente. Usar a stack como área principal de alocação temporária reduz a carga do &lt;strong&gt;garbage collector&lt;/strong&gt;, que só precisa se preocupar com as variáveis no heap. Mas como nem tudo é perfeito, você ainda precisa estar ciente de quando suas variáveis vão parar lá, pra evitar alocações desnecessárias e gargalos invisíveis de desempenho. E sim, isso significa que &lt;em&gt;às vezes&lt;/em&gt; vale a pena olhar os relatórios de escape do compilador — por mais deprimentes que eles sejam.&lt;/p&gt;

&lt;h4&gt;
  
  
  As funções em Go
&lt;/h4&gt;

&lt;p&gt;Já as funções, diferentemente das variáveis, têm um destino completamente diferente. O código da função em si é armazenado em uma região especial da memória chamada text segment, também conhecida como code segment. Essa parte da memória é onde vive o código executável do programa. Quando você chama uma função, a CPU salta para o endereço correspondente no text segment, e as variáveis que a função usa são empilhadas na stack ou alocadas no heap, dependendo do que o compilador decidir.&lt;/p&gt;

&lt;p&gt;Essa arquitetura torna o Go extremamente eficiente para lidar com concorrência e múltiplas goroutines. Cada goroutine tem sua própria stack que começa pequena e pode crescer de forma dinâmica, permitindo que você tenha milhares de tarefas leves rodando ao mesmo tempo. Ao mesmo tempo, a separação clara entre stack, heap e text segment ajuda o compilador a otimizar a execução e o uso de memória de forma bastante eficaz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alocação na Stack
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="n"&gt;printValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;printValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse exemplo, a variável &lt;code&gt;x&lt;/code&gt; é um valor simples. Ele é passado por &lt;strong&gt;valor&lt;/strong&gt; para a função &lt;code&gt;printValue&lt;/code&gt;, e não é retornado, nem usado fora do &lt;code&gt;main&lt;/code&gt;. Como nada escapa, o compilador é feliz e aloca tudo isso na &lt;strong&gt;stack&lt;/strong&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Alocação na Heap
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora temos o mesmo valor, &lt;code&gt;42&lt;/code&gt;, e o mesmo tipo de variável. Mas olha o crime aqui: estamos &lt;strong&gt;retornando um ponteiro para uma variável local&lt;/strong&gt;. A stack de &lt;code&gt;createPointer&lt;/code&gt; vai sumir assim que a função retornar, então se &lt;code&gt;x&lt;/code&gt; estivesse ali, estaríamos acessando memória morta. O compilador detecta isso e joga &lt;code&gt;x&lt;/code&gt; direto no &lt;strong&gt;heap&lt;/strong&gt;, garantindo que ele sobreviva após o retorno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consideração final
&lt;/h2&gt;

&lt;p&gt;Se você chegou até aqui, parabéns. Você agora sabe mais sobre a &lt;em&gt;call stack&lt;/em&gt; do Go do que 90% das pessoas que colocam "Golang ninja" no LinkedIn. E se em algum momento achou que a stack era só um lugarzinho onde variáveis iam tirar uma soneca, sinto informar que ela é mais parecida com uma república de estudantes: todo mundo entra, ninguém limpa nada, e eventualmente alguém causa um &lt;em&gt;panic&lt;/em&gt; e tudo desmorona.&lt;/p&gt;

&lt;p&gt;Mas agora você sabe o que está acontecendo por trás das cortinas — ou melhor, das &lt;em&gt;goroutines&lt;/em&gt; — e pode finalmente olhar para um &lt;code&gt;stack trace&lt;/code&gt; sem chorar (muito). E lembre-se: se algo deu errado, provavelmente foi porque você retornou o ponteiro errado…&lt;/p&gt;

&lt;h1&gt;
  
  
  beba_água.
&lt;/h1&gt;

</description>
      <category>go</category>
      <category>goroutines</category>
      <category>stack</category>
      <category>learning</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jean Grijp</dc:creator>
      <pubDate>Mon, 06 Jan 2025 02:58:49 +0000</pubDate>
      <link>https://dev.to/jeangrijp/-3na6</link>
      <guid>https://dev.to/jeangrijp/-3na6</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/wesley_bispo_1703" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2647468%2F43b3dd9e-a3a2-4cb9-aef0-0a314443a67e.jpg" alt="wesley_bispo_1703"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/wesley_bispo_1703/como-sao-feitas-as-alocacoes-2dl4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Entendendo Heap e Stack no Gerenciamento de Memória&lt;/h2&gt;
      &lt;h3&gt;Wesley Bispo ・ Jan 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#softwareengineering&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#learning&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#performance&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
  </channel>
</rss>
