<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Testes | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/testes/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:49 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/testes/index.xml" rel="self" type="application/rss+xml"/><item><title>Testes de Integração em MonoRepo | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/20/testes-de-integracao-em-monorepo-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/20/testes-de-integracao-em-monorepo-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Fri, 20 Feb 2026 12:48:45 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 8.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Testes unitários são a zona de conforto de todo desenvolvedor. Você mocka tudo, isola tudo, roda em milissegundos, e o verde no terminal te dá uma falsa sensação de segurança. Até o dia que você deploya e descobre que o serviço A grava num formato que o serviço B não consegue ler. Ou que o RSS do Hacker News mudou de Atom pra RSS e seu parser explodiu. Ou que a Yahoo Finance começou a retornar 429 depois do terceiro ticker porque o rate limit que funcionava em teste não existe na vida real.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 8.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>&ndash;</p>
<p>Testes unitários são a zona de conforto de todo desenvolvedor. Você mocka tudo, isola tudo, roda em milissegundos, e o verde no terminal te dá uma falsa sensação de segurança. Até o dia que você deploya e descobre que o serviço A grava num formato que o serviço B não consegue ler. Ou que o RSS do Hacker News mudou de Atom pra RSS e seu parser explodiu. Ou que a Yahoo Finance começou a retornar 429 depois do terceiro ticker porque o rate limit que funcionava em teste não existe na vida real.</p>
<p>Testes unitários provam que as peças funcionam isoladas. Testes de integração provam que o <strong>sistema</strong> funciona. E num projeto com múltiplas aplicações compartilhando dados via filesystem, a diferença entre os dois é a diferença entre &ldquo;o código compila&rdquo; e &ldquo;a newsletter chega na inbox&rdquo;.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-22-50.jpg" alt="unit tests"  loading="lazy" /></p>
<h2>O Problema: Três Apps, Um Filesystem, Zero Garantias<span class="hx:absolute hx:-mt-20" id="o-problema-três-apps-um-filesystem-zero-garantias"></span>
    <a href="#o-problema-tr%c3%aas-apps-um-filesystem-zero-garantias" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O projeto tem uma arquitetura incomum: três aplicações Rails/Hugo num monorepo, integradas via um diretório <code>content/</code> compartilhado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>akitando-news/
├── marvin-bot/    # Gera conteúdo (Discord bot, AI, scraping)
├── newsletter/    # Monta e envia a newsletter
├── blog/          # Hugo &#43; Hextra → Netlify
├── podcast-tts/   # Servidor TTS (Python/FastAPI)
└── content/       # Markdown &#43; YAML, a cola entre tudo</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O marvin-bot escreve Markdown com frontmatter YAML em <code>content/anime/</code>, <code>content/hacker_news/</code>, etc. O newsletter lê esses arquivos pra montar a newsletter. O blog serve o mesmo conteúdo como páginas estáticas. O podcast lê a newsletter montada pra gerar o script de diálogo.</p>
<p>O contrato entre esses sistemas é <strong>implícito</strong>. Não tem schema. Não tem API versionada. Não tem protobuf. O contrato é: &ldquo;o arquivo Markdown tem esse frontmatter, com esses campos, nessa estrutura&rdquo;. Se o marvin-bot muda o formato de um campo — digamos, muda <code>image</code> de string pra array — o newsletter quebra silenciosamente. Nenhum teste unitário de nenhum dos dois apps detecta isso.</p>
<p>Essa é a falha fundamental de testar sistemas distribuídos com testes unitários: <strong>você está testando cada lado do contrato separadamente, mas nunca o contrato em si</strong>.</p>
<h2>Camada 1: Testes Unitários com Isolamento Paranoico<span class="hx:absolute hx:-mt-20" id="camada-1-testes-unitários-com-isolamento-paranoico"></span>
    <a href="#camada-1-testes-unit%c3%a1rios-com-isolamento-paranoico" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de falar de integração, preciso explicar o que os testes unitários fazem nesse projeto — porque sem essa base, os testes de integração não teriam onde pisar.</p>
<p>Os 977 testes do marvin-bot rodam em paralelo usando todos os cores da máquina. Cada worker de teste tem seu próprio diretório:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">parallelize</span><span class="p">(</span><span class="ss">workers</span><span class="p">:</span> <span class="ss">:number_of_processors</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">parallelize_setup</span> <span class="k">do</span> <span class="o">|</span><span class="n">worker</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">content_dir</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;tmp&#34;</span><span class="p">,</span> <span class="s2">&#34;test_content_w</span><span class="si">#{</span><span class="n">worker</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">to_s</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas worker-level não é suficiente. Dentro de cada worker, múltiplos testes rodam sequencialmente — e se um teste deixa um arquivo em <code>content/stories/</code>, o próximo teste pode encontrar dados espúrios. A solução: <strong>um diretório único por teste</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">setup</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@base_content_dir</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">content_dir</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@test_content_dir</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="vi">@base_content_dir</span><span class="p">,</span> <span class="no">SecureRandom</span><span class="o">.</span><span class="n">hex</span><span class="p">(</span><span class="mi">8</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">content_dir</span> <span class="o">=</span> <span class="vi">@test_content_dir</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="no">FileUtils</span><span class="o">.</span><span class="n">mkdir_p</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="vi">@test_content_dir</span><span class="p">,</span> <span class="s2">&#34;stories&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="no">FileUtils</span><span class="o">.</span><span class="n">mkdir_p</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="vi">@test_content_dir</span><span class="p">,</span> <span class="s2">&#34;newsletters&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="no">FileUtils</span><span class="o">.</span><span class="n">mkdir_p</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="vi">@test_content_dir</span><span class="p">,</span> <span class="s2">&#34;images&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">teardown</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">FileUtils</span><span class="o">.</span><span class="n">rm_rf</span><span class="p">(</span><span class="vi">@test_content_dir</span><span class="p">)</span> <span class="k">if</span> <span class="vi">@test_content_dir</span>
</span></span><span class="line"><span class="cl">  <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">content_dir</span> <span class="o">=</span> <span class="vi">@base_content_dir</span> <span class="k">if</span> <span class="vi">@base_content_dir</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Presta atenção na linha do teardown que restaura <code>@base_content_dir</code>. Sem ela, o próximo teste criaria seu diretório <strong>dentro</strong> do diretório do teste anterior — <code>tmp/test_content_w3/abc123/def456/</code> — e em 50 testes você teria um caminho com 50 níveis de profundidade. Aprendi isso perdendo uma tarde inteira debugando por que os testes ficavam cada vez mais lentos ao longo da suite.</p>
<p>Esse padrão — worker-level base + per-test subdirectory + restore no teardown — é idêntico nos dois apps. E é o que permite rodar 977 testes em 7 segundos com 32 workers paralelos.</p>
<p>A regra de ouro: <strong>o <code>content_dir</code> de teste NUNCA pode apontar pro <code>../content</code> real</strong>. Se apontar, o <code>rm_rf</code> do teardown apaga os prompts de produção. Não é hipotético — aconteceu durante o desenvolvimento.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-24-08.jpg" alt="unit tests newsletter"  loading="lazy" /></p>
<h2>Camada 2: O Ambiente de Integração<span class="hx:absolute hx:-mt-20" id="camada-2-o-ambiente-de-integração"></span>
    <a href="#camada-2-o-ambiente-de-integra%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Testes unitários mockam tudo: APIs externas, AI, Discord, SES. Isso é correto — você não quer gastar $2 em tokens do GPT-5.2 toda vez que roda <code>bin/rails test</code>. Mas significa que os testes nunca exercitam o pipeline real.</p>
<p>O projeto tem um terceiro environment além de development e test: <strong>integration</strong>. Não é test. Não é staging. É um modo específico pra rodar o pipeline completo com dados reais, usando APIs reais, mas sem mandar emails de verdade:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/environments/integration.rb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Jobs rodam inline (perform_later vira perform_now)</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">active_job</span><span class="o">.</span><span class="n">queue_adapter</span> <span class="o">=</span> <span class="ss">:inline</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Emails salvos em arquivo ao invés de enviados via SES</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_mailer</span><span class="o">.</span><span class="n">delivery_method</span> <span class="o">=</span> <span class="ss">:file</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_mailer</span><span class="o">.</span><span class="n">file_settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">location</span><span class="p">:</span> <span class="no">Rails</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;tmp/integration_emails&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>queue_adapter = :inline</code> é crucial. Em produção, <code>perform_later</code> enfileira o job no SolidQueue e um worker separado executa. Em integração, executa <strong>na hora, no mesmo processo</strong>. Isso transforma o pipeline assíncrono de produção num pipeline síncrono que você pode rodar, observar, e debugar sequencialmente.</p>
<p>O SES é substituído por escrita em disco — cada email vira um arquivo HTML em <code>tmp/integration_emails/</code>. Depois de rodar o pipeline, você abre esses HTMLs no browser pra validar visualmente que o email renderiza correto.</p>
<h2>Camada 3: DevCache — LLM Não É Idempotente<span class="hx:absolute hx:-mt-20" id="camada-3-devcache--llm-não-é-idempotente"></span>
    <a href="#camada-3-devcache--llm-n%c3%a3o-%c3%a9-idempotente" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está um problema que quem trabalha com IA em produção conhece: chamar o GPT-5.2 com o mesmo prompt duas vezes <strong>não retorna a mesma resposta</strong>. E cada chamada custa dinheiro. Se você está iterando no pipeline de integração — rodando, ajustando um prompt, rodando de novo — gastar $3 por iteração fica caro rápido.</p>
<p>A solução é o <code>DevCache</code>: um cache file-based que só ativa no env de integração:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">DevCache</span>
</span></span><span class="line"><span class="cl">  <span class="no">CACHE_TTL</span> <span class="o">=</span> <span class="mi">1</span><span class="o">.</span><span class="n">day</span>
</span></span><span class="line"><span class="cl">  <span class="no">CACHE_ENVS</span> <span class="o">=</span> <span class="sx">%w[integration]</span><span class="o">.</span><span class="n">freeze</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">yield</span> <span class="k">unless</span> <span class="n">enabled?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">path</span> <span class="o">=</span> <span class="n">cache_path</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">path</span><span class="o">.</span><span class="n">exist?</span> <span class="o">&amp;&amp;</span> <span class="no">File</span><span class="o">.</span><span class="n">mtime</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">&gt;</span> <span class="no">CACHE_TTL</span><span class="o">.</span><span class="n">ago</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">read</span><span class="p">,</span> <span class="ss">symbolize_names</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">path</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="no">JSON</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em produção e em test, <code>DevCache.fetch</code> executa o bloco direto. Em integração, salva o resultado como JSON e retorna o cached na próxima execução. TTL de 1 dia — suficiente pra iterar no mesmo dia sem recalcular, curto o bastante pra não servir dados velhos.</p>
<p>O código de geração usa assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">summary</span> <span class="o">=</span> <span class="no">DevCache</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s2">&#34;summarize&#34;</span><span class="p">,</span> <span class="n">url_key</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">AiChat</span><span class="o">.</span><span class="n">summarize</span><span class="p">(</span><span class="n">article</span><span class="o">[</span><span class="ss">:content</span><span class="o">]</span><span class="p">,</span> <span class="ss">source_url</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Primeira execução: chama o GPT-5.2, paga $0.02, salva em <code>tmp/dev_cache/summarize/{md5}.json</code>. Segunda execução: lê do cache, custo zero. O <code>FORCE=1</code> busta o cache quando você quer forçar regeneração — mas como flag explícita, não como default.</p>
<p>O DevCache existe em ambos os apps (marvin-bot e newsletter) com implementação idêntica. Quando roda <code>integration:clean</code>, ambos os caches são limpos.</p>
<p>Ponto: <strong>cache de LLM em ambiente de desenvolvimento não é otimização — é requisito</strong>. Sem ele, iterar num prompt que precisa de 8 chamadas de API fica financeiramente inviável.</p>
<h2>Camada 4: Dados Reais via Rsync<span class="hx:absolute hx:-mt-20" id="camada-4-dados-reais-via-rsync"></span>
    <a href="#camada-4-dados-reais-via-rsync" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui é onde a maioria dos projetos para. Eles têm testes unitários, talvez um ambiente de staging, e mandam pro ar. O problema: staging usa dados de teste. Dados de teste são <strong>limpos demais</strong>. Não têm os edge cases que dados reais têm:</p>
<ul>
<li>Artigos com títulos em japonês (anime ranking)</li>
<li>URLs que redirecionam 3 vezes antes de chegar no destino</li>
<li>Imagens que retornam 200 com Content-Type <code>text/html</code> (CloudFlare error page)</li>
<li>Newsletters anteriores com formatos de frontmatter ligeiramente diferentes</li>
<li>Stories com <code>score: null</code> porque foram criadas antes de o campo existir</li>
</ul>
<p>A solução: <strong>rsync de produção</strong>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Limpa conteúdo gerado (preserva stories e imagens de produção)</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> marvin-bot <span class="o">&amp;&amp;</span> bin/rails integration:clean
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Sincroniza content/ de produção pra local</span>
</span></span><span class="line"><span class="cl">rsync -avz --delete production:/path/to/content/ ../content/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Roda o pipeline completo com dados reais</span>
</span></span><span class="line"><span class="cl"><span class="nv">RAILS_ENV</span><span class="o">=</span>integration bin/rails integration:pipeline</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>integration:clean</code> é cirúrgico: remove diretórios gerados (<code>anime/</code>, <code>hacker_news/</code>, <code>newsletters/</code>, <code>podcasts/</code>) mas preserva <code>stories/</code> e <code>images/</code> — que são dados submetidos pelo usuário via Discord. Depois do rsync, o diretório local é uma cópia exata da produção.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-25-37.jpg" alt="cleanup"  loading="lazy" /></p>
<p>O pipeline então gera todas as seções a partir desses dados reais. Se um parser quebra com um título em coreano, você descobre aqui — não em produção às 17h de domingo quando os 8 jobs rodam em paralelo.</p>
<h2>Camada 5: O Pipeline Completo<span class="hx:absolute hx:-mt-20" id="camada-5-o-pipeline-completo"></span>
    <a href="#camada-5-o-pipeline-completo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O coração do sistema de integração é o <code>integration:pipeline</code>. Ele simula a semana inteira de produção em uma execução:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">RAILS_ENV</span><span class="o">=</span>integration bin/rails integration:pipeline</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O pipeline roda em ondas, respeitando dependências:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Wave 1 (paralelo, 8 jobs):
  Book, Holidays, History, Geek History,
  Anime, Hacker News, YouTube, World Events

Wave 2 (depende de World Events):
  Market Recap

Wave 3 (depende de tudo):
  Closing Remarks

Preflight → Newsletter Assembly → Podcast</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-26-13.jpg" alt="waves"  loading="lazy" /></p>
<p>Cada wave roda seus jobs em threads paralelas, exatamente como a produção faz com SolidQueue. A execução paralela não é um detalhe de implementação — é uma feature de teste. Se dois jobs tentam escrever no mesmo arquivo, ou se um job lê um arquivo que outro está no meio de escrever, o bug aparece aqui.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">threads</span> <span class="o">=</span> <span class="n">to_run</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="nb">name</span><span class="p">,</span> <span class="n">klass</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="no">Thread</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection_pool</span><span class="o">.</span><span class="n">with_connection</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">run_single_job</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">klass</span><span class="p">,</span> <span class="n">target_date</span><span class="p">,</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="n">threads</span><span class="o">.</span><span class="n">each</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:join</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>with_connection</code> é necessário porque cada thread precisa de sua própria conexão SQLite — sem ele, conexões vazam e o pool esgota.</p>
<p>Ao final, o pipeline imprime uma tabela de status e um resumo de billing:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-34-56.jpg" alt="billing"  loading="lazy" /></p>
<p>Esse billing summary não é vaidade — é controle de custos. Se um prompt refatorado aumenta o output tokens em 3x, você vê aqui antes de ir pra produção e gastar 10x num domingo com 200 stories.</p>
<h2>Camada 6: Preflight — Validação Estrutural Automatizada<span class="hx:absolute hx:-mt-20" id="camada-6-preflight--validação-estrutural-automatizada"></span>
    <a href="#camada-6-preflight--valida%c3%a7%c3%a3o-estrutural-automatizada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de gerar todas as seções, o pipeline roda o <code>ContentPreflight</code>. Ele não valida o <em>conteúdo</em> — valida a <em>estrutura</em>. Cada seção é checada:</p>
<ul>
<li><strong>Arquivo existe?</strong> Se <code>content/anime/2026-02-16.md</code> não existe, algo falhou.</li>
<li><strong>Item count mínimo?</strong> A seção de anime precisa de pelo menos 3 itens. Se tem 1, o gerador teve um problema.</li>
<li><strong>Markers obrigatórios?</strong> O Markdown precisa ter <code>[COMMENTARY]</code> e <code>[AKITA]</code> — sem eles, a newsletter assembly não sabe onde cortar.</li>
<li><strong>Dados específicos?</strong> Market recap precisa de pelo menos 5 ticker rows.</li>
</ul>
<p>O resultado é um status por seção: <code>pass</code>, <code>degraded</code>, <code>fail</code>, ou <code>skip</code>. O pipeline usa isso pra decidir se a newsletter pode ser montada automaticamente ou se precisa de intervenção humana:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-36-19.jpg" alt="preflight"  loading="lazy" /></p>
<p>Em produção, esse resultado vai pro Discord como um embed. No pipeline de integração, é printado no terminal. A lógica é a mesma — o que muda é o canal de notificação.</p>
<h2>Camada 7: Cross-App — Newsletter Assembly<span class="hx:absolute hx:-mt-20" id="camada-7-cross-app--newsletter-assembly"></span>
    <a href="#camada-7-cross-app--newsletter-assembly" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está onde o monorepo paga dividendos. O marvin-bot precisa invocar o newsletter pra montar a newsletter final. Em produção, são servidores diferentes. No pipeline de integração, é um <code>system()</code> call:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">env_vars</span> <span class="o">=</span> <span class="s2">&#34;RAILS_ENV=integration CONTENT_DIR=</span><span class="si">#{</span><span class="no">Shellwords</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">content_dir</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">cmd</span> <span class="o">=</span> <span class="s2">&#34;cd </span><span class="si">#{</span><span class="n">newsletter_dir</span><span class="si">}</span><span class="s2"> &amp;&amp; </span><span class="si">#{</span><span class="n">env_vars</span><span class="si">}</span><span class="s2"> bin/rails integration:generate_all&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-38-17.jpg" alt="assembly"  loading="lazy" /></p>
<p>O newsletter lê os arquivos que o marvin-bot acabou de gerar em <code>content/</code>, monta a newsletter, e salva o resultado em <code>content/newsletters/</code>. O CONTENT_DIR é passado explicitamente — os dois apps apontam pro mesmo diretório.</p>
<p>Se o formato do frontmatter mudou e o newsletter não consegue parsear, o erro aparece aqui. Se o <code>SectionParser</code> do newsletter divergiu do <code>SectionParser</code> do marvin-bot (são cópias idênticas, mas manuais), aqui é onde você descobre.</p>
<p>Depois da newsletter, o podcast: o pipeline invoca <code>podcast:integration</code>, que gera o script de diálogo a partir da newsletter montada, sintetiza o áudio via TTS, e monta o MP3 final. É o pipeline mais longo — de newsletter Markdown a MP3 pronto pro Spotify, passando por LLM de duas passadas e síntese de voz por turn.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-47-52.jpg" alt="tts"  loading="lazy" /></p>
<h2>O Valor de Testar com Dados de Produção<span class="hx:absolute hx:-mt-20" id="o-valor-de-testar-com-dados-de-produção"></span>
    <a href="#o-valor-de-testar-com-dados-de-produ%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vou dar exemplos concretos de bugs que só apareceram com dados reais:</p>
<p><strong>1. Títulos de anime em japonês quebravam o slugify.</strong>
O <code>ContentWriter.slugify</code> usava <code>parameterize</code> do Rails, que strip caracteres non-ASCII. Título: &ldquo;進撃の巨人 Season 4&rdquo; → slug: &ldquo;season-4&rdquo;. Dois animes com subtítulo &ldquo;Season 4&rdquo; collidiam no filesystem. Nunca apareceu em teste porque os fixtures usavam títulos em inglês.</p>
<p><strong>2. O parser de HackerNews assumia RSS, mas o feed mudou pra Atom.</strong>
<code>rss/channel/item</code> virou <code>feed/entry</code>. Testes unitários mockavam o XML. O parser em si nunca foi testado contra o feed real. Na integração com rsync do feed real, o parser retornou zero itens — detectado pelo preflight como <code>fail</code>.</p>
<p><strong>3. Yahoo Finance retornava 429 depois do 5º ticker.</strong>
O rate limiting não existia em teste (HTTP era mockado). Na integração real, os primeiros 5 tickers passavam, depois tudo falhava. A solução — sleep entre requests — nunca seria implementada sem dados reais revelando o problema.</p>
<p><strong>4. Imagens com URL válida mas Content-Type errado.</strong>
Uma CDN retornava 200 para URLs de imagem, mas com body HTML (Cloudflare error page). O <code>UrlValidator.reachable?</code> retornava <code>true</code>. O <code>ImageProcessor.download_image</code> baixava o HTML e tentava redimensionar. O mini_magick crashava com um erro obscuro. O fix: validar Content-Type no HEAD response — mas esse cenário nunca apareceria com URLs mockadas.</p>
<p><strong>5. O newsletter assembly falhava com stories que tinham <code>score: null</code>.</strong>
Stories antigas, criadas antes do campo <code>score</code> ter um default, tinham <code>nil</code> no frontmatter. O assembly tentava fazer <code>sort_by { |s| s[:frontmatter][&quot;score&quot;] }</code> — <code>nil &lt;=&gt; &quot;high&quot;</code> levanta <code>ArgumentError</code>. Só apareceu quando rsynquei dados de produção que tinham 3 meses de stories acumuladas.</p>
<p>Nenhum desses bugs seria pego por teste unitário. Todos foram pegos pelo pipeline de integração com dados reais.</p>
<h2>Dicas Práticas<span class="hx:absolute hx:-mt-20" id="dicas-práticas"></span>
    <a href="#dicas-pr%c3%a1ticas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>1. Separe Custo de Fidelidade<span class="hx:absolute hx:-mt-20" id="1-separe-custo-de-fidelidade"></span>
    <a href="#1-separe-custo-de-fidelidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O pipeline de integração completo custa ~$0.40 em API calls. Não é muito, mas se você roda 10 vezes num dia iterando, são $4. O DevCache resolve isso: primeira execução paga, as seguintes são grátis.</p>
<p>Mas tem cenários onde você <strong>quer</strong> pagar de novo — quando muda um prompt e precisa ver o resultado real. O <code>FORCE=1</code> busta o cache seletivamente. E o <code>SKIP_EXISTING=1</code> pula seções que já foram geradas, economizando tempo quando você só quer re-rodar uma seção específica.</p>
<h3>2. O Integration Environment Não É Staging<span class="hx:absolute hx:-mt-20" id="2-o-integration-environment-não-é-staging"></span>
    <a href="#2-o-integration-environment-n%c3%a3o-%c3%a9-staging" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Staging é um servidor que roda 24/7 simulando produção. O integration environment é um <strong>modo de execução local</strong> que você liga quando precisa e desliga quando terminou. Não precisa de infraestrutura dedicada. Não precisa de database separado. É o mesmo código, na mesma máquina, com flags diferentes.</p>
<p>A diferença é filosófica: staging testa &ldquo;o sistema funciona no servidor de staging?&rdquo;. Integration testa &ldquo;o sistema funciona <strong>com esses dados específicos</strong>?&rdquo;. O primeiro é environment-dependent. O segundo é data-dependent. E bugs de dados são mais comuns que bugs de environment.</p>
<h3>3. Clean Antes de Rsync, Não Depois<span class="hx:absolute hx:-mt-20" id="3-clean-antes-de-rsync-não-depois"></span>
    <a href="#3-clean-antes-de-rsync-n%c3%a3o-depois" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A ordem importa: limpe o conteúdo gerado <strong>antes</strong> de sincronizar de produção. Se fizer o contrário — rsync primeiro, clean depois — você perde os dados de produção que acabou de baixar.</p>
<p>O <code>integration:clean</code> é cirúrgico por design. Remove anime/, hacker_news/, newsletters/ (gerados), mas preserva stories/ e images/ (produção). Isso permite um workflow iterativo: rsync uma vez, roda o pipeline, ajusta um prompt, roda de novo — sem re-baixar 500MB de content.</p>
<h3>4. Preflight É Seu Smoke Test<span class="hx:absolute hx:-mt-20" id="4-preflight-é-seu-smoke-test"></span>
    <a href="#4-preflight-%c3%a9-seu-smoke-test" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Não confie que &ldquo;rodou sem exceção = está correto&rdquo;. Um job de geração pode rodar, produzir um Markdown com 2 itens ao invés de 10, e retornar sucesso. O preflight detecta isso verificando contagens mínimas, markers obrigatórios, e estrutura esperada.</p>
<p>Na prática, todo bug de LLM que encontrei foi detectado pelo preflight: respostas truncadas, formato errado, seções faltando. O GPT-5.2 é bom, mas não é determinístico — e quando erra, erra de formas criativas que nenhum schema validation pegaria.</p>
<h3>5. Billing Summary Não É Opcional<span class="hx:absolute hx:-mt-20" id="5-billing-summary-não-é-opcional"></span>
    <a href="#5-billing-summary-n%c3%a3o-%c3%a9-opcional" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você está usando LLM em produção, <strong>todo</strong> pipeline de teste precisa reportar custo. Não por contabilidade — por engenharia. Um prompt que funciona mas custa 3x mais que o anterior é um bug de performance. Tokens de input e output por provider, custo por job, custo total — tudo visível a cada execução.</p>
<h3>6. Monorepo Facilita, Mas Não Resolve<span class="hx:absolute hx:-mt-20" id="6-monorepo-facilita-mas-não-resolve"></span>
    <a href="#6-monorepo-facilita-mas-n%c3%a3o-resolve" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ter os três apps no mesmo repositório facilita: um <code>git bisect</code> cobre mudanças em ambos os lados do contrato. Um PR que muda o formato do frontmatter no marvin-bot <strong>pode</strong> também atualizar o parser no newsletter — no mesmo commit.</p>
<p>Mas &ldquo;pode&rdquo; não é &ldquo;garante&rdquo;. O contrato entre os apps é file-based e implícito. Não tem tipo compartilhado, não tem interface comum. O <code>SectionParser</code> é copiado manualmente entre os dois apps. Se alguém edita um e esquece do outro, divergem. O teste de integração é a <strong>única</strong> verificação de que os dois lados concordam.</p>
<h3>7. Preview Antes de Enviar<span class="hx:absolute hx:-mt-20" id="7-preview-antes-de-enviar"></span>
    <a href="#7-preview-antes-de-enviar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O pipeline não termina na geração. O newsletter tem um <code>newsletter:preview</code> que renderiza o resultado final em HTML light e dark:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> newsletter
</span></span><span class="line"><span class="cl"><span class="nv">FILE</span><span class="o">=</span>../content/newsletters/2026-02-16.md bin/rails newsletter:preview
</span></span><span class="line"><span class="cl"><span class="c1"># → tmp/preview/newsletter_light.html</span>
</span></span><span class="line"><span class="cl"><span class="c1"># → tmp/preview/newsletter_dark.html</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Abrir esses HTMLs no browser é o teste de integração visual. O Markdown pode estar perfeito, o frontmatter correto, todos os campos presentes — mas a imagem de um anime aparece esticada porque o aspect ratio mudou. Ou o bloco de comentário do Marvin não tem contraste suficiente no dark mode. Esses bugs são visuais — nenhum teste automatizado pega.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_12-48-32.jpg" alt="final assembly"  loading="lazy" /></p>
<h3>8. O Pipeline de CI É Diferente do Pipeline de Integração<span class="hx:absolute hx:-mt-20" id="8-o-pipeline-de-ci-é-diferente-do-pipeline-de-integração"></span>
    <a href="#8-o-pipeline-de-ci-%c3%a9-diferente-do-pipeline-de-integra%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>São coisas diferentes. CI roda em todo commit:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bin/ci  <span class="c1"># rubocop + bundler-audit + brakeman + tests (~22s)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Integração roda quando você quer validar o pipeline end-to-end — antes de um deploy, depois de mudar prompts, ou depois de rsync de dados novos de produção. CI é rápido e barato. Integração é lento e custa dinheiro. Os dois são necessários.</p>
<h2>O Custo de Não Ter Integração<span class="hx:absolute hx:-mt-20" id="o-custo-de-não-ter-integração"></span>
    <a href="#o-custo-de-n%c3%a3o-ter-integra%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vou ser direto: se esse projeto não tivesse o pipeline de integração, eu teria enviado pelo menos 3 newsletters quebradas.</p>
<p>Uma com seção de anime faltando porque o parser de XML assumia RSS e o feed era Atom. Uma com todas as imagens de YouTube apontando pra 404 porque o padrão de URL de thumbnail mudou. E uma com o market recap mostrando tickers do mês passado porque o Yahoo Finance bloqueou as requests e o job falhou silenciosamente.</p>
<p>Testes unitários teriam passado em todos esses cenários. O CI teria dado verde. O deploy teria ido pro ar. E às 7 da manhã de segunda-feira, 300 pessoas teriam recebido uma newsletter com buracos.</p>
<p>O pipeline de integração é caro — em tempo de setup, em custo de API, em complexidade de manutenção. Mas é ordens de magnitude mais barato que enviar conteúdo quebrado pra assinantes reais. Especialmente quando você só tem uma chance por semana.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A hierarquia de testes nesse projeto é:</p>
<ol>
<li><strong>Testes unitários</strong> (1.330 nos dois apps, 7 segundos em paralelo, $0): prova que cada peça funciona isolada</li>
<li><strong>CI pipeline</strong> (rubocop + audit + brakeman + tests, ~22 segundos, $0): prova que o código está saudável</li>
<li><strong>Pipeline de integração</strong> (dados reais, APIs reais, ~3 minutos, ~$0.40): prova que o <strong>sistema</strong> funciona</li>
<li><strong>Preview visual</strong> (HTML no browser, manual, $0): prova que o resultado final é apresentável</li>
</ol>
<p>Cada camada pega um tipo diferente de bug. Nenhuma sozinha é suficiente. E a que mais surpreende — a que pega os bugs que você jurava que não existiam — é a terceira: dados reais, pipeline real, sem mocks.</p>
<p>A confiança que você tem no seu sistema é proporcional ao quão reais são os dados nos seus testes. Mocka tudo e você tem 100% de coverage e zero de confiança. Testa com dados de produção e você tem a verdade — por mais inconveniente que seja.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>testes</category><category>engenharia-de-software</category></item><item><title>[Akitando] #130 - Rant: Projetos, TESTES e Estimativa??? | Rated-R</title><link>https://akitaonrails.github.io/2022/10/25/akitando-130-rant-projetos-testes-e-estimativa-rated-r/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/10/25/akitando-130-rant-projetos-testes-e-estimativa-rated-r/</guid><pubDate>Tue, 25 Oct 2022 10:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/H_-7o_pLn1s"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente vou discutir um pouco sobre Programação Orientada a Testes e o que isso tem a ver com gerenciamento de projetos, equipes, e um pouco sobre como todo mundo ainda encara estimativas da maneira errada.&lt;/p&gt;
&lt;p&gt;Este video é pra você programador, gerente, cliente, empreendedor, que tem dificuldades de entregar projetos de qualidade. Vamos entender tudo que vocês estão fazendo de errado e porque dá errado.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/H_-7o_pLn1s"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente vou discutir um pouco sobre Programação Orientada a Testes e o que isso tem a ver com gerenciamento de projetos, equipes, e um pouco sobre como todo mundo ainda encara estimativas da maneira errada.</p>
<p>Este video é pra você programador, gerente, cliente, empreendedor, que tem dificuldades de entregar projetos de qualidade. Vamos entender tudo que vocês estão fazendo de errado e porque dá errado.</p>
<p>== Conteúdo</p>
<ul>
<li>00:00 - Intro</li>
<li>01:13 - CAP 01 - Suas Premissas estão Erradas - Esqueça Bullshit</li>
<li>05:33 - Programadores não são Pedreiros!</li>
<li>08:31 - CAP 02 - Provas de Conceito - Comecem a Fazer!</li>
<li>13:01 - CAP 03 - Programação Orientada a Testes - Comecem a Fazer!</li>
<li>17:53 - Demonstração de testes de Firefox</li>
<li>19:02 - Demonstração de testes de React</li>
<li>19:27 - Demonstração de testes de Tailwind</li>
<li>19:57 - Demonstração de testes de Django</li>
<li>20:28 - Demonstração de testes de Laravel</li>
<li>21:06 - Demonstração de testes de VSCode</li>
<li>22:14 - Demonstração de testes de Rust</li>
<li>24:02 - Demonstração de testes de Spring Boot</li>
<li>27:25 - Vocês não SABEM fazer testes!</li>
<li>28:45 - CAP 04 - Como a falta de Testes afeta Produtividade? A Raíz do Ágil</li>
<li>32:27 - Como é um fluxo de trabalho Ágil?</li>
<li>37:01 - CAP 05 - O que significa Estimar? Estimar não é Prever</li>
<li>47:27 - CAP 06 - Removendo Maus Elementos. Não acumule problemas.</li>
<li>50:07 - CAP 07 - Resumindo os Princípios: O Mínimo pra Começar</li>
<li>52:27 - Bloopers</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Vamos dar uma pausa na série de videos técnicos. Se não assistiu ainda, fiz uma minissérie falando sobre redes e logo em seguida começando a falar sobre Linux. Como foi muita coisa técnica, eu me entediei, então resolvi mudar a marcha hoje pra falar sobre gerenciamento de projetos, em particular finalmente quero discutir sobre o famigerado desenvolvimento orientado a testes. É um puta tema cabeludo que daria pra fazer um canal inteiro sobre, mas a idéia é discutir só alguns pontos básicos, pra iniciantes no assunto mesmo. Então vamos lá.</p>
<p>(&hellip;)</p>
<p>Se você já tem experiência, trabalha em grandes empresas e grandes projetos, certamente vai discordar de algumas coisas que vou falar hoje e não está errado. Processos de gerenciamento de projetos de software variam bastante e devem ser customizados especificamente pro seu tipo de empresa, pro seu estilo de trabalho em equipe. Não existe nenhuma metodologia que vai se encaixar magicamente em todos os lugares. Simplesmente não existe, é inútil tentar fazer isso. Eu falei sobre isso no video de Esqueça Metodologias Ágeis. Recomendo assistir esse também depois.</p>
<p>A expectativa é que existe algum conjunto de procedimentos que toda equipe consegue seguir pra manter os projetos estáveis e eficientes. Certamente deve existir algum tipo de PMI, Agile, CMMi, bla bla bla Alguma fórmula que o Spotify, o Netflix, a Microsoft, alguém inventou, que resolve todos os problemas. E não tem. Quanto mais cedo aceitarem isso, menos dor de cabeça vão ter. Se você se ver dizendo &ldquo;a gente segue o modelo Spotify&rdquo; ou segue qualquer modelo, certamente está fazendo errado. Veja no meu video do Guia Definitivo de Organizações.</p>
<p>Além disso, se você é um empreendedor não-técnico, certamente assistiu um monte de palestras motivacionais, bullshit de startups e tá fazendo um monte de coisas erradas. Eu fiz 3 videos pra vocês também, o de Empreendendo Errado com Software e o video em duas partes dos 10 Mitos sobre Tech Startups. Vou tentar não repetir o que já disse lá, então assistam depois também.</p>
<p>Vamos lá, no final do dia, você, como membro de uma equipe de desenvolvimento de software, precisa entregar alguma coisa que seu chefe ou seu cliente precisam no produto deles. Ou mesmo no seu próprio produto. Se for iniciante, é muito mais fácil integrar uma equipe que já existe, que tem membros experientes e processos já em andamento. Um iniciante sempre vai fazer errado da primeira vez, especialmente sozinho. Não tem como acertar de cara sem nunca ter feito, e isso não é um defeito, é natural. São muitas peças móveis que você nunca viu funcionando. É que nem eu dar um carro desmontado e mandar montar tudo e ainda funcionar direito no final. Impossível.</p>
<p>Vai parecer que é simples, só encaixar as peças e apertar os parafusos. Mas não é só encaixar. Precisa monitorar cada pedaço, testar, ver se não tem vazamentos, se tá tudo alinhado, balanceado, etc. Porque se sair só montando tudo sem testar nada, quando terminar e tentar ligar o carro, vai explodir. Ou, o mais provável, nem vai ligar, e ninguém vai saber porque. E agora que tá tudo montado, vai precisar desmontar tudo e ir testando. No mínimo vai ser uma grande perda de tempo.</p>
<p>Antes de mais nada, você precisa de pelo menos um membro da equipe um pouco mais experiente. Nem vou dizer sênior porque não quero ficar tentando definir hoje o que é junior ou sênior, mas alguém que já entregou no mínimo um projeto de sucesso. Porque essa pessoa precisa se responsabilizar pelas decisões técnicas. Decidir qual framework, bibliotecas, módulos, como as coisas vão ficar organizadas, como vai ser feito deployment. E lógico, todo mundo da equipe pode e deve contribuir com idéias, sugestões, mas alguém precisa bater o martelo e todo mundo seguir em frente.</p>
<p>Senão toda hora se perde tempo com discussão inútil. Um grupo cheio de opiniões que leva horas ou dias pra decidir as coisas é ineficiente e o resultado sempre vai ser uma droga. Aliás, se isso acontece, é um bom sinal que o tal sênior responsável é ruim. Nenhuma equipe respeita alguém que só fala mas não faz. Um bom sênior fala menos e mostra mais, ensina como faz na prática. E o melhor é errar cedo, ter evidências de porque a primeira decisão deu errado, e isso servir de input pra uma segunda decisão melhor. Do que ficar horas discutindo o melhor caminho, pra no final dar errado de qualquer jeito.</p>
<p>Isso talvez seja a coisa mais fundamental numa equipe. Errar antes e consertar rápido é infinitamente melhor do que horas e horas de discussão tentando chegar em consenso e bikeshedding. Ninguém tem certeza? Foda-se. Cara ou coroa. Faz um teste. Vê se funciona. Se falhar, agora veja porque falhou, conserta, e segue em frente. Essa é a essência do processo de lidar com o desconhecido. Todo mundo pensa que quando algo é desconhecido o certo é tentar planejar infinitamente até ter todos os passos minuciosamente detalhados. Mas tá errado. É desconhecido, qualquer planejamento que tentar fazer, quanto mais detalhado for, mais errado vai estar.</p>
<p>Acho que esse é o ponto principal da discussão de hoje. Quando você e sua equipe são inexperientes, das duas uma, ou vão andar super lento porque vão tentar planejar em excesso, discutir em excesso, ou vão ser kamikazes retardados que vão começar a fazer e só ir fazendo sem parar pra ver se estão indo na direção certa. Ambos estão errados. Não consigo entender porque o desespero não leva ao mais óbvio, que é errar cedo, consertar rápido e o mais importante: garantir que o que já foi feito e está certo, não quebre à toa, porque isso seria o equivalente a andar pra trás.</p>
<p>Não tem nenhum problema rascunhar o que precisa ser feito. Faça um checklist de funcionalidades. Faça post-its com cada tarefa. Faça uma planilha. Não importa. Todo mundo da equipe precisa ter uma noção geral do que precisa ser feito. Mas não tente detalhar em excesso. Só coloque detalhes no que estiver muito incerto, no momento em que precisa ser feito e não com meses de antecedência. Portanto, qualquer um que tenha estudado engenharia de software ou gerenciamento de projetos tradicional, e viu coisas como requisitos, casos de uso, ou até mesmo stories, pára. Você só precisa de uma lista, curta, com poucas palavras. Não é pra levar dias escrevendo, é pra levar horas, no máximo. Vou voltar nesse ponto mais pro fim.</p>
<p>Não importa quantos diagramas, fluxogramas, photoshops ou words vocês produzam, nada disso é o software. Pra um amador, dá impressão que o ideal é ter o máximo de páginas de requerimentos detalhados quanto possível. Tá errado. Essa noção errada vem pelo seguinte: qualquer projeto de engenharia civil, pra fazer uma casa ou um prédio, precisa de meses de planejamento, várias plantas baixas, diagramas de encanamento, diagrama elétrico e tudo mais, pra garantir que o prédio seja construído perfeitamente dentro das especificações.</p>
<p>Portanto, software deveria ser a mesma coisa. Vamos fazer quinhentas páginas de diagramas UML, que é o equivalente a planta baixa, porque daí, quando for escrever o software propriamente dito, vai sair tudo perfeitamente dentro das especificações. Essa premissa está errada. Todo mundo acha que um programador é um pedreiro. Não querendo desvalorizar o trabalho de pedreiros, mas um programador, na realidade, é o arquiteto. O trabalho de pedreiro, é 100% automático desde sempre. O pedreiro de software é o compilador ou interpretador. A planta baixa É o código fonte do software. Portanto todo programador, quer ele queira ou não, faz trabalho de um arquiteto.</p>
<p>Lógico, se sua linha de trabalho é só instalar Wordpress pra diversos clientes. Não se está produzindo software novo, só copiando e colando o que já existe e esse trabalho, sim, parece mais uma linha de fábrica. Mas estamos falando de fazer software que não existe, quase do zero. Reusando vários componentes como frameworks ou bibliotecas, sim, mas o software final não seria possível fazer numa linha de fábrica. Cada passo no desenvolvimento desse software requer tomadas de decisões de cada programador envolvido. Não existe receita.</p>
<p>O famoso exemplo de fazer um botão com cor na tela. Dá pra fazer de dezenas de jeitos diferentes. Posso fazer todo o CSS e HTML do zero. Posso escolher usar um Tailwind da vida. Mas se escolher isso, não pode ser só pra um botão, o certo é usar em todos os botões e elementos gráficos de todas as telas. Eu quero isso? Posso escolher fazer todo o sistema de eventos do zero ou usar React ou Vue. Mas de novo, isso vai afetar todo o sistema. Qual minha equipe tem mais facilidade pra usar? Já tinha começado como um sistema em Angular? Se eu decidir que quero usar Vue no lugar, quanto custa reescrever tudo? Vale a pena?</p>
<p>Tudo são decisões. Muitas sem grandes consequências, mas algumas podem quebrar seu projeto inteiro se feito de forma irresponsável. Como se toma esse tipo de decisão? Não tem receita pra isso. Na maioria das vezes, seu prazo tá apertado, e o ideal é não escolher decisões que envolvam jogar tudo fora e fazer tudo de novo, então isso já elimina várias escolhas. Mas algumas são incertas, se der certo talvez vocês ganhem produtividade pra frente. Pra isso se fazem provas de conceito.</p>
<p>Todo mundo ignora o poder das provas de conceito. Prova de conceito ou protótipos são pedaços de código, uma pequena parte da aplicação, que deve ser feito com o objetivo de potencialmente jogar fora depois. Portanto não precisa ser integrado ao código principal. Pode ser feito como uma branch do repositório, o que num GitHub chamamos de pull requests ou num GitLab chamamos de merge requests. Tecnicamente é um desvio no repositório de código. Enquanto todo mundo continua trabalhando no tronco principal, a branch <code>main</code>, alguém trabalha nesse desvio, num galho, por alguns poucos dias, pra ver na prática se a mudança sugerida realmente trás benefícios ou não.</p>
<p>Provas de conceito tem que ser um empreendimento curto, um micro-projeto de prazo fixo. Coisa de 2 dias, uma semana no máximo. Mesmo estando incompleto, no final desse período deve dar pra equipe ver na prática como a idéia se materializa em código, os impactos que causa, e se todo mundo está confortável em seguir em frente. Daí pode até jogar fora esse galho e todo mundo voltar pro tronco principal aplicando seja lá qual seja essa mudança técnica. Ou então se prova que a idéia era mais complexa do que se imaginava, e todo mundo descarta essa idéia. Mas isso não foi perda de tempo.</p>
<p>Você tem 10 grandes dúvidas técnicas que podem custar muito tempo, coisas no nível: devemos usar React ou Vue? Devemos ficar no Postgres ou migrar pra Firebase? Devemos ficar só em APIs REST ou já começar tudo em GraphQL? Vamos dar deploy das coisas em Vercel ou criar Terraform pra AWS? Só que ninguém da equipe nunca teve muita experiência em nenhuma dessas coisas. Cada uma pode potencialmente custar 2 a 4 meses. Sua equipe só tem 3 pessoas. Então fácil, se for fazer tudo, não vai sair por menos que 10 meses pra equipe, e com a falta de experiência, fácil vai virar o ano.</p>
<p>Em vez disso, pára tudo, cada um da equipe vai fazer provas de conceito de cada coisa, num prazo máximo de 2 semanas. E esse prazo é fixo, depois de 2 semanas vamos ver o que saiu. No final, mais da metade dessas coisas se provam realmente mais complicadas ou o custo claramente não vai compensar. Em vez de ir em frente com 10 coisas incertas que vai custar pelo menos 1 ano pra fazer, vocês concordam agora em desistir de 7 delas, porque a prova de conceito deu evidências que não vai dar certo. Por outro lado, 3 dessas coisas se provaram boas, e vocês vão seguir em frente.</p>
<p>Ao custo de 2 semanas da equipe, vocês economizaram mais de meio ano de trabalho que ia dar errado, e vão prosseguir com 3 coisas que vão custar um mês da equipe e que todo mundo tem mais certeza que vai dar certo. Esse é o grande truque: não se comprometa com coisas de longo prazo que ninguém tem certeza. Pague um tempo curto pra descobrir se dá pra cobrir essa incerteza. Não é jogar tempo fora, é pagar um pouco pra não jogar muito mais fora. Essa noção deveria ser simples, mas na minha experiência, pouca gente parece entender que mais vale a pena jogar fora 2 semanas do que se comprometer cegamente com um projeto incerto que vai levar um ano.</p>
<p>Na cabeça de muita gente, como 1 ano tá longe, fica aquela sensação de &ldquo;vamos indo, lá na frente a gente vê como faz&rdquo;. Especialmente em tech startups que vivem de dinheiro de investimento, esse tipo de decisão errada acontece muito, porque afinal é muito mais fácil gastar o dinheiro dos outros. E mesmo quem tem que gastar do próprio bolso, fica apreensivo com essa noção de &ldquo;jogar 2 semanas fora&rdquo;. Só que não é &ldquo;jogar fora&rdquo;, é o mesmo que pagar um seguro.</p>
<p>Ninguém gosta de pagar mil reais, dois mil reais, todo ano, que ao final do ano, parece que não serviu pra nada. Só que se durante esse ano tiver um acidente, e você atropelar e machucar alguém, agora não é dois mil reais que vai custar, vai ser 100 mil, 200 mil reais. Quem faz economia porca e fica de bravata achando que &ldquo;não precisa, não vai acontecer&rdquo;, é o primeiro que se envolve em acidentes e depois fica falido. Em projetos é a mesma coisa. Se for coisa que você já faz várias vezes antes, o risco é baixo, já sabe o que tem que fazer, tem noção de quanto vai custar e mais ou menos sabe o que pode ou não dar errado, então consegue ir em frente. Mas se nunca fez, é melhor fazer um teste antes de decidir.</p>
<p>E caindo no dia a dia de programação, é esse o mesmo argumento de porque falamos que todo programador deveria fazer, no mínimo, testes unitários de tudo que está trabalhando. Toda nova funcionalidade, deveria ser coberta de testes. Toda correção de bugs, deveria começar com um teste que simula o bug, pra que já comece falhando, e você sabe que corrigiu quando esse teste passa. Não só isso, esse bug em particular não vai acontecer de novo. Todo teste que você começa escrevendo antes de fazer o código, é como uma mini prova de conceito antes de tentar fazer uma coisa que não tem muita certeza. Se não tem certeza, comece escrevendo um teste.</p>
<p>Muitas pessoas já me pediram pra fazer um video sobre testes, mas eu realmente não tenho vontade, porque testes é um assunto que tem já toneladas de documentação, livros, tutoriais, cursos, blog posts, PDFs gratuitos online, pra cada linguagem diferente, pra cada framework. Testes não é um troço que você aprende em 5 minutos e faz igual pra sempre. É um conjunto de técnicas. O ideal é um junior parear com alguém mais experiente e aprender na prática: quando precisa fazer, e com que nível de detalhes. É muito importante que sua equipe tenha pelo menos uma pessoa mais experiente que entende a importância de ter testes e oriente os demais a fazer.</p>
<p>Deixa eu fazer o sales pitch de 2 minutos de porque todo programador deve fazer testes, de porque nenhum pull request pode ser mergeado sem testes, e porque uma pessoa que se diz sênior e é contra fazer testes, por definição, não deveria se considerar sênior. Toda regressão é tempo e dinheiro jogado fora. E o que é uma regressão?</p>
<p>Regressão é exatamente o que o nome diz. Se até este momento, digamos que alguém de QA testou tudo e tinha zero bugs. Alguém me faz um pull request que ficou trabalhando sem integrar por duas semanas. Sem testar. E dá merge na master. Faz deploy, e o que acontece? Surgem um monte de bugs. Não só na funcionalidade que ele tava trabalhando, mas em outros lugares que antes funcionava. Esse amador regrediu o trabalho de todo mundo. Tudo deu passos pra trás.</p>
<p>Agora o certo é dar revert nesse pull request, reverter mesmo. Só que esse pull request não tem mais como voltar atrás, porque dar deploy, ele alterou o schema do banco de dados e não tem como voltar mais ao schema anterior sem um grande downtime. Um desastre. E, claro, tenha certeza que esse indivíduo fez esse deploy na tarde de sexta-feira. Lógico. Cancela o happy hour porque a noite de sexta vai ser longa, fim de semana adentro, pra consertar essa cagada.</p>
<p>O fluxo é simples: todo programador, seja fazendo testes-antes ou testes-depois, quando diz que concluiu seu pull request, também adicionou um mínimo de testes, fez rebase com a master, e indicou pra equipe que tá tudo pronto. Alguém da equipe sempre deve avaliar esse pull request, normalmente alguém mais experiente. Pelo menos pra bater o olho e ver que não tem nada que parece muito mau feito ou muito fora do lugar e, principalmente, se tem testes. Se não tiver, já rejeita.</p>
<p>Se for uma equipe minimamente bem equipada, está usando repositórios privados de Git seja num GitHub ou GitLab. Ambos tem suporte a rodar testes automatizados num serviço separado. Nesse momento ele já puxou o que está nesse pull request, subiu um container de docker, e executou os testes automatizados do projeto. E quando o avaliador for checar o pull request, já vai saber inclusive se os testes passaram ou não. O GitLab tem a própria ferramenta que é o GitLab CI, de Continuous Integration. Tem diversos serviços externos que integram com GitHub como CircleCI, Travis CI, Semaphore e outros. É super simples configurar no seu projeto e não vejo nenhum motivo pra não usar.</p>
<p>Se o pull request parece que está ok, e o CI diz que os testes passaram, só agora é permitido fazer merge desse código novo na master. No mínimo, sabemos que as funcionalidades que funcionavam antes, cobertas com testes, continuam funcionando depois, mesmo com a inclusão desse código novo. É um bom indicativo pra toda a equipe que as coisas estão andando pra frente, e não ficando com débito técnico acumulado pra trás. Eu vi um tweet alguns dias atrás, de alguém que tava relatando como um idiota da equipe resolveu atualizar a versão do Java, acho que do 16 pro 18 e fez deploy pra produção. Óbvio que deu pau. Óbvio que o pau apareceu na sexta-feira.</p>
<p>Tem idiota que acha que porque usa uma linguagem compilada, com tipagem estática como Java, não tem problema não ter testes, porque se tudo compila, obviamente é porque funciona. Não tem nada que é mais certificado de júnior, do que pensar assim. Ahhnnnn como assim? Voltando ao exemplo de software livre. Vamos ver o projeto do navegador Firefox. Sabe Firefox? Sim, aquele Firefox. Eu baixei o código aqui na minha máquina. Firefox é feito na maior parte em C++, um pouco de Rust, um pouco de Javascript. É um navegador bem estável e quem usa acho que raramente tem algum problema dele renderizar errado, crashear ou coisas estranhas assim.</p>
<p>Se você é desenvolvedor de front-end, certamente já usou o Developer Tools certo? Pra debugar o que está programando. E esse Developer Tools tem uma suite de testes, pra garantir que toda nova versão, antes de lançar pra você baixar na sua máquina, passa todos os testes e não introduz nenhuma regressão. Ou seja, tudo que funcionava numa versão continua funcionando na mais nova. No repositório de código fonte, só rodar <code>mach test devtools/*</code> e olha só, abre a versão que acabei de compilar direto do código fonte e roda uma suite de testes de integração, simulando cada funcionalidade do Developer Tools. É assim que a Mozilla garante que código novo não introduz regressões óbvias.</p>
<p>Se der uma fuçada no código, olha só, cada diretório tem um sub-diretório de testes. E muitos deles tem testes específicos pra bugs que foram reportados. Assim eles garantem que bugs corrigidos também não vão aparecer do nada de novo. Bugs que foram corrigidos e aparecem de novo porque não tinha um teste pra cobrir, se chama &ldquo;jogar dinheiro fora&rdquo;. Porque tem que corrigir mais de uma vez. Ao longo do tempo, você está perdendo tempo, dinheiro, eficiência, e mercado pra outro projeto que vai fazer melhor que você com menos recursos.</p>
<p>Tem gente que acha que porque é programador front-end, não precisa se preocupar com testes. Acho que todo mundo gosta do projeto React do Facebook, quer dizer &ldquo;meta&rdquo;, certo? Eu baixei aqui o código fonte da biblioteca React. E olha só, se rodar <code>yarn test</code>, o React tem uma suite de testes feita em Jest. Pelo mesmo objetivo. Mais de 7 mil testes unitários garantem que quando você atualiza o React, não vai quebrar tudo.</p>
<p>Sabe aquela biblioteca de estilos, o TailwindCSS? Um amador poderia imaginar que é só uma coleção de CSS, obviamente não precisa de testes, certo? Errado. Eu baixei o código fonte do Tailwind e adivinha, se rodar <code>yarn test</code>, mais testes unitários em Jest. Quase 900 testes unitários, fora checagens com eslint que por acaso deu alguns erros. Eu baixei do branch principal, ainda estão mexendo e na próxima versão esses bugs serão corrigidos. Mas está claro o que funciona e o que não funciona pra quem está contribuindo.</p>
<p>Bah. Esse povo de front tá muito fresco. Ficar fazendo testes é coisa de hipster. Certamente povo de Python, mais calejado, mais raíz, não se importa com essas besteiras. Por isso eu baixei aqui o código fonte do framework Django. E olha só, tem um diretório chamado <code>tests</code> e dentro eu posso rodar esse script chamado <code>runtests.py</code>. E lá vai ele rodar centenas de testes pra garantir também que versões novas do Django não tá quebrando nada que já funcionava antes. Mais de 16 mil testes. Passando bonitinho.</p>
<p>Foda-se povo de Javascript e Python. Vamos mudar pra PHP. PHP sempre foi conhecido por ser Quick and Dirty, rápido e sujo. Código raíz. Programador Cowboy que programa orientado a gambiarra. POG-zão na veia. Eu baixei aqui o código fonte do framework Laravel que é um dos mais usados hoje. Só que não. Eu posso rodar <code>composer exec phpunit</code> e olha só, quase 8 mil testes, de novo, pra garantir que a versão mais nova não introduz nenhuma regressão. Por acaso falhou, povo ainda tá mexendo, próxima versão estável certamente já vai ter corrigido esse bug. Mas nós sabemos que tem o bug, porque tem suite de testes.</p>
<p>Aliás, sabe o editor de textos mais usados por programadores hoje? O tal do Visual Studio Code? Provavelmente até você assistindo que não gosta de testes aí usa pra fazer seus códigos porcaria. Eu baixei o código fonte também e tem testes automatizados rodando <code>yarn smoketest</code>, que segundo a documentação deles, é rodado antes de gerar o binário final que você baixa pra instalar. Igual o Firefox, tem testes de integração onde abre o aplicativo e vai testando cada funcionalidade que a gente aceita que simplesmente funciona em toda nova versão. E tudo só funciona, porque tem testes pra garantir isso.</p>
<p>E voltando ao Firefox. Parte dele é feito em Rust. E pra você, seu infeliz, que ainda acha que só porque o código compila, tá tudo bem? Não era o Rust que tem o tal compilador mais inteligente de todos os tempos? Que muita gente acha que é mágica e garante não só binário super performático como se tivesse sido feito em C e sem bugs de gerenciamento de memória e problemas de segurança? Muita gente só falta dizer que o compilador escreve o código pra você, de tão bom que ele é. Certamente, se o código em Rust compila, não precisa de testes, certo?</p>
<p>Bom, eu baixei o código fonte da biblioteca Tokio, que é o framework pra desenvolvimento assíncrono em aplicativos feitos em Rust. Todo projeto Rust acompanha um arquivo <code>Cargo.toml</code>, assim como todo projeto Javascript acompanha um <code>package.json</code>. E assim como existe npm ou yarn, em Rust temos o comando <code>cargo</code>. <code>cargo install</code> é equivalente a um <code>npm install</code>. E adivinha, o próprio cargo, ferramenta padrão que já vem com o Rust, tem essa opção <code>test</code>. E o que isso faz? Vamos ver. Só executar <code>cargo test</code>. Olha só ele rodando a suite de testes da biblioteca Tokio.</p>
<p>Ou seja, nem mesmo um projeto em Rust, que é armado com o compilador que é considerado a obra prima dos compiladores, evita os autores do Tokio de não fazer testes. Aliás, pode ver, ele sai rodando centenas de testes, não é um ou dois. Ahhh mas só de compilar já devia ser suficiente &hellip; não, sua múmia paralítica, os criadores do Rust armaram o Cargo pra rodar testes. Você acha que você, logo você, entende mais do que os criadores do Rust?</p>
<p>E você que é de Java e também detesta testes, que tem essa noção atrasada de porque o Java compila, tem tipos estáticos, então se compila tá tudo certo. Você que acha que só linguagens dinâmicas não compiladas como Ruby ou Javascript que precisam de testes. Sabe onde nasceu o conceito de TDD? Foi com Kent Beck, que fez o primeiro jUnit, em Java. Desde os anos 90 a gente sabe que só porque alguma coisa compila, não quer dizer que funciona. Só porque compila não significa que bugs não foram introduzidos. E por ficarem cansados de toda hora ter que correr atrás do próprio rabo, de ficar corrigindo coisa que já funcionava, que os agilistas originais formalizaram o conceito de testes.</p>
<p>Aliás, falei de Java e não mostrei Java. Só de exemplo, baixei o código fonte de um dos frameworks web mais usados que é o Spring Boot. Rodando o bom e velho <code>gradlew build</code> olha só. Baixa todas as dependências, junto com a internet inteira duas vezes, e roda todos os testes automatizados na sequência. O build só é considerado ok se compila e os testes rodam sem erro até o fim. E são milhares de testes. Ué, mas não era só compilar que já tava garantido que funciona? Obviamente não.</p>
<p>Muito antes de existir o conceito de TDD a gente já fazia testes automatizados de um jeito ou de outro. Toda empresa de software madura faz testes. É por isso que seus programas e seu sistema operacional não quebram o tempo todo, toda hora. E mesmo com tudo isso, ainda assim bugs acontecem. Significa que se não tivessem todos esses testes, teríamos ordens de grandeza mais bugs em tudo. Novas versões iam demorar ordens de grandeza mais pra sair. Ia precisar de ordens de grandeza mais pessoas testando. Mas não. Hoje em dia quase todo dia algum programa que você tem instalado ganha uma versão nova, mas você raramente é incomodado. Bugs acontecem, mas são raros. E a razão disso é que todo software maduro, proprietário ou de código aberto, tem testes.</p>
<p>Veja meu blogzinho feito em Ruby on Rails. Ele é ridiculamente simples. Eu mesmo que fiz em 2012. Não lembro se foi em Rails versão 3 ou 4. De lá pra cá eu atualizei até o Rails 6 rodando em Ruby 3. Toda vez que sai versão nova da linguagem ou do framework, eu atualizo, pra ganhar as correções de segurança principalmente. Eu venho atualizando faz 10 anos. E toda vez atualizo as bibliotecas também. Mas diferente do tweet da pessoa que falou que o cara atualizou o Java e subiu pra produção. Antes eu rodo meus testes em Rspec. Se não passa, obviamente não subo. Faço pequenos ajustes quando precisa, se a API de alguma biblioteca mudou. Mas meus testes pegam a maior parte dos problemas. Uma vez por ano, gasto 30 minutos, atualizo as coisas e subo.</p>
<p>Ter testes não é burocracia, não é perda de tempo. É um seguro. Não fazer testes é a mesma coisa que dirigir sem seguro. Na maior parte do tempo, não parece ter muito valor mesmo. Sempre dá pra fazer tudo rápido, sujo e na gambiarra. Você se sente o herói de entregar as coisas rápido. Mas é inevitável, sua produtividade, e da sua equipe, vão gradativamente caindo. Porque mais e mais se perde tempo corrigindo bugs do que fazendo coisa nova. Em breve, a maior parte do seu tempo é só corrigindo bugs. E o que o amador fala? Ahhh, vamos mudar pro framework mais novo. Vamos mudar pra linguagem mais da hora. Quantos de vocês conseguiram manter e atualizar o mesmo sistema por uma década?</p>
<p>Não existe nenhuma linguagem inventada na história que só pelo fato de compilar garante que não precisa ter testes. Todo mundo que entendia isso já fazia testes de alguma forma. A gente fazia alguns scripts separados do código pra rodar partes que eram mais sensíveis se quebrasse, ou que sabíamos que sempre alguém fazia merda e quebrava. Era uma das coisas que separavam os amadores dos espertos. O que aconteceu foi que as comunidade ágeis deram nomes aos bois e criaram convenções e semânticas pra todo mundo falar a mesma língua. Mas não existiu uma data especial que divide antes, com zero testes, e depois, com testes. Foi gradual, onde décadas atirando no próprio pé naturalmente levaram os programadores mais experientes a chegar na mesma conclusão.</p>
<p>Enfim. O problema é que a maioria de vocês assistindo detesta fazer testes. A desculpa é perda de tempo. Mas a verdade é porque vocês não conseguem fazer. Vocês acham que ninguém percebe. Todo mundo que se sente incapaz de fazer alguma coisa, tenta denegrir esse alguma coisa. E é óbvio, não sabe fazer porque nunca faz. Assim como tudo em programação, fazer testes depende de treino e prática. Não existe nenhum livro ou curso que dá pra ler e automaticamente sair fazendo testes perfeitos.</p>
<p>Você que é front, assistiu um curso e já saiu criando interfaces do nível de complexidade de um Canvas instantaneamente? Ou precisou de semanas e semanas apanhando de CSS obscuro? Ou você que é back, só leu um tutorial e já saiu criando APIs perfeitas em GraphQL integrado com backend de Firebase? Em 5 minutos já saiu codando como se não houvesse amanhã? Duvido.</p>
<p>Não adianta ficar só procurando coisa pra ler. Precisa praticar. Assim como tudo que já fez, todos os seus primeiros testes vão ser uma bosta. Mas é assim que começa. Eu citei vários projetos de código aberto. Dá uma olhada nos testes deles pra se inspirar. Tem centenas de outros projetos mais parecidos com o que estiver trabalhando agora. Baixa e lê os testes deles. Copia. Altera. Testa. Joga fora. Começa de novo. Repete. É assim mesmo.</p>
<p>Outra desculpa é falar que o projeto já tá faz meses ou até anos sem nenhum teste. É o cúmulo da preguiça ouvir isso. Ótimo. Faça você o primeiro teste. Pelo menos essa uma funcionalidade que acabou de entregar, está coberto e vai ser mais difícil de aparecer bug aleatório pela falta de atenção de outro programador depois. E se alguém pisar em cima do seu código, só de rodar esse um teste, dá pra saber que quebrou, quem foi, em qual commit, e corrigir mais fácil. De 1 teste em 1 teste, a cobertura naturalmente aumenta.</p>
<p>Alguém reportou um bug? Abriu um ticket? Faça o teste que simula o bug reportado. Agora corrige. Pronto, esse é 1 bug que dificilmente vai aparecer de novo. E olha só, do nada, você começou a andar pra frente em vez de ficar só olhando pra trás. E total de cobertura? Precisa ser 100%? Isso é outra pergunta idiota que sempre aparece numa discussão de quem é desleixado. Foda-se. Você não fez nem 1% ainda, pra que quer discutir 100%? Não interessa. Faz 50%, faz 500%. Só, faz, a, porra, dos, testes.</p>
<p>O que acontece é o seguinte. Sua equipe tem 3 pessoas. Cada um consegue pegar 2 ou 3 tarefas do backlog por semana. 6 a 9 tarefas entregas toda semana. Se o sistema começa a ser usado por usuários de verdade, rapidamente começam a aparecer bugs e problemas. Surge 1 ticket, 2 tickets. De repente a equipe só consegue entregar 3 a 6 tarefas por semana, porque precisa resolver bugs urgentes. Mas continua entregando coisas. Só que essas coisas novas, causam mais bugs. Na próxima semana a produtividade caiu pra 2 a 4 tarefas. E o número de bugs aumenta em vez de diminuir. Em pouco tempo, a equipe male male entrega 1 ou 2 tarefas novas, o resto do tempo é só resolvendo bugs.</p>
<p>E qual a decisão do gerente do projeto? &ldquo;Ah, é normal, precisa contratar mais programadores.&rdquo; Ele convence a diretoria. Dobra a equipe. E voilá, a equipe volta e entregar 6 a 9 tarefas novas por semana. E todo mundo continua sem se importar com testes ou qualidade em geral, é crunch, só entregar e entregar. O volume de código só aumenta, ninguém joga nada fora, vai só acumulando débito técnico. Mais código novo significa mais bugs. No final acontece a mesma coisa, nas semanas seguintes a produtividade cai pra 3 a 6 tarefas, depois pra 2 a 4 tarefas, e de novo, a nova equipe, com o dobro de integrantes, tá male male entregando 1 ou 2 coisas novas por semana, o resto é só apagando incêndio.</p>
<p>Digamos que todo mundo é ruim em testes. Se lá no começo tivesse decidido entregar tudo com testes, em vez de 6 a 9 tarefas, a equipe estaria entregando só de 4 a 6 coisas, talvez menos. Parece bem ruim. A diferença é que vai ser 4 a 6 TODA semana. Surgiu um bug novo? O programador que pega pra corrigir adiciona testes, e segue todo o processo que descrevi antes. Esse bug em particular não aparece de novo. Entregou coisa nova? A suite de testes garante que o que tinha antes não quebrou por conta disso. E assim, toda semana, de forma sustentável, a equipe entrega 4 a 6 tarefas. Não é o máximo de 9, mas não cai pra 1 ou 2.</p>
<p>Daí a empresa decide contratar mais gente e dobra a equipe. Mas a equipe é diligente e treina os novos integrantes sobre a importância de qualidade. A produtividade quase dobra, de 4 a 6 pra 8 a 10 tarefas. A produtividade só dobra se todo mundo segue o protocolo, ninguém pisa no calo de ninguém, e tudo que já foi feito e testado continua funcionando no futuro. Todo upgrade de bibliotecas e correções de segurança tem garantia que o sistema continua funcionando, porque a suite de testes tá passando. Daí nenhum deploy de upgrade feito na sexta feira causa desastre que vai precisar varar fim de semana corrigindo. Olha só que mágico.</p>
<p>Muita gente vai inventar um monte de desculpas pra não fazer testes, e eu garanto que pra cada desculpa nós já temos soluções. Nenhum programador precisa rodar a suite de testes o tempo todo se essa suite demora muito pra executar. Basta rodar alguns poucos ao redor do trabalho que está fazendo naquele momento. Daí sobe um pull request que vai ser pego por um GitLab CI e esse servidor de CI é quem vai rodar a suite inteira de testes. Não só isso, se a suite passar, esse CI pode fazer CD, que é continuous deployment, e automaticamente subir em ambiente de staging e notificar a equipe de QA.</p>
<p>Obviamente eu não posso mostrar projetos confidenciais dos meus clientes, mas posso mostrar esse projeto interno que nós usamos. Olha um pull request. Se olharmos o que mudou podemos ver que além do código, temos ajustes em specs também, que são testes. Quando o desenvolvedor subiu o código, o CI pegou e iniciou o job pra rodar os testes. Nesse caso levou pouco mais de 5 minutos. Passou com sucesso e, lógico, só porque todos os testes passaram não significa que não tem nenhum bug, só que não tem nenhum bug muito óbvio. Mais do que isso, no nosso caso tem configurado também checagens de segurança. No ecossistema Ruby temos coisas como Brakeman, que avalia o código por buracos de segurança conhecidos como injections.</p>
<p>Essa aplicação sobe no Heroku, que tem uma funcionalidade chamada review apps. Podemos ter um deployment separado de testes pra cada pull request que ainda está aberta. Justamente pra alguém conseguir testar de verdade antes de aprovar. Um review app é um ambiente de staging, só que isolado pra cada pull request. Dá pra ter dezenas de review apps em paralelo. Assim não precisa ficar acumulando e integrando tudo no mesmo ambiente de staging pra testar, o que seria uma zona. Testa tudo isolado. Vai aprovando no ritmo do responsável pelos testes. E no final, quando estiver tudo testado, aprovado e mergeado no branch principal, daí faz um deployment pra produção aqui do lado.</p>
<p>Neste exemplo, no dia que fui tirar foto de tela, não tinha nenhum pull request aberto. Mas eles apareceriam listados aqui. Cada um com uma URL separada de testes. Esse é outro motivo de porque eu sempre recomendo Heroku. Ele não é o mais barato, mas esse tipo de recurso facilita o fluxo de trabalho em ordens de grandeza. Só as horas de desenvolvimento que se economiza de cada desenvolvedor já se paga.</p>
<p>Enfim, uma vez cada pull request de cada desenvolvedor sendo aprovado, tudo vai sendo integrado no branch principal e podemos fazer um deploy de tudo integrado pro ambiente de staging. Agora o pessoal de QA pode fazer o trabalho deles direito, que é testar as coisas novas, e não ficar retestando tudo de novo que já funcionava antes. Todo report de bugs volta pro programador, que faz a correção e ajusta os testes pra cobrir os casos que não tinha pensado.</p>
<p>Com o pull request foi ajustado, os testes passam, QA já viu em staging, o sênior da equipe pode dar a última olhada e fazer o merge na branch principal. E aí alguém de devops ou alguém responsável pela produção pode pegar o que está na branch principal e fechar uma versão pra produção, com a segurança de saber que se está na branch principal, então está funcionando corretamente.</p>
<p>E se estiver usando Heroku, ainda tem uma facilidade extra. Digamos que todo mundo fez tudo direitinho, mas mesmo assim surgiu um bug que só aparecem em produção. Basta ir nesta aba de atividades e clicar em roll back pra voltar pra versão anterior. Heroku é caro, mas é nessas horas que ele se paga. Medo de deploy na sexta-feira só existe em equipes disfuncionais que não fazem esse básico que acabei de falar.</p>
<p>Falando em disfuncional, o maior desafio em toda equipe de projetos, é coordenar a comunicação. Ao ter um repositório de códigos como Git, um processo de testes de integração contínua, pareamento entre sêniors e juniors pra compartilhar conhecimento, e um protocolo como de pull requests, estabelecemos um conjunto simples e objetivo de entendimento nessa equipe. Não seguir esse protocolo é um desrespeito à equipe. Você não programa pra você mesmo, você contribui com sua equipe. O importante não é andar rápido, o importante é não estragar o que já funcionava.</p>
<p>Da mesma forma, ninguém deve sair adicionando coisas no projeto que ninguém está sabendo. Por isso que no mundo ágil se inventou os tais rituais como de planejamento e revisão. Rituais não devem ser obrigações, mas pontos de coordenação. Se os rituais estão sendo meramente pras pessoas aparecerem mas não participar, então não servem pra nada e devem ser eliminados e repensados. Esse é um ponto não-técnico que não quero tentar detalhar demais, mas preciso falar de um ponto específico: estimativas.</p>
<p>Se tem uma coisa que programador odeia fazer mais do que testes é estimar tarefas. Já disse isso em outros videos, mas existem duas palavras que todo mundo não-técnico confunde: estimar e prever. O programador estima, mas os gerentes ou diretores acham que receberam previsões. Estimativas são ordens de grandeza, não está certo nem errado. É uma ordem de grandeza. Previsões sim, é pra serem precisas. Quando um programador diz que pode levar uns 2 ou 4 dias, é isso que pode levar, 2 a 4 dias. Talvez leve 5. E aí o gerente que achava que tinha uma previsão na mão, fica puto, porque tava esperando 2 dias, recebeu em 5 e quer sair chutando tudo. E é isso que eu chamo de um idiota.</p>
<p>Eu acho que todo gerente de projetos deveria gastar um tempo movimentando o próprio dinheiro no mercado financeiro. Comprar e vender ações, ou opções, ou criptomoedas, não importa. Porque todo mundo gostaria de saber pra onde vai o mercado, se vai subir ou se vai cair. É impossível acertar 100% do tempo. Se alguém soubesse prever isso, quebraria o mercado financeiro. O máximo que podemos fazer é dar chutes educados. Estimativas. A gente tenta acertar, comprar na baixa, vender na alta. Ou no mínimo planejar pra alta e se proteger da baixa. Criamos contingências, e sabemos que é impossível ganhar sempre. O máximo que podemos fazer é tentar minimizar perdas. E as perdas nunca vão ser zero.</p>
<p>Gerenciamento de projetos é a mesma coisa. Um gerente inteligente não se interessa em tentar prever cada tarefa individualmente. Ele observa o movimento geral da equipe e do produto. Tá em tendência de alta de produtividade? Baixa de produtividade? Tem como influenciar a baixa? Qual é o obstáculo? Algum diretor deu carteirada e quebrou o planejamento? Alguém ficou doente? Um bom gerente está numa posição que um analista financeiro não tem: de influenciar as tendências.</p>
<p>Lógico, quanto mais um programador ganha experiência e tem mais segurança no que realmente sabe e o que não sabe, mais e mais suas estimativa se aproximam de previsões. Ele já tem uma boa noção de quanto tempo leva a maioria das coisas. Quanto mais inexperiente, mais longe vai ser essa estimativa. Faz parte do crescimento profissional de cada um começar a quantificar suas próprias capacidades. Ninguém tem obrigação de &ldquo;acertar&rdquo; estimativas, mas claro, quanto mais precisas forem, mais todo mundo consegue confiar mais e mais em você. É uma das formas de subir na carreira. Entregar o que promete.</p>
<p>Tarefas, stories ou seja lá como quer chamar, são meras formalidades. Quanto mais curtas, objetivas, direto ao ponto forem, melhor. O objetivo não é um concurso de literatura onde todo mundo tem que fazer uma dissertação. A quantidade de palavras deve ser o suficiente pra que toda a equipe envolvida saiba no que está sendo trabalhado. Só isso. Tem quinhentos blog posts, livros e cursos tentando ensinar &ldquo;a melhor forma&rdquo; de escrever requerimentos. E a maioria é bullshit. Quer dizer, até faz sentido, pode ser usado como inspiração, mas não existe nenhuma receita que dá pra só seguir. Assim como testes e estimativas, escrever requerimentos também depende de prática. Estude o que se sugere, mas não siga ao pé da letra.</p>
<p>User Stories tem aquele formato idiota de &ldquo;eu, como usuário, quero conseguir adicionar um produto no carrinho e ter o total do pedido, para poder pagar minha compra&rdquo; ou algo assim. Pode ser, é bonitinho, tem o objetivo de forçar as pessoas a pensar em pra quem é a funcionalidade sendo feita, qual o objetivo e facilitar pensar na melhor forma de fazer. Tem coisa que precisa de mais detalhe que isso. Um protótipo de tela no Canvas. Um rascunho de conta de imposto. Tem coisa que nem precisa de tanto detalhe assim. Bugs costumam ser mais diretos &ldquo;quando o usuário abre no firefox de mac, o box de items fica em cima do box de cartão e não dá pra digitar o código de segurança do fucking pedido e a fucking compra não finaliza&rdquo;. Tá super claro.</p>
<p>Eu tive um cliente que o imbecil do CTO mandava todo programador descrever em detalhes como ia programar, antes de escrever uma linha de códigos. Tinha que listar que classes novas ia criar, com que métodos, com quais nomes. Era a coisa mais idiota que eu já vi, só perde pra época que povo achava que precisava fazer diagramas de classe UML precisos meses antes de escrever uma linha de código. É idiótico. É da época que falei que se achava que programador era só pedreiro e havia uma forma mágica de descrever tudo, antes de escrever o código. É uma noção boçal.</p>
<p>O resumo é simples: a descrição deve ser a mais concisa, com o menor número de palavras possível. A equipe deve conseguir se reunir e em pouco tempo entender o que precisa ser feito, quem vai fazer, quem precisa de ajuda. O gerente, ou Product Owner, e o equivalente tech lead ou programador mais sênior, deveriam estar alinhados. Daí quando seja lá qual programador entregar o pull request praquela funcionalidade, o tech lead consegue avaliar rápido e mandar pro QA. O PO e o QA precisam estar alinhados, pra ver se o que foi entregue resolve o que se queria resolver.</p>
<p>Burocracia e formalidades em excesso aparecem em empresas onde esses personagens tem preguiça de conversar ou pior: não sabem o que estão fazendo. Porque se soubessem, poderiam ir direto ao assunto. Toda burocracia em excesso é defendida por aqueles que não sabem o que estão fazendo, porque aí podem usar a desculpa de &ldquo;mas, mas, eu segui os procedimentos, a culpa não é minha&rdquo;. Toda burocracia é uma muleta pra cabideiro que não deveria estar ocupando as posições que estão ocupando. É assim simples.</p>
<p>Voltando pra estimativas. Não existe nenhuma fórmula nem procedimento que, dado uma descrição em texto de um caso de uso, ou user story ou seja lá o que for, chega num número de horas ou dias exatos pra resolver essa tarefa. Cocomo e outras bullshitagens são lixo. Não tem como. Só a experiência dos programadores envolvidos é capaz de dar uma ordem de grandeza, cuja qualidade vai ser proporcional à experiência da equipe. Se a equipe for super experiente, mesmo assim vai ser uma ordem de grandeza.</p>
<p>Não tem nada mais retardado do que gente que pega esses números de estimativas, número de horas realizadas, vai pondo num gráfico e fica tentando derivar coisas desses números, fazer integral, aplicar fórmulas. Só porque se tem números, não signfica que existe cálculos que dê pra fazer. Eu posso sair jogando dados, colocar todos os resultados num gráficos e gerar um monte de conclusões. É assim que nasce pseudo-ciência, numerologia, astrologia e todas essas logias idiotas. Estimativa é um chute. Estimativas nunca estão erradas, porque não são previsões. Fazer contas com esses números não faz nenhum sentido.</p>
<p>Dá pra saber que vai ser perto de 6 meses mas menos de 1 ano. Agora, se vai ser 5 meses, 2 semanas, 4 dias e 5 horas, exatamente. Isso é impossível. Gastar horas tentando estimar tudo é um exercício de futilidade. Não perca tempo tentando prever com exatidão. Aprenda a lidar com ordens de grandeza. Simplesmente escreva o que se sabe que realmente precisa fazer. E nem tente detalhar 6 meses de trabalho de uma só vez. É inútil. Descreva o curto prazo em detalhes, 1 mês, no máximo 2 meses de trabalho. Tenha a direçao do longo prazo em mente, mas ajuste enquanto vai andando. Deixe a equipe se dividir em quem faz o que. E siga o que falei até agora sobre pull requests e merge.</p>
<p>Mesmo sem ter estimativa nenhuma, você como gerente ou como qualquer membro da equipe vai ver que numa semana a equipe entregou 10 tarefas, stories ou seja lá como quer chamar. Na semana seguinte, entregou 9. Na outra semana conseguiu entregar 11. Na semana seguinte entregou 7. O que o gerente ou facilitador dessa equipe começa a notar? Uma tendência. Parece que, seja lá como se descreve tarefas nessa equipe, eles conseguem entregar uma média de 8 a 9 unidades dessas &ldquo;coisas&rdquo;. E se for esperto, vai notar que quando entrega menos coincide quando sobe versão nova pra produção, daí o suporte recebe reports de bugs, que entram no backlog da equipe e isso diminui a quantidade de &ldquo;coisas&rdquo; entregues.</p>
<p>O gerente fala com o tech lead, que revê o processo, e vê que ele tava deixando passar pull requests com testes não tão bons. Ele pareia com o programador júnior que tava fazendo errado. Ensina na prática onde tava errando. E agora ele passa a produzir melhor. E é assim que uma equipe evolui, um passo de cada vez. Não é via metodologia, nem aumentando formalidades e burocracias, é via bom senso. Resolvendo problemas na raíz em vez de ficar inventando band-aids, ou pior, mais burocracias inúteis. E o principal: reorganizado a lista de coisas a fazer pra garantir que as coisas mais importantes estão sendo feitas primeiro e tudo que é opcional ou de pouco valor está no fim da lista. Assim, caso não dê tempo de fazer tudo, pelo menos você sabe que o mais importante foi feito.</p>
<p>Cuidado, não estou dizendo que não se deve perder tempo descrevendo tarefas ou que não se deve fazer estimativas. Estou falando que a formalidade não é tão importante, tem que começar a escrever e aceitar que vai estar mau escrito. Aceitar o feedback de que o programador fez errado porque do jeito que tava escrito ficou ambíguo. E aí vai ajustando os detalhes pra próxima vez, até o nível que faz sentido. &ldquo;Ah, se tivesse descrito essa conta com um exemplo, ele teria entendido&rdquo;, ou algo assim. Aceitar que uma estimativa nunca vai estar certa ou errada. É só uma referência. Não tente fazer projeções baseado em estimativas, porque isso só vai multiplicar um erro com outro erro.</p>
<p>Em vez disso veja em ordens de grandeza como falei. Seja lá como organiza as tarefas da equipe, veja, no geral, que tipos de funcionalidades ou correções de bugs a equipe entrega num certo período de tempo, como toda semana. Daí nem precisa perguntar pra equipe se o que tem no backlog cabe ou não na semana. Você já sabe essa resposta pela velocidade que já conhece. Quando sabe que não vai caber, seja honesto e faça seu trabalho: corte ou simplifique. Não tem mágica. Se não cabe, não cabe. Se tentar fazer caber, obviamente a qualidade vai cair. É onde vai vir o &ldquo;se eu pular fazer testes, talvez caiba&rdquo;. E quando se cede uma vez, isso vai acumular. Na semana seguinte já começa a vir a conta. E uma decisão feita no desespero numa semana, vai prejudicar todo o resto do andamento daqui pra frente.</p>
<p>Não existe almoço de graça. Toda vez que vai se deixando passar uma pequena coisa errada, isso vai acumulando. Em breve, vai estar tudo errado. Ser sustentável é exercitar disciplina. E não tem coisa mais maçante, mais entediante, do que ser disciplinado. Quando tudo é uma bagunça, é difícil elencar os problemas. Mas numa equipe disciplinada, fica fácil ver quem tá puxando a equipe pra trás. Sem ter a muleta da burocracia, fica fácil ver quem é a pessoa inconstante, aquele que promete demais, entrega de menos, e quando entrega outra pessoa precisa consertar. A equipe inteira sabe quem é essa pessoa. Eles devem ensinar, ajudar, mas quando a pessoa é resistente a trabalhar em equipe, ela deve ser removida. É assim simples.</p>
<p>A decisão errada é manter essa pessoa e contratar outra pra aumentar produtividade. Muitas equipes que eu já vi teriam a produtividade aumentada só de cortar os maus elementos, porque agora eles não precisam ficar consertando o que o mau elemento tá entregando de mau jeito. Automaticamente a produtividade aumenta, o stress diminui, todo mundo trabalha melhor. Uma coisa que aconteceu durante esse período que já passou de vacas gordas, com montes de empresas contratando gente ruim no atacado, é que em vez de aumentar a produtividade, eles prejudicaram a qualidade do produto e das equipes. Imagina um monte de gente ruim, fazendo o contrário de tudo que falei até agora. Só acumulou um monte de código porcaria que funciona em produção na força bruta, com alguns poucos hiper estressados, varando noite, consertando a porcaria que recebe.</p>
<p>Se a equipe não se importa com o que está entregando, nada do que eu falei até agora importa. E todo novo integrante, ao ver que ninguém tá realmente muito interessado, vai aprender do jeito errado: que o melhor é ligar o foda-se e fazer igual todo mundo. E é assim que um projeto entra em colapso, porque ninguém está ligando. Em empresas com dinheiro dos outros demais, é o que mais acontece. Mas se sua empresa realmente depende do código sendo feito, sinto dizer, rapidamente todo mundo vai perder o controle. E daí pra frente é só cavar o buraco mais fundo.</p>
<p>De novo. Todo projeto é uma incerteza, especialmente nos primeiros dias. Especialmente se for uma equipe nova, recém constituída. Precisam aprender a trabalhar juntos ao mesmo tempo que precisam entender o que diabos vão construir. É uma montanha de incertezas. Por isso o ideal é garantir que o básico do básico está garantido: que pelo menos o que está sendo entregue vai continuar funcionando ao longo do tempo. Exercitem o processo de testes, pull requests, integração contínua, deployment em staging. Daí comecem a ver até que nível de detalhes realmente as tarefas precisam ser definidas. E vai ajustando.</p>
<p>Não tente estabelecer regras escritas em pedra no dia um. É inútil. Comece no equivalente a fazer rascunhos. Avalie se o rascunho está num nível de detalhes suficiente. Se não estiver, vai ajustando. O PO, o UX, precisam ir refinando isso. Daí o QA e o suporte precisam também ir refinando o processo de testes. Esse processo volta pra equipe de desenvolvimento, que reflete se está cometendo deslizes técnicos que poderiam evitar bugs óbvios. Todo mundo ainda estava crú em fazer testes, e aos poucos vai aprendendo a realmente testar os casos que interessam e deixar de lado os casos que não precisa. Naturalmente vão se acostumando a saber até onde precisa ou não cobrir determinados tipos de funcionalidades.</p>
<p>Sério. Exercite sua equipe em usar Git direito, em usar os recursos de testes que certamente já tem no seu framework. Como falei, Rails tem, Django tem, Laravel tem, Spring Boot tem, Express tem, React tem. Todo mundo tem suporte a testes que vocês só escolheram não usar. Só que vocês não são seres ungidos que fazem código sem erros. Pelo contrário, todo programador vai mais fazer erros do que acertar. Especialmente se for júnior. É normal. Só que hoje todo mundo deixa esses erros acumularem no repositório, porque os deploys pra produção acontecem só 1 vez por mês, ou 1 vez por bimestre. Daí toda vez que faz deploy fica todo mundo apreensivo, porque já sabe que vai dar merda. Todo mundo sabe que empurrou os problemas com a barriga. Daí o dia do deploy é o dia da pizza na madrugada. Todo mundo correndo atrás do próprio rabo pra consertar os erros que vão aparecendo. Um caos.</p>
<p>Não seja esse clichê. Faz direito um pouco todo dia pro dia de deploy em produção ser só mais um dia normal, tedioso, sem nenhuma adrenalina, porque todo mundo tem segurança que tá tudo testado e funcionando. Fazer as coisas direito é muito chato, eu sei. Deixa pra fazer código porco gambiarrado nos seus projetinhos pessoais. Mas em projeto dos outros, aprenda a ser profissional. Imagina se seu médico também fizesse programação orientada a gambiarras sem testes como você faz. Imagine se o médico do seu filho fizesse como você faz. Acho que não ia gostar né? Cresce, e age como profissional. Se ficaram com dúvidas, mandem nos comentários abaixo. Se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>testes</category><category>engenharia-de-software</category><category>agile</category><category>akitando</category></item><item><title>O Resultado do Modelo do Imperial College sobre a COVID-19 pode estar ERRADO</title><link>https://akitaonrails.github.io/2020/05/07/o-modelo-do-imperial-college-sobre-a-covid-19-pode-estar-errado/</link><guid isPermaLink="true">https://akitaonrails.github.io/2020/05/07/o-modelo-do-imperial-college-sobre-a-covid-19-pode-estar-errado/</guid><pubDate>Thu, 07 May 2020 05:02:00 -0300</pubDate><description>&lt;p&gt;Se você tem assistido os noticiários, todas as decisões tomadas em torno do controle da pandemia da COVID-19 se baseia no resultado das simulações publicadas no famoso &lt;a href="https://www.ingsa.org/covidtag/academic-papers/imperial-college/"target="_blank" rel="noopener"&gt;&amp;ldquo;Report 9&amp;rdquo;&lt;/a&gt; do Imperial College, de autoria do epidemiologista Neil Ferguson, o autor do paper do Imperial College que convenceu todo mundo sobre os lockdowns e que em 07/05/2020 renunciou do governo por ele próprio ter quebrado o lockdown (clique &lt;a href="https://www.theguardian.com/world/2020/may/06/uk-scientists-being-drawn-into-very-unpleasant-political-situation"target="_blank" rel="noopener"&gt;aqui&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;O &lt;a href="https://twitter.com/fernandoulrich"target="_blank" rel="noopener"&gt;Fernando Ulrich&lt;/a&gt; compartilhou comigo um artigo publicado hoje, dia 7 de maio de 2020, de autoria da programadora sênior, ex-engenheira do Google, Sue Denin, do blog Lockdown Sceptics. Recomendo fortemente que vocês leiam o relato da própria Sue em seu post:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você tem assistido os noticiários, todas as decisões tomadas em torno do controle da pandemia da COVID-19 se baseia no resultado das simulações publicadas no famoso <a href="https://www.ingsa.org/covidtag/academic-papers/imperial-college/"target="_blank" rel="noopener">&ldquo;Report 9&rdquo;</a> do Imperial College, de autoria do epidemiologista Neil Ferguson, o autor do paper do Imperial College que convenceu todo mundo sobre os lockdowns e que em 07/05/2020 renunciou do governo por ele próprio ter quebrado o lockdown (clique <a href="https://www.theguardian.com/world/2020/may/06/uk-scientists-being-drawn-into-very-unpleasant-political-situation"target="_blank" rel="noopener">aqui</a>).</p>
<p>O <a href="https://twitter.com/fernandoulrich"target="_blank" rel="noopener">Fernando Ulrich</a> compartilhou comigo um artigo publicado hoje, dia 7 de maio de 2020, de autoria da programadora sênior, ex-engenheira do Google, Sue Denin, do blog Lockdown Sceptics. Recomendo fortemente que vocês leiam o relato da própria Sue em seu post:</p>
<ul>
<li><a href="https://lockdownsceptics.org/code-review-of-fergusons-model/"target="_blank" rel="noopener">Code Review of Ferguson’s Model</a></li>
</ul>
<p>Se tudo isso for verdade, só tenho uma coisa a dizer: o código da simulação responsável pelos papers publicados pelo Imperial College é lastimável, pra dizer o mínimo.</p>
<p>PORÉM, e é um GRANDE PORÉM, eu não compartilho das opiniões não-técnicas do artigo original e sim das análises técnicas. Tanto o original quanto este não estão discordando do modelo e sim da implementação do código. E refutar uma análise levando em conta a pessoa fazendo a análise é desonestidade. Uma análise tem que independer da pessoa e das circunstâncias, caso contrário é somente enviesamento. Portanto eu não estou traduzindo o artigo da Sue nem citando ela como evidência e sim fazendo minha própria análise em cima do código e da sua execução, pura e simples.</p>
<p>Mas, com o impacto das decisões sendo tomadas por governos ao redor do mundo baseado nos resultados desse papers do Imperial College, é claro que não existe nada mais correto do que 100% desse código de simulação ser lançado ao escrutínio do público. Ele foi lançado faz só 15 dias, o que é too little, too late, e ainda está incompleto (falta os dados de entrada).</p>
<p>Só agora uma <em>versão</em> desse código foi publicado num repositório no GitHub, que você mesmo pode baixar aqui:</p>
<ul>
<li><a href="https://github.com/mrc-ide/covid-sim"target="_blank" rel="noopener">https://github.com/mrc-ide/covid-sim</a></li>
</ul>
<p>Eu não tenho competência para criticar o modelo propriamente dito publicado nos papers, mas qualquer programador consegue avaliar a qualidade do código que tenta implementar esse modelo, em alguns minutos. Vamos assumir que o modelo em si está correto. Porém é impossível dizer se o código que temos presente é uma implementação correta desse modelo.</p>
<p>Baseado no que o próprio Ferguson twitou e também no que ninguém menos que o lendário John Carmack (ex-CTO da Oculus, fundador da Id Software), o código que temos no repositório linkado acima foi bastante refatorado/retrabalhado por engenheiros da Microsoft e do GitHub.</p>
<p>Segundo Carmack, o código original que Ferguson levou mais de uma década criando - de todas as maneiras mais macarrônicas possíveis - era um único arquivo de C++ de mais de 15 mil linhas.</p>
<p><a href="https://user-images.githubusercontent.com/194979/80436185-6d26bc00-88b3-11ea-8d1d-c0854972193e.png"target="_blank" rel="noopener"><img src="https://user-images.githubusercontent.com/194979/80436185-6d26bc00-88b3-11ea-8d1d-c0854972193e.png" alt="Carmack"  loading="lazy" /></a></p>
<p>Precisou da limpeza do Carmack e de engenheiros da Microsoft e do GitHub pra minimamente dividir esse arquivão em mais arquivos menores, refatorar minimamente esse código, criar scripts de execução, e um teste de regressão mínimos.</p>
<p>Mesmo assim, o estado atual desse projeto é problemático.</p>
<p>Deixemos uma coisa clara: que atire a primeira pedra o programador que nunca fez um código macarrônico, sem testes, com números-mágicos sem documentação, variáveis com nomes impossíveis de entender pra que servem. Eu já fiz isso e eu garanto que qualquer outro programador fez o mesmo.</p>
<p>Também entendo que códigos de pesquisa acadêmica tendem a ter esse nível de qualidade mesmo. Porém, quando chegamos num ponto onde isso dificulta checar que a implementação realmente faz o que diz que faz, isso se torna um problema grave.</p>
<p>Eu convido todo programador a <a href="https://github.com/mrc-ide/covid-sim"target="_blank" rel="noopener">abrir o repositório</a> e explorar alguns dos arquivos. Se você pegar o que conhece como Clean Code e fazer exatamente o inverso, vai ter o código do Ferguson. Pegue qualquer lista de boas práticas, você vai achar o inverso nesse código. A complexidade ciclomática é enorme, cada função tem dezenas de linhas e o principal: não tem testes unitários, só tem um teste de regressão muito simples.</p>
<p>Você vai precisar de <code>cmake</code> instalado. Vou assumir que você sabe como instalar as dependências, está documentado no <a href="https://github.com/mrc-ide/covid-sim/blob/master/docs/build.md"target="_blank" rel="noopener">build.md</a> do projeto.</p>
<p>Também precisa ter python instalado. Ele já vem com dados de exemplo (que não são reais nem os que foram usados pra gerar os resultados do Report 9). Você pode executar a simulação assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>data/run_sample.py United_Kingdom</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O script de Python está meio que servindo como um Makefile e vai compilar o binário <code>CovidSim</code> e montar os parâmetros que ele aceita:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">cmd</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/PP:&#34;</span> <span class="o">+</span> <span class="n">pp_file</span><span class="p">,</span> <span class="c1"># Preparam file</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/P:&#34;</span> <span class="o">+</span> <span class="n">no_int_file</span><span class="p">,</span> <span class="c1"># Param file</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/O:&#34;</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outputdir</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;</span><span class="si">{0}</span><span class="s2">_NoInt_R0=</span><span class="si">{1}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">country</span><span class="p">,</span> <span class="n">r</span><span class="p">)),</span> <span class="c1"># Output</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/D:&#34;</span> <span class="o">+</span> <span class="n">wpop_file</span><span class="p">,</span> <span class="c1"># Input (this time text) pop density</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/M:&#34;</span> <span class="o">+</span> <span class="n">wpop_bin</span><span class="p">,</span> <span class="c1"># Where to save binary pop density</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/S:&#34;</span> <span class="o">+</span> <span class="n">network_bin</span><span class="p">,</span> <span class="c1"># Where to save binary net setup</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/R:</span><span class="si">{0}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">rs</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;98798150&#34;</span><span class="p">,</span> <span class="c1"># These four numbers are RNG seeds</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;729101&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;17389101&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;4797132&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">])</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No caso do exemplo, todas essas variáveis <code>pp_file</code>, <code>no_int_file</code>, etc vão configurar de quais arquivos ele vai ler dados como de população e pra que arquivos vai escrever o network binário que parece que serve de &ldquo;cache&rdquo; entre rodadas e os arquivos de saída finais.</p>
<p>Mais importante são os 4 últimos parâmetros, que são 2 Setup Seeds e 2 Run Seeds.</p>
<p>Essa simulação executa o modelo, ou cenário, dezenas de vezes variando os diversos parâmetros aleatoriamente. Como a Sue mencionou, é mais ou menos como rodar <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method"target="_blank" rel="noopener">Monte Carlo</a>.</p>
<p>Para leigos e iniciantes vale explicar o que é um &ldquo;seed&rdquo;. Em computação não existe forma de gerar um número verdadeiramente aleatório. O que conseguimos fazer é criar &ldquo;Geradores de Números Pseudo-Aleatórios&rdquo; (RNG). Ou seja é uma função como <code>random.seed(n)</code> no Python.</p>
<p>Por exemplo, digamos que queremos gerar uma sequência de 3 números aleatórios no console do Python, podemos fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">18</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">73</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">98</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesse exemplo estou pedindo pra gerar números entre 1 a 100, e a cada execução de <code>random.randint</code> ele gerou a sequência 18, 73, 98.</p>
<p>Agora digamos que eu quero gerar novamente a mesma sequência. Basta eu reinicializar o gerador mandando o mesmo seed &ldquo;1&rdquo; (pode ser qualquer número). Depois disso quando eu rodar <code>random.randint</code>, ele vai gerar a mesma sequência exatamente como antes.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">18</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">73</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">98</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode testar na sua máquina, colocando o seed &ldquo;1&rdquo; e vai ter a mesma sequência que a minha acima.</p>
<p>Isso é importante no nosso contexto porque como eu disse, simulações como esta da velocidade de contaminação e mortes da Covid-19 tecnicamente se assemelham a implementar Monte Carlo, que é a execução do cenário centenas ou milhares de vezes, variando o grau de alguns parâmetros aleatoriamente para ver o comportamento médio.</p>
<p>Porém, se eu inicializar a simulação com os mesmos seeds, como é o caso dos parâmetros Setup Seed e Run Seed do script anterior, os resultados deveriam ser exatamente os mesmos. Quando uma função recebe os mesmos parâmetros de entrada e gera exatamente os mesmos resultados, não importa quantas vezes você rode a função, chamamos isso de <strong>&ldquo;DETERMINÍSTICO&rdquo;</strong> e, claro, numa simulação como esta, é obrigatório que o resultado seja sempre determinístico.</p>
<p>Mas como a Sue encontrou primeiro, se você abrir este <a href="https://github.com/mrc-ide/covid-sim/issues/116"target="_blank" rel="noopener">ticket de Issue</a> do repositório no GitHub vai ver que uma equipe da Universidade de Edimburgh reportou um bug dizendo que eles testaram a execução com dados reais múltiplas vezes e os resultados vieram diferentes pros mesmos Seeds (!!) Mais precisamente, eles habilitaram um modo pra rodar mais rápido e descobriram que o resultado variava em torno de 80 mil mortes depois de 80 dias.</p>
<p><a href="https://github.com/mrc-ide/covid-sim/issues/116"target="_blank" rel="noopener"><img src="https://lockdownsceptics.org/wp-content/uploads/2020/05/image.png" alt="Não-Deterministico"  loading="lazy" /></a></p>
<p>Ou seja, só habilitando um modo de performance - que não muda nenhum arquivo de entrada e nenhum parâmetro da simulação, os resultados eram drasticamente diferentes em várias ordens de grandeza. E a resposta do Imperial College foi tipo:</p>
<p><em>&ldquo;Ah, eu sei é isso mesmo, isso acontece mas a gente tira a média e é isso aí&rdquo;</em></p>
<p>É como se eu fizesse pra você um programa pra calcular quanto imposto você tem que pagar. Você preenche tudo e manda rodar. Digamos que dá R$ 5 mil. Pronto, mas só pra garantir você roda de novo com os mesmos valores de entrada e a resposta dá diferente, R$ 20 mil! Aí você coça a cabeça e roda de novo, agora dá R$ 500 reais.</p>
<p>O que você faz agora? Quanto paga de imposto? A resposta da Imperial College foi, simplificadamente, o equivalente a dizer:</p>
<p><em>&ldquo;Ah, é isso mesmo, dá diferença, mas relaxa, soma 5 mil com 20 mil com 500 e divide por 3 &hellip; vai dar &hellip; R$ 8.500, pronto, paga isso&rdquo;</em></p>
<p>Eu não sei se rodei da forma correta, mas executando <code>data/run_sample.py United_Kingdom</code> eu obtive os seguintes resultados:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>d2fb079633352adafde041fc70f2948e0df5c9c7ee5780d280494470f1a283cd 
run_1/United_Kingdom_NoInt_R0=3.0.avNE.age.xls
b888b67c10a660ba9184a1c048058dc3e73f5a9623efb7a34980f09a0eaffcc1 
run_1/United_Kingdom_NoInt_R0=3.0.avNE.severity.adunit.xls
39f8adeeaf52d38af6851c4633f33e6484e598e3c8cb52b6b5a620394e11076a 
run_1/United_Kingdom_NoInt_R0=3.0.avNE.severity.xls
40bd33d65520dbcef8ec886fdd2f45bf49a4f07cbbbbf93f7b86c4c14f1261d6 
run_1/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.age.xls
8c2aaacb033794480b8c5972c36c3cca4a911430e8c1e8ea0345be523254f50c 
run_1/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.severity.adunit.xls
5e87d7a9b460158902133160137fc92fdb598e1363e50d91458a4dc8e54a15b7 
run_1/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.severity.xls</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esses são os SHA256 dos arquivos Excel (.xls) que ele gera no final. O script rodou em 6 cores na minha máquina virtual, consumindo mais de 9 GB de RAM e levando um tempão pra executar com os dados fake que vem no projeto. Com os dados de verdade, rodando em máquinas mais parrudas, ele vai consumir fácil acima de 20 GB. Dependendo dos dados de qual país, pode chegar a mais de 200 GB de RAM.</p>
<p>Outra coisa, se alguém está confundindo a natureza de &ldquo;resultados aleatórios&rdquo; (dentro de uma pequena margem) por causa de erros de arredondamento de fontos flutuantes ou resultado usando paralelismo por GPU, o código não tem &ldquo;rounds&rdquo; que poderiam acumular erros e eu estou rodando tudo em uma máquina virtual sem GPU, só CPU puro (mesmo o projeto tendo um pequeno suporte a OpenMP).</p>
<p>Se eu apagar os arquivos intermediários de network que ele gera como &ldquo;cache&rdquo; e rodar tudo de novo, com exatamente os mesmos parâmetros (e note que eu mostrei acima que os Seeds estão hard-coded, portanto os resultados deveriam ser os mesmos), eis os resultados gerados:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>efd5b1a06abf98ebbba2f59752a7a63e0fe96eb6c2857fdba8b276ac6f338bb2 
run_2/United_Kingdom_NoInt_R0=3.0.avNE.age.xls
c3a20e4860b755553d0857c41c63112da285377b7c81886666efe326a46cc572 
run_2/United_Kingdom_NoInt_R0=3.0.avNE.severity.adunit.xls
660ef8752181bf338db2eca52b9506958bdb8a9b8eec782ad6e53d5b2b7a0f6e 
run_2/United_Kingdom_NoInt_R0=3.0.avNE.severity.xls
ff8459daa5d95d8bd21b64ee1314ed2b5922b45a0bf69394f36bb988d0e24984 `\
run_2/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.age.xls
a2ff7a91dcd9772de6cf9a4ea3c271af47d93706d2968aaeed36b90522cf34c4 
run_2/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.severity.adunit.xls
c965db754f3850f59b4f617361cbd12a7a60991e7dbab81defe15022bf3e9741 
run_2/United_Kingdom_PC7_CI_HQ_SD_R0=3.0.avNE.severity.xls</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que todos os SHA256, as impressões digitais dos arquivos, são diferentes. Não me cite afirmando que esse é o erro, pode ser que eu tenha deixado escapar algum parâmetro aleatório, mas não parece.</p>
<p>Pra visualizar o conteúdo dos arquivos gerados, eu usei os scripts de R que estão no mesmo projeto, por exemplo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-R" data-lang="R"><span class="line"><span class="cl"><span class="n">cd</span> <span class="n">Rscripts</span>
</span></span><span class="line"><span class="cl"><span class="n">Rscript</span> <span class="n">CompareRuns.R</span> <span class="n">..</span><span class="o">/</span><span class="n">run_1</span> <span class="n">..</span><span class="o">/</span><span class="n">run_2</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O resultado das simulações são arquivos XLS que são processados por scripts de R pra plotar em forma de gráficos, eis alguns exemplos:</p>
<p><a href="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/685/Death_Cumulative_Incidence.png"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/685/Death_Cumulative_Incidence.png" alt="Death Cumulative Incidence"  loading="lazy" /></a></p>
<p><a href="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/686/Death_Incidence.png"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/686/Death_Incidence.png" alt="Death Incidence"  loading="lazy" /></a></p>
<p>Esses são só dois exemplos mas ele gera uma dúzia de outros gráficos.</p>
<p>Eu fico na dúvida porque o projeto tem um diretório de testes de regressão. Essa é outra parte lamentável aliás, são só 2 míseros scripts de teste de regressão, parecidos com esse <code>data/run_sample.py</code> e eles se chamam <code>tests/regressiontest_UK_100th.py</code> e <code>tests/regressiontest_US_based.py</code>. Eles fazem algo parecido com o que eu acabei de mostrar acima: rodam a simulação inteira, gera os arquivos e compara o hash dos arquivos gerados com o hash hard-coded no teste.</p>
<p>São basicamente testes de integração ou black-box tests só. Por acaso, os testes passam, por isso eu falei pra não afirmar que os meus resultados acima são evidência de bug ainda. Mas pode ser que os dados usados pro teste, que são mais simples, levam a resultados que fazem bater - famoso caso de teste feito pra passar. Se alguém achar o que eu executei errado acima - ou não - não deixe de comentar.</p>
<p>Agora, este ponto dos testes é um absurdo. Um dos projetos de software mais importante da nossa atualidade não tem nenhum teste unitário. É uma mera comparação de hashes dos arquivos de saída.</p>
<p>Como o Carmack notou, tem muito código em C++ que parece que foi uma tradução direta de FORTRAN antigo. Não sei quanto procede, mas você encontra em todo lugar funções bonitas e bem documentadas como esta em <a href="https://github.com/mrc-ide/covid-sim/blob/master/src/SetupModel.cpp"target="_blank" rel="noopener">SetupModel.cpp</a>, na linha 1932 &hellip; isso 1932 (olha o TAMANHO desse arquivo!):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">NC</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">i</span> <span class="o">=</span> <span class="n">k</span> <span class="o">%</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">j</span> <span class="o">=</span> <span class="n">k</span> <span class="o">/</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">f2</span> <span class="o">=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">j</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">j</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">j</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">j</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">ncw</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="n">i</span><span class="p">].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="n">i</span><span class="p">].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">ncw</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="n">i</span><span class="p">].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="n">i</span><span class="p">].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">j</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">j</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[</span><span class="n">j</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">j</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">ncw</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">f2</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">I</span><span class="p">;</span> <span class="n">f3</span> <span class="o">+=</span> <span class="n">Cells</span><span class="p">[(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">nch</span> <span class="o">+</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)].</span><span class="n">S</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="n">Cells</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">L</span> <span class="o">=</span> <span class="n">f3</span><span class="p">;</span> <span class="n">Cells</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">R</span> <span class="o">=</span> <span class="n">f2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou em <a href="https://github.com/mrc-ide/covid-sim/blob/688db07a08a2a2416a37bb1092fde1d07a2b6c39/src/Sweep.cpp"target="_blank" rel="noopener">Sweep.cpp</a> que você uma função como <code>TreatSweep</code> que vai da linha 1165 até 1765, ou seja uma única função com nada menos que SEISCENTAS LINHAS. E isso não é exceção, a maioria das funções principais tem de dezenas a centenas de linhas.</p>
<p>É a norma nesse código achar trechos ilegíveis como este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">PlaceTypeNum</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o">!=</span> <span class="n">P</span><span class="p">.</span><span class="n">HotelPlaceType</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#pragma omp parallel for private(i,k,d,q,s2,s3,t3,l,m,x,y) schedule(static,1000) reduction(+:t)
</span></span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">P</span><span class="p">.</span><span class="n">N</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">k</span> <span class="o">=</span> <span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">PlaceLinks</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">k</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">q</span> <span class="o">=</span> <span class="p">(</span><span class="n">P</span><span class="p">.</span><span class="n">LatentToSymptDelay</span> <span class="o">&gt;</span> <span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">recovery_or_death_time</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">TimeStep</span><span class="p">)</span> <span class="o">?</span> <span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">recovery_or_death_time</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="nl">TimeStep</span> <span class="p">:</span> <span class="n">P</span><span class="p">.</span><span class="n">LatentToSymptDelay</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="n">s2</span> <span class="o">=</span> <span class="nf">fabs</span><span class="p">(</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">infectiousness</span><span class="p">)</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">TimeStep</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">PlaceTypeTrans</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">				<span class="n">x</span> <span class="o">=</span> <span class="n">s2</span> <span class="o">/</span> <span class="n">P</span><span class="p">.</span><span class="n">PlaceTypeGroupSizeParam1</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">				<span class="n">d</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span> <span class="n">l</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">q</span> <span class="o">/</span> <span class="n">P</span><span class="p">.</span><span class="n">TimeStep</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="k">for</span> <span class="p">(</span><span class="n">m</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">m</span> <span class="o">&lt;</span> <span class="n">l</span><span class="p">;</span> <span class="n">m</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">x</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">infectiousness</span><span class="p">[</span><span class="n">m</span><span class="p">];</span> <span class="n">d</span> <span class="o">*=</span> <span class="p">((</span><span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">y</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">				<span class="n">s3</span> <span class="o">=</span> <span class="p">((</span><span class="kt">double</span><span class="p">)(</span><span class="n">Places</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">].</span><span class="n">group_size</span><span class="p">[</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">PlaceGroupLinks</span><span class="p">[</span><span class="n">j</span><span class="p">]]</span> <span class="o">-</span> <span class="mi">1</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">				<span class="n">x</span> <span class="o">*=</span> <span class="p">((</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">infectiousness</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="p">(</span><span class="n">P</span><span class="p">.</span><span class="n">SymptPlaceTypeContactRate</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">P</span><span class="p">.</span><span class="n">SymptPlaceTypeWithdrawalProp</span><span class="p">[</span><span class="n">j</span><span class="p">]))</span> <span class="o">:</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="n">l</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">recovery_or_death_time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="k">for</span> <span class="p">(;</span> <span class="n">m</span> <span class="o">&lt;</span> <span class="n">l</span><span class="p">;</span> <span class="n">m</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">x</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">infectiousness</span><span class="p">[</span><span class="n">m</span><span class="p">];</span> <span class="n">d</span> <span class="o">*=</span> <span class="p">((</span><span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">y</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">				<span class="n">t3</span> <span class="o">=</span> <span class="n">d</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="n">x</span> <span class="o">=</span> <span class="n">P</span><span class="p">.</span><span class="n">PlaceTypePropBetweenGroupLinks</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">*</span> <span class="n">s2</span> <span class="o">/</span> <span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="n">Places</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">].</span><span class="n">n</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="n">d</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span> <span class="n">l</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">q</span> <span class="o">/</span> <span class="n">P</span><span class="p">.</span><span class="n">TimeStep</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="k">for</span> <span class="p">(</span><span class="n">m</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">m</span> <span class="o">&lt;</span> <span class="n">l</span><span class="p">;</span> <span class="n">m</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">x</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">infectiousness</span><span class="p">[</span><span class="n">m</span><span class="p">];</span> <span class="n">d</span> <span class="o">*=</span> <span class="p">((</span><span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">y</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">				<span class="n">x</span> <span class="o">*=</span> <span class="p">((</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">infectiousness</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="p">(</span><span class="n">P</span><span class="p">.</span><span class="n">SymptPlaceTypeContactRate</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">P</span><span class="p">.</span><span class="n">SymptPlaceTypeWithdrawalProp</span><span class="p">[</span><span class="n">j</span><span class="p">]))</span> <span class="o">:</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="n">l</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">Hosts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">recovery_or_death_time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="k">for</span> <span class="p">(;</span> <span class="n">m</span> <span class="o">&lt;</span> <span class="n">l</span><span class="p">;</span> <span class="n">m</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">x</span> <span class="o">*</span> <span class="n">P</span><span class="p">.</span><span class="n">infectiousness</span><span class="p">[</span><span class="n">m</span><span class="p">];</span> <span class="n">d</span> <span class="o">*=</span> <span class="p">((</span><span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">y</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">				<span class="n">t</span> <span class="o">+=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">t3</span> <span class="o">*</span> <span class="n">d</span><span class="p">)</span> <span class="o">*</span> <span class="n">s3</span> <span class="o">+</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">d</span><span class="p">)</span> <span class="o">*</span> <span class="p">(((</span><span class="kt">double</span><span class="p">)(</span><span class="n">Places</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">].</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span> <span class="o">-</span> <span class="n">s3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#34;%lg  &#34;</span><span class="p">,</span> <span class="n">t</span> <span class="o">/</span> <span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="n">P</span><span class="p">.</span><span class="n">N</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Relembrando que esta é a versão que já passou por uma MEGA-LIMPEZA de refatoração das mãos de engenheiros da Microsoft e GitHub.</p>
<p>Imaginem o código original do Ferguson como era, o código que gerou os resultados do Report 9.</p>
<p>A única forma de confiar nesse código é refatorar TODAS essas funções de centenas de linhas em funções menores de single-responsability, cobrir uma a uma com testes unitários, trocar todas as variáveis ilegíveis por nomes corretos, remover todos os números mágicos hard coded em tabelas de constantes e assim por diante.</p>
<p>Isso sem contar o <a href="https://github.com/mrc-ide/covid-sim/issues/116"target="_blank" rel="noopener">problema reportado pela Universidade de Edinburgh</a> onde parece estar acontecendo, no mínimo problemas de Race Condition. Todo mundo sabe que programar Threads é difícil, ainda mais em C++ sem usar patterns e bibliotecas adequadas pra isso.</p>
<p>Em simulações desse tipo chamados &ldquo;estocásticos&rdquo; é de fato possível que exista uma aleatoriedade por causa dos cálculos envolvidos que envolvem coisas como ponto flutuante (que pode dar pequenas diferenças mesmo). Mas não sei se é esse o caso, especialmente quando os próprios desenvolvedores do código não tem essa certeza. Na Issue que citei acima, quando questionados de porque o resultado dava diferente, a resposta foi que &ldquo;sim, sabemos que tem problema, mas é porque está rodando em multithreads, tente rodar em 1 thread só&rdquo;</p>
<p>Porém a universidade respondeu &ldquo;mas é isso mesmo que estamos fazendo&rdquo;, e a parte preocupante é que o código de fato é tão difícil de entender que nem os programadores envolvidos tem certeza de qual deveria ser o comportamento correto.</p>
<p>Veja o <a href="https://github.com/mrc-ide/covid-sim/commit/6416d017a3a1f10d04314a85cb3eb9e419e4ca45"target="_blank" rel="noopener">commit de 3 dias atrás do Ferguson</a> entitulado &ldquo;Bugs found - code is stable and seems deterministic again. WIll finish more tests and send pull request&rdquo;. Tipo, se olhar o histórico do Git vê-se de novo, que não se segue boas práticas. Esse commit tem um monte de modificação estética (adiciona espaço, tira espaço) e a mudança importante de verdade são meia dúzia de linhas perdidas no meio de 105 adições e 81 deleções.</p>
<p>Se o comportamento &ldquo;normal&rdquo; é mesmo ser não-determinístico, porque existe este commit do próprio Ferguson dizendo &ldquo;corrigi e agora está determinístico&rdquo; (e pelo que relatei acima, não parece estar). Ou seja, o problema todo escala pro nível do desenvolvedor não saber qual deveria ser o resultado correto.</p>
<p>Falando em histórico de Git, o <a href="https://github.com/mrc-ide/covid-sim/commit/bd87d475563cd54978325bf73ce45e80a7c8de65"target="_blank" rel="noopener">primeiro commit</a> desse repositório, que data de 15 dias atrás tem o título de &ldquo;Squash history for public release&rdquo;.</p>
<p>Sabe quando seu histórico tá uma zona e você não quer mostrar a bagunça? O que você faz? Um squash do histórico todo num único commit. Sabe todo trabalho que os engenheiros da Microsoft e GitHub fizeram? Não está no histórico, foi dado um squash de tudo! Isso é um absurdo pra um projeto público dessa importância.</p>
<p>Claramente o projeto original que era um arquivão de 15 mil linhas jamais passaria pelo escrutínio de nenhum programador, nem precisa ser um especialista ou um Carmack. Se depois da limpeza toda, esse é o resultado e ainda tem bugs, significa que com alta probabilidade, as projeções todas divulgadas pela Imperial College não podem ser confiadas.</p>
<p>Existe uma <a href="https://github.com/mrc-ide/covid-sim/issues/144"target="_blank" rel="noopener">Issue aberta</a> (que pelo menos até agora não foi apagada ainda) que pede a divulgação do histórico de Git completo e do código original. Isso ainda não foi cumprido.</p>
<p>Existe outra <a href="https://github.com/mrc-ide/covid-sim/issues/165"target="_blank" rel="noopener">Issue aberta</a> entitulada &ldquo;We, the undersigned software engineers, call for any papers based on this codebase to be immediately retracted.&rdquo; que está servindo como um aviso/abaixo-assinado onde diversos programadores que, como eu e a Sue, olhamos essa bagunça, estamos avisando que os resultados gerados por esse código não podem ser confiados.</p>
<p>A única forma de sabermos se os resultados são confiáveis é que os dados originais de entrada, os arquivos de input, sejam divulgados e que diversos programadores e entidades rodem a simulação múltiplas vezes com os mesmos arquivos, usando os mesmos Seeds. Daí os arquivos finais de resultado <strong>OBRIGATORIAMENTE</strong> precisam ser iguais entre si.</p>
<p>Porém, se eles não baterem, só confirmaria a suspeita que o código que veio sendo usado até agora contém bugs sérios, em boa parte devido à todas as más práticas de desenvolvimento de software. E esses bugs tornam esse software NÃO-DETERMINÍSTICO.</p>
<p>O <a href="https://github.com/mrc-ide/covid-sim"target="_blank" rel="noopener">código fonte está no GitHub</a>, se você for programador, convido todos a fazerem o mesmo que eu: avaliar o código e se possível escrutinizar ao máximo cada canto obscuro.</p>
<p>Porém, esse código é incompleto. Não sabemos quais resultados publicados foram gerados com qual versão desse software. Sequer sabemos como era o código original que provavelmente foi o mais usado. Também não temos os dados originais de entrada pra repetirmos as simulações e comparar os resultados.</p>
<p>O corolário é que infelizmente precisamos duvidar de tudo que já foi divulgado sobre esse modelo até agora até que as peças que faltam sejam divulgadas. E este é um exemplo de mau código e mau gerenciamento de lifecycle de software, que não deve servir de modelo pra nenhum projeto de software.</p>
<p>Com o que temos até agora divulgado não é possível concluir com 100% de certeza nem que a implementação está correta e nem que está errada, porque faltam as peças que eu disse. Mas com as divergências que temos visto, e com a qualidade do software que acabei de explicar e que vocês podem ver com os próprios olhos, também não é uma impossibilidade acreditar que os resultados gerados por este software estejam errados em algumas ordens de grandeza.</p>
<p><strong>Atualização:</strong> tenho visto muitos feedbacks deste post depois que publiquei dizendo coisas do tipo &ldquo;o mundo acadêmico os códigos são assim mesmo, é normal ser sujo, isso não importa&rdquo;. E de fato, eu sei disso e não teria nenhum problema se a execução desse código fornecesse resultados consistentes todas vezes. Mas a partir do momento que se determina que os resultados não são confiáveis, e não tem como ler no código onde está o erro por causa da sujeira, aí temos um problema.</p>
]]></content:encoded><category>ciencia</category><category>testes</category><category>engenharia-de-software</category></item><item><title>SmallTips: Rodando o GitLab CI Runner Localmente</title><link>https://akitaonrails.github.io/2018/04/28/smalltips-rodando-o-gitlab-ci-runner-localmente/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/04/28/smalltips-rodando-o-gitlab-ci-runner-localmente/</guid><pubDate>Sat, 28 Apr 2018 18:35:00 -0300</pubDate><description>&lt;p&gt;Se tem uma coisa que você sempre deve fazer é manter sua suíte de testes. A cada bug corrigido, a cada nova funcionalidade, adicione novos testes.&lt;/p&gt;
&lt;p&gt;Nas últimas semanas trabalhei muito pesado, literalmente codificando sem parar, sete dias por semana. Lembra daquela história de &amp;ldquo;para de reclamar que não tem tempo, o que você faz da meia-noite às 6 da manhã?&amp;rdquo;? Pois é, isso não se aplica a mim.&lt;/p&gt;
&lt;p&gt;E é justamente quando você está com sono e exausto que começa a cometer erros. Mesmo que o seu código rode localmente, você vai se ferrar em uma dessas sessões de código às 3 da manhã.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se tem uma coisa que você sempre deve fazer é manter sua suíte de testes. A cada bug corrigido, a cada nova funcionalidade, adicione novos testes.</p>
<p>Nas últimas semanas trabalhei muito pesado, literalmente codificando sem parar, sete dias por semana. Lembra daquela história de &ldquo;para de reclamar que não tem tempo, o que você faz da meia-noite às 6 da manhã?&rdquo;? Pois é, isso não se aplica a mim.</p>
<p>E é justamente quando você está com sono e exausto que começa a cometer erros. Mesmo que o seu código rode localmente, você vai se ferrar em uma dessas sessões de código às 3 da manhã.</p>
<p>A suíte de testes foi minha salvação nesses momentos — meu copiloto, me trazendo de volta à razão sempre que a noite ficava pesada demais.</p>
<p>Mantenho meu próprio servidor GitLab para todos os projetos de clientes e internos da empresa. É só um pouco de paranoia, para garantir que meu código fica comigo. E o GitLab é uma ferramenta fantástica, ainda mais porque tem <a href="https://about.gitlab.com/features/gitlab-ci-cd/"target="_blank" rel="noopener">seu próprio sistema de Integração Contínua</a>.</p>
<p>Basta adicionar o arquivo <code>.gitlab-ci.yml</code> ao projeto, criar uma branch, fazer o push e abrir um Merge Request — o CI entra em ação, com <a href="https://docs.gitlab.com/ee/ci/yaml/#jobs"target="_blank" rel="noopener">jobs paralelos</a> se você quiser. Mesmo quando eu esquecia de rodar os testes localmente, o CI não me deixava passar.</p>
<p>Manter o CI funcionando exige que você pare e cuide das specs periodicamente. Às vezes você vai se perguntar: por que esse teste passa na minha máquina mas continua falhando no servidor de CI?</p>
<p>É aí que fica muito útil rodar a imagem Docker do CI localmente, para resolver de vez aquelas inconsistências de ambiente.</p>
<p>Você consegue fazer isso rodando o próprio GitLab CI Runner. Ele lê o <code>.gitlab-ci.yml</code> do seu projeto e executa tudo localmente via Docker. Qualquer problema que aparece no servidor vai se reproduzir localmente também. Com a vantagem de que você não precisa entrar em fila caso haja outros jobs aguardando execução (muitos desenvolvedores trabalhando, poucas máquinas disponíveis para processar tudo na hora). E você não fica poluindo essa fila com jobs desnecessários.</p>
<p>Se você seguiu minhas recomendações e instalou a excelente distro Arch Linux (ou meu derivado favorito, Manjaro Gnome), pode instalar o runner pelo AUR assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacaur -S gitlab-runner</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Caso contrário, verifique os repositórios da sua distro ou baixe o binário <a href="https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/install/bleeding-edge.md#download-the-standalone-binaries"target="_blank" rel="noopener">aqui</a>. Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://gitlab-runner-downloads.s3.amazonaws.com/master/binaries/gitlab-runner-linux-amd64
sudo mv gitlab-runner-linux-amd64 /usr/local/bin
sudo chmod &#43;x /usr/local/bin/gitlab-runner</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com o runner instalado, suponha que você tenha um trecho assim no seu <code>.gitlab-ci.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">./bin/cc-test-reporter before-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">bundle exec rspec --exclude-pattern &#34;**/features/**/*_spec.rb&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">after_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">./bin/cc-test-reporter after-build --exit-code $? || true</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode configurar vários jobs de teste rodando em paralelo. Recomendo separar: testes JS de front-end, testes unitários de back-end com Rspec/minitest, testes de feature com Capybara, verificações de segurança com Brakeman, por exemplo. Cada um roda em paralelo e você não precisa esperar tudo terminar se só quer ver o resultado dos testes de JS. Feedback mais rápido, correção imediata.</p>
<p>Localmente, a partir da raiz do projeto, basta rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gitlab-runner exec docker backend</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Claro, assumindo que você já tem o Docker instalado e configurado. Se não tiver, leia <a href="https://wiki.archlinux.org/index.php/Docker#Installation"target="_blank" rel="noopener">o excelente artigo do Arch Wiki sobre Docker</a> — basicamente é instalar o pacote Docker e se adicionar ao grupo docker.</p>
<p>É isso! Super simples, quase sem fricção, e o runner faz tudo que o servidor GitLab CI faria.</p>
<p>Um porém: infelizmente, parece que você <a href="https://gitlab.com/gitlab-org/gitlab-runner/issues/2409"target="_blank" rel="noopener">não consegue salvar cache ou artefatos</a> entre execuções (gems, pacotes npm, etc.), então toda vez que o runner local roda, ele começa do zero. Isso o torna menos útil para uso contínuo, mas ainda é mais rápido do que fazer push para o servidor e esperar sua vez numa fila cheia.</p>
<p>Já me salvou algumas vezes.</p>
]]></content:encoded><category>devops</category><category>containers</category><category>testes</category></item><item><title>Iniciante: Longa Vida ao PhantomJS - Vamos Usar Chrome Headless Agora</title><link>https://akitaonrails.github.io/2017/10/31/iniciante-longa-vida-ao-phantomjs-vamos-usar-chrome-headless-agora/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/10/31/iniciante-longa-vida-ao-phantomjs-vamos-usar-chrome-headless-agora/</guid><pubDate>Tue, 31 Oct 2017 19:48:00 -0200</pubDate><description>&lt;p&gt;Se você faz Feature Specs — o processo de subir um servidor real da aplicação e um browser headless real para testar funcionalidades do ponto de vista do usuário — então você conhece o &lt;a href="https://github.com/teamcapybara/capybara/issues/1860"target="_blank" rel="noopener"&gt;Capybara&lt;/a&gt; e um dos seus drivers mais famosos, o &lt;a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener"&gt;Poltergeist&lt;/a&gt;. O Poltergeist encapsula o PhantomJS, que é um browser headless baseado em WebKit bastante conhecido.&lt;/p&gt;
&lt;p&gt;Só que o WebKit é famoso por ser &lt;strong&gt;muito&lt;/strong&gt; complicado de lidar. Então dá pra imaginar o pesadelo que é manter o PhantomJS, que é basicamente manter um browser completo como Chrome ou Safari.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você faz Feature Specs — o processo de subir um servidor real da aplicação e um browser headless real para testar funcionalidades do ponto de vista do usuário — então você conhece o <a href="https://github.com/teamcapybara/capybara/issues/1860"target="_blank" rel="noopener">Capybara</a> e um dos seus drivers mais famosos, o <a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener">Poltergeist</a>. O Poltergeist encapsula o PhantomJS, que é um browser headless baseado em WebKit bastante conhecido.</p>
<p>Só que o WebKit é famoso por ser <strong>muito</strong> complicado de lidar. Então dá pra imaginar o pesadelo que é manter o PhantomJS, que é basicamente manter um browser completo como Chrome ou Safari.</p>
<p>Não é de espantar que, quando o time do Chrome anunciou a disponibilidade do <a href="https://developers.google.com/web/updates/2017/04/headless-chrome"target="_blank" rel="noopener">Chrome Driver</a>, o mantenedor do PhantomJS <a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener">decidiu se afastar do projeto</a>.</p>
<p>Se você conhece os contribuidores do PhantomJS, agradeça a eles — o projeto nos ajudou a construir funcionalidades de usuário mais sólidas por anos.</p>
<p>Dito isso, não precisa se preocupar. É perfeitamente possível substituir Poltergeist/PhantomJS por Selenium WebDriver/Chrome Driver no seu setup de RSpec/Capybara.</p>
<p>Meu amigo <a href="https://www.lucascaton.com.br/2017/06/22/how-to-run-your-feature-specs-using-capybara-and-headless-chrome/"target="_blank" rel="noopener">Lucas Caton</a> escreveu sobre isso em junho deste ano. Vale acompanhar o blog dele também.</p>
<p>Se você usa Linux com o browser Chromium, não precisa instalar nada — o Chrome Driver já vem com o Chromium. Caso contrário, instale os pacotes adequados para o seu sistema operacional. Por exemplo: <code>brew install chromedriver</code> no OS X, ou <code>pacaur -S chromedriver</code> no Arch Linux se preferir não ter o Chromium instalado. No Ubuntu, talvez seja necessário <a href="https://askubuntu.com/questions/539498/where-does-chromedriver-install-to"target="_blank" rel="noopener">ajustar seu PATH</a>.</p>
<p>Regra geral: instale o Chromium.</p>
<p>No meu caso, as mudanças foram as seguintes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"># Gemfile
</span></span><span class="line"><span class="cl"><span class="gd">- gem &#34;poltergeist&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ gem &#34;selenium-webdriver&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ gem &#34;rspec-retry&#34;
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois, na configuração do Capybara:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl">Capybara.server = :puma # Até o setup funcionar
</span></span><span class="line"><span class="cl">Capybara.server = :puma, { Silent: true } # Para limpar o output dos testes
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">- Capybara.register_driver :poltergeist do |app|
</span></span></span><span class="line"><span class="cl"><span class="gd">-   options = {
</span></span></span><span class="line"><span class="cl"><span class="gd">-     timeout: 3.minutes,
</span></span></span><span class="line"><span class="cl"><span class="gd">-     phantomjs_options: [
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--proxy-type=none&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--load-images=no&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--ignore-ssl-errors=yes&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--ssl-protocol=any&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--web-security=false&#39;
</span></span></span><span class="line"><span class="cl"><span class="gd">-     ]
</span></span></span><span class="line"><span class="cl"><span class="gd">-   }
</span></span></span><span class="line"><span class="cl"><span class="gd">-   Capybara::Poltergeist::Driver.new(app, options)
</span></span></span><span class="line"><span class="cl"><span class="gd">- end
</span></span></span><span class="line"><span class="cl"><span class="gd">- Capybara.javascript_driver = :poltergeist
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gi">+ Capybara.register_driver :chrome do |app|
</span></span></span><span class="line"><span class="cl"><span class="gi">+   capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
</span></span></span><span class="line"><span class="cl"><span class="gi">+     chromeOptions: {
</span></span></span><span class="line"><span class="cl"><span class="gi">+       args: %w[ no-sandbox headless disable-popup-blocking disable-gpu window-size=1280,1024]
</span></span></span><span class="line"><span class="cl"><span class="gi">+     }
</span></span></span><span class="line"><span class="cl"><span class="gi">+   )
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+   Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
</span></span></span><span class="line"><span class="cl"><span class="gi">+ end
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Capybara::Screenshot.register_driver :chrome do |driver, path|
</span></span></span><span class="line"><span class="cl"><span class="gi">+   driver.save_screenshot(path)
</span></span></span><span class="line"><span class="cl"><span class="gi">+ end
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Capybara.javascript_driver = :chrome
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Capybara.default_max_wait_time = 5 # aumente esse timeout se sua aplicação for pesada para carregar
</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em feature specs, às vezes o próprio Rails demora um pouco para subir, compilar assets e tal, e o primeiro spec pode dar timeout. Para evitar falha na execução dos testes, recomendo adicionar a gem <code>rspec-retry</code> como fiz acima, e incluir o seguinte no seu <code>spec/rails_helper.rb</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rspec/retry&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">RSpec</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># mostra o status de retry no processo do spec</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">verbose_retry</span> <span class="o">=</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Tenta duas vezes (repete uma vez)</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">default_retry_count</span> <span class="o">=</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Faz retry apenas quando o Selenium lança Net::ReadTimeout</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">exceptions_to_retry</span> <span class="o">=</span> <span class="o">[</span><span class="no">Net</span><span class="o">::</span><span class="no">ReadTimeout</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é isso. Não precisei alterar nenhum dos meus feature specs e todos rodaram perfeitamente. Parabéns às equipes que mantêm o Capybara e o Selenium-WebDriver por suportarem essa mudança.</p>
<p>Se você também desenvolve em Node.js, provavelmente já usou algo como o Casper, que dizem suportar Chrome Headless também. Mas já que estamos no assunto, vale muito conferir o <a href="https://github.com/GoogleChrome/puppeteer"target="_blank" rel="noopener">Puppeteer</a>, da própria equipe do Google. É uma biblioteca baseada em Promises onde você escreve código assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">puppeteer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;puppeteer&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">puppeteer</span><span class="p">.</span><span class="nx">launch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://example.com&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span><span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;example.png&#39;</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então sim, o Chrome Headless parece uma ótima opção — afinal, a maioria dos usuários já usa o Chrome, o que significa que teremos feature specs mais confiáveis e ferramentas de web crawling mais robustas.</p>
]]></content:encoded><category>rails</category><category>testes</category><category>tutoriais</category></item><item><title>Como Não Zerar sua Cota de Desenvolvimento no Pusher</title><link>https://akitaonrails.github.io/2017/10/27/como-nao-zerar-sua-cota-de-desenvolvimento-no-pusher/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/10/27/como-nao-zerar-sua-cota-de-desenvolvimento-no-pusher/</guid><pubDate>Fri, 27 Oct 2017 13:43:00 -0200</pubDate><description>&lt;p&gt;Se você está desenvolvendo com WebSockets (notificações em tempo real, chat em tempo real, etc), uma das melhores opções SaaS disponíveis ainda é o &lt;a href="pusher.com"&gt;&lt;strong&gt;Pusher&lt;/strong&gt;&lt;/a&gt;. Sempre foi confiável.&lt;/p&gt;
&lt;p&gt;Para cada aplicação que você cria, ele oferece ambientes separados de desenvolvimento, staging e produção, com tokens de chave/segredo independentes.&lt;/p&gt;
&lt;p&gt;Um problema que encontrei esses dias foi que eu estava zerando rapidamente a cota gratuita de mensagens do ambiente de desenvolvimento (200.000 mensagens por dia). O motivo era que toda a minha equipe usava a mesma chave, e o servidor de Integração Contínua fazia conexões reais sempre que rodava no mesmo ambiente. Isso consome tudo rapidamente e bloqueia tanto o desenvolvimento quanto o CI.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você está desenvolvendo com WebSockets (notificações em tempo real, chat em tempo real, etc), uma das melhores opções SaaS disponíveis ainda é o <a href="pusher.com"><strong>Pusher</strong></a>. Sempre foi confiável.</p>
<p>Para cada aplicação que você cria, ele oferece ambientes separados de desenvolvimento, staging e produção, com tokens de chave/segredo independentes.</p>
<p>Um problema que encontrei esses dias foi que eu estava zerando rapidamente a cota gratuita de mensagens do ambiente de desenvolvimento (200.000 mensagens por dia). O motivo era que toda a minha equipe usava a mesma chave, e o servidor de Integração Contínua fazia conexões reais sempre que rodava no mesmo ambiente. Isso consome tudo rapidamente e bloqueia tanto o desenvolvimento quanto o CI.</p>
<p>Na verdade, fazer conexões reais a sistemas externos em situações de teste não é boa prática. Os testes podem falhar aleatoriamente por vários motivos, então sempre devemos mockar essas dependências. Mas mockar um sistema complexo (WebSockets e HTTP) como o Pusher não é trivial.</p>
<p>Felizmente, encontrei o <a href="https://github.com/tristandunn/pusher-fake"target="_blank" rel="noopener">Pusher Fake</a>. Ele basicamente implementa todas as APIs e endpoints WebSocket necessários para imitar o Pusher e enganar tanto o servidor quanto o cliente JS para se comunicar com ele.</p>
<p>A ideia é que sua aplicação Rails, em modo de desenvolvimento, faça um fork de um processo separado de servidor para que o cliente Pusher se conecte. Essa gem é ao mesmo tempo um servidor clone do Pusher e uma série de wrappers para carregá-lo no seu setup.</p>
<p>Começando pelo básico.</p>
<p>Na sua aplicação, você terá tanto uma configuração de conexão do cliente Pusher no lado Ruby (para enviar mensagens a um canal no servidor Pusher) quanto uma instância Pusher do lado Javascript, no cliente, principalmente para assinar um canal em uma conexão WebSocket e receber mensagens.</p>
<p>Primeiro, precisamos configurar o lado Ruby. Geralmente fica em um <code>config/initializers/pusher.rb</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># nunca defina Pusher.host ou Pusher.port aqui</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que estou usando variáveis de ambiente para guardar a configuração. Você deve usar algo como a <a href="https://github.com/laserlemon/figaro"target="_blank" rel="noopener">gem figaro</a> ou a <a href="https://github.com/bkeepers/dotenv"target="_blank" rel="noopener">gem dotenv-rails</a>. Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">PUSHER_APP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xpto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_KEY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_SECRET</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_CLUSTER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;us2&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No mínimo você precisa de um ID de aplicação, uma chave, um segredo e um nome de cluster. Tudo isso é fornecido pelo Pusher quando você registra uma nova aplicação lá.</p>
<p>Segundo, precisamos configurar a instância Javascript. Geralmente você tem algo no diretório <code>assets/javascripts</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// exemplo .js.erb
</span></span></span><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_APP] %&gt;, {
</span></span></span><span class="line"><span class="cl"><span class="s1">  cluster: &lt;%= ENV[&#39;</span><span class="nx">PUSHER_KEY</span><span class="s1">&#39;] %&gt;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  encrypted: &lt;%= ENV[&#39;</span><span class="nx">PUSHER_ENCRYPTED</span><span class="err">&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No Console de Desenvolvimento do Chrome, você pode inspecionar essa instância digitando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Pusher.instances[0]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Assim você consegue verificar se está pegando as configurações corretas para a conexão e depurar problemas.</p>
<p>As dependências são a gem pusher no seu <code>Gemfile</code> e o cliente javascript.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Gemfile</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;pusher&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No caso do arquivo javascript, você pode adicioná-lo ao projeto e depender do <a href="http://www.akitaonrails.com/2017/10/24/iniciante-experimentando-o-rails-5-1-x"target="_blank" rel="noopener">Webpacker</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yarn add pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, no seu arquivo javascript ES6, você faz:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Pusher</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;pusher-js&#39;</span><span class="p">);</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou pode linkar diretamente no seu layout:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://js.pusher.com/4.2/pusher.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para mais informações sobre o cliente pusher-js, leia o <a href="https://github.com/pusher/pusher-js"target="_blank" rel="noopener">arquivo README</a>.</p>
<h2>Adicionando o Pusher Fake<span class="hx:absolute hx:-mt-20" id="adicionando-o-pusher-fake"></span>
    <a href="#adicionando-o-pusher-fake" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Nesse ponto, você já deve conseguir se conectar à conta real do Pusher e ver a mágica do tempo real acontecendo.</p>
<p>E você já está consumindo a cota gratuita disponível no seu ambiente de desenvolvimento no Pusher. Para a maioria das pessoas, isso é suficiente.</p>
<p>Mas o objetivo é NÃO se conectar ao Pusher pela internet e manter tudo local para desenvolvimento e testes. Vamos começar adicionando o <a href="https://github.com/tristandunn/pusher-fake"target="_blank" rel="noopener">Pusher Fake</a> ao nosso <code>Gemfile</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:development</span><span class="p">,</span> <span class="ss">:test</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;pusher-fake&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora é onde a configuração do Pusher Fake pode ficar complicada se você não entender o que está acontecendo. Como disse antes, o PusherFake precisa fazer um fork de um novo processo para carregar um servidor local que imita o Pusher.</p>
<p>Para carregá-lo, você precisa apontar para o servidor local. Lembra do nosso <code>config/initializers/pusher.rb</code>? Basta fazer um require de um arquivo simples assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">development?</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="s2">&#34;pusher-fake/support/base&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só isso já apresenta MUITOS problemas se você não tiver cuidado. Esse <code>require</code> vai <strong>fazer um fork</strong> de um novo processo. Se você estiver usando um servidor web de processo único como Webrick ou Thin, tudo bem. Se estiver usando Puma, Unicorn ou Passenger com no máximo um processo, também deve funcionar. Mas se você carregar um servidor web que por si mesmo faz fork de novos processos, vai ter problema.</p>
<p>Na prática, prefiro carregar o servidor Pusher Fake separadamente, em modo standalone. Felizmente ele fornece um comando de linha de comando para iniciá-lo. E é boa prática configurar isso em um arquivo <code>Procfile.dev</code> e usar o <a href="https://github.com/ddollar/foreman"target="_blank" rel="noopener">foreman</a> para iniciar tudo. O <code>Procfile.dev</code> fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>web: bundle exec rails s -p 3000
db: postgres -D /usr/local/var/postgres
redis: redis-server /usr/local/etc/redis.conf
mailcatcher: mailcatcher -f
pusherfake: pusher-fake -i ${PUSHER_APP:-xpto} --socket-host 0.0.0.0 --socket-port ${PUSHER_WS_PORT:-45449} --web-host 0.0.0.0 --web-port ${PUSHER_PORT:-8888} -k ${PUSHER_KEY:-abcd1234} -s ${PUSHER_SECRET:-abcd1234}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De bônus, veja como configuro outros serviços como PostgreSQL, Redis, etc.</p>
<p>Se você não sabia, pode usar <code>${VARIABLE_NAME:-default_value}</code> para usar uma variável de ambiente ou ter um valor padrão caso ela não exista. Isso significa que suas variáveis de ambiente configuradas com Figaro ou Dotenv precisam ter os mesmos valores.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">PUSHER_APP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xpto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_KEY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_SECRET</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_CLUSTER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;us2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;127.0.0.1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;8888&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_WS_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;127.0.0.1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_WS_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;45449&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora seu <code>config/initializers/pusher.rb</code> deve ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">development?</span>
</span></span><span class="line"><span class="cl">  <span class="no">Pusher</span><span class="o">.</span><span class="n">host</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_HOST&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="no">Pusher</span><span class="o">.</span><span class="n">port</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_PORT&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E a configuração do Pusher-js em algum lugar do seu diretório <code>app/assets/javascripts/</code> vai ficar algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="k">if</span> <span class="nx">defined</span><span class="o">?</span><span class="p">(</span><span class="nx">PusherFake</span><span class="p">)</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="k">if</span> <span class="nx">Rails</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">test</span><span class="o">?</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">pusher</span> <span class="o">=</span> <span class="o">&lt;%=</span> <span class="nx">PusherFake</span><span class="p">.</span><span class="nx">javascript</span><span class="p">(</span><span class="nx">cluster</span><span class="o">:</span> <span class="nx">ENV</span><span class="p">[</span><span class="s2">&#34;PUSHER_CLUSTER&#34;</span><span class="p">])</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="k">else</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cluster</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">wsHost</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_WS_HOST&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">wsPort</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_WS_PORT&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">encrypted</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_ENCRYPTED&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span>    
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="nx">end</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="k">else</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cluster</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">encrypted</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_ENCRYPTED&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="nx">end</span> <span class="o">%&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembre que este é um arquivo Javascript+ERB, então podemos buscar as mesmas variáveis de ambiente para a configuração.</p>
<p>Agora, sempre que você executar <code>foreman start -f Procfile.dev -p 3000</code>, ele vai carregar o servidor Pusher Fake com as configurações de desenvolvimento corretas, e tanto o seu servidor ruby quanto o cliente javascript devem se conectar a ele sem nenhum problema.</p>
<p>Note também o trecho <code>if Rails.env.test?</code>. Isso é para a sua suite de testes RSpec. No caso do ambiente de testes, não vamos carregar o servidor fake manualmente; em vez disso, vamos criar algo como <code>spec/support/pusher-fake.rb</code> com:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;pusher-fake/support/rspec&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é isso. O <code>PusherFake.javascript</code> vai definir a configuração padrão de conexão WebSocket, e o <code>require</code> acima vai tanto fazer o fork do servidor fake quanto configurar o RSpec para limpar os canais a cada execução de teste (via <code>PusherFake::Channel.reset</code>).</p>
<p>Desta forma, seu ambiente de testes também evita se conectar ao servidor Pusher real, externo.</p>
<p>A chave de tudo isso são as variáveis de ambiente. Você precisa garantir que cada peça esteja carregando a mesma configuração — caso contrário, você terá o servidor fake fazendo bind em uma porta diferente da que o Pusher-js está tentando se conectar, e vai ter erros. Depure com cuidado.</p>
<p>O mais importante: se você fez tudo corretamente, agora é independente do servidor Pusher real para os ambientes de desenvolvimento e testes, nunca vai atingir nenhum limite de cota, e sua equipe e seu CI poderão trabalhar de forma ininterrupta, com comportamento determinístico.</p>
]]></content:encoded><category>rails</category><category>testes</category><category>tutoriais</category></item><item><title>Compartilhando models entre aplicações Rails - Parte 1</title><link>https://akitaonrails.github.io/2016/10/03/compartilhando-models-entre-aplicacoes-rails-parte-1/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/10/03/compartilhando-models-entre-aplicacoes-rails-parte-1/</guid><pubDate>Mon, 03 Oct 2016 15:00:00 -0300</pubDate><description>&lt;p&gt;&lt;em&gt;&amp;ldquo;Cria um microservice e expõe uma API.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Essa seria a resposta rápida se você perguntar para qualquer desenvolvedor como compartilhar lógica de negócio entre aplicações.&lt;/p&gt;
&lt;p&gt;Apesar de fazer sentido em muitas situações, essa resposta deixa a desejar em vários outros casos.&lt;/p&gt;
&lt;p&gt;Meu TL;DR para algumas situações é o seguinte: você pode organizar a lógica dos seus models como &lt;a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html"target="_blank" rel="noopener"&gt;ActiveSupport::Concerns&lt;/a&gt; (ou Modules Ruby puros, se preferir) e movê-los para uma Rubygem que suas aplicações podem consumir.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>&ldquo;Cria um microservice e expõe uma API.&rdquo;</em></p>
<p>Essa seria a resposta rápida se você perguntar para qualquer desenvolvedor como compartilhar lógica de negócio entre aplicações.</p>
<p>Apesar de fazer sentido em muitas situações, essa resposta deixa a desejar em vários outros casos.</p>
<p>Meu TL;DR para algumas situações é o seguinte: você pode organizar a lógica dos seus models como <a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html"target="_blank" rel="noopener">ActiveSupport::Concerns</a> (ou Modules Ruby puros, se preferir) e movê-los para uma Rubygem que suas aplicações podem consumir.</p>
<p>Repare que estou falando apenas de Models, não de Controllers ou Views. Para compartilhar essas outras coisas você precisaria de uma Rails Engine completa. Mas em muitos casos que vi a vontade era apenas compartilhar a lógica de negócio entre aplicações, mantendo o front-end separado.</p>
<p>Um pequeno exemplo desse cenário é o projeto open source em que venho trabalhando nas últimas semanas. <a href="https://github.com/Codeminer42/cm42-central"target="_blank" rel="noopener">Central</a>, uma alternativa ao Pivotal Tracker/Trello - caso você se interesse.</p>
<p>Há alguns dias comecei um novo projeto (apenas para uso interno) que precisava consultar os mesmos models do Central. Eu não queria implementar APIs HTTP nesse momento, e a nova aplicação teria seus próprios models com relacionamentos com os models do Central (tratando-os como read-only).</p>
<p>Depois de alguns refactorings, a maioria dos models do Central ficou parecida com este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Team</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Associations</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Validations</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Scopes</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">DomainValidator</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E tenho essa dependência no <code>Gemfile</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;central-support&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;Codeminer42/cm42-central-support&#39;</span><span class="p">,</span> <span class="ss">branch</span><span class="p">:</span> <span class="s1">&#39;master&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;central/support&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sempre que mudo as concerns, rodo um <code>bundle update central-support</code> nos projetos (esse é o cuidado que você precisa ter para evitar lidar com models desatualizados).</p>
<p>Isso foi possível porque a maioria desses models já estava madura e estável e eu não vou mexer neles com frequência. Eu não recomendo expor dependências instáveis (como gems ou APIs, tanto faz), porque essa é a receita para uma dor de cabeça enorme com breaking changes em cascata por causa de dependências desatualizadas que mudam com frequência demais.</p>
<blockquote>
  <p>Você deve APENAS expor lógica de negócio que esteja razoavelmente estável (mudanças apenas de semana em semana, mais ou menos).</p>

</blockquote>
<p>A jornada toda foi montar uma certa estrutura de Rubygems, organizar os models originais em Concerns (o que não quebra nenhum comportamento), garantir que os specs continuassem passando, e então mover o conteúdo (models e specs) para a nova Rubygem e garantir que os specs passassem por lá.</p>
<p>Foi assim que construí uma dependência open source secundária do Central, chamada <a href="https://github.com/Codeminer42/cm42-central-support"target="_blank" rel="noopener">Central Support</a>. Como muitas gems, o arquivo principal <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/lib/central/support.rb"target="_blank" rel="noopener">lib/central/support.rb</a> é só um monte de &lsquo;requires&rsquo; para carregar todas as dependências.</p>
<p>Então organizei metodicamente a lógica em concerns, como <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/lib/central/support/concerns/team_concern/associations.rb"target="_blank" rel="noopener">lib/central/support/concerns/team_concern/association.rb</a>, que é só a extração das associações de Active Record do model &lsquo;Team&rsquo;.</p>
<p>Recortar do Central, colar no Support. Quando toda a lógica relevante já tinha sido movida, consegui mover o spec inteiro do model Team, basicamente sem alterações, e fazê-lo rodar. Toda vez que eu movia um pedacinho, dava <code>bundle update</code> na gem e rodava a suíte principal de specs para garantir que nada tinha quebrado.</p>
<p>E essa é a parte difícil: montar um sandbox onde essas concerns possam rodar e ser testadas.</p>
<p>Para começar, precisei construir uma aplicação Rails mínima dentro da pasta de specs, em <code>spec/support/rails_app</code>. E ali eu pude colocar models falsos que incluem as concerns que acabei de extrair do Central.</p>
<p>Existe pouca documentação sobre como fazer isso, mas acho que dá para fazer um <code>rails new</code> e começar dali, ou copiar a minha pasta <code>rails_app</code> para ter o mínimo necessário. Meu caso é mais simples porque essa gem não é de propósito geral, então não preciso rodá-la contra versões diferentes do Rails, por exemplo.</p>
<p>Essa app de teste interna precisa ter um <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/support/rails_app/Gemfile"target="_blank" rel="noopener"><code>Gemfile</code></a> cuidadosamente montado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;central-support&#39;</span><span class="p">,</span> <span class="ss">path</span><span class="p">:</span> <span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s2">&#34;../../../..&#34;</span><span class="p">,</span> <span class="bp">__FILE__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;devise&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;pg&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;friendly_id&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;foreigner&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:test</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;test-unit&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rspec-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você não precisa adicionar as gems do <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/central-support.gemspec"target="_blank" rel="noopener">gemspec</a> principal. Mas você pode remover as dependências de desenvolvimento que iriam para o gemspec e mantê-las no Gemfile da app de teste.</p>
<p>Agora, a partir do <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/Gemfile"target="_blank" rel="noopener"><code>Gemfile</code> principal</a> você pode fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">source</span> <span class="s1">&#39;https://rubygems.org&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">eval_gemfile</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">),</span> <span class="s2">&#34;spec/support/rails_app/Gemfile&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A maioria dos tutoriais de como construir uma Rubygem coloca uma linha para carregar dependências do gemspec, mas aqui estamos substituindo isso pelo Gemfile da app de teste. Esse é o manifest que será carregado quando rodarmos <code>bundle exec rspec</code>, por exemplo.</p>
<p>Por falar nisso, esse é o <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/rails_helper.rb"target="_blank" rel="noopener"><code>spec/rails_helper.rb</code></a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;RAILS_ENV&#39;</span><span class="o">]</span> <span class="o">||=</span> <span class="s1">&#39;test&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rails/all&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;factory_girl&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;factory_girl_rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rspec/rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;shoulda/matchers&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sb">`cd spec/support/rails_app ; bin/rails db:drop db:create db:schema:load RAILS_ENV=test`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;support/rails_app/config/environment&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;support/database_cleaner&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;support/factory_girl&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;support/factories&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;spec_helper&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para visualizar como tudo se encaixa:</p>
<ul>
<li><code>bundle exec rspec</code> carrega o <code>Gemfile</code> principal</li>
<li>o <code>Gemfile</code> principal carrega a partir do <code>Gemfile</code> da app de teste interna</li>
<li>esse <code>Gemfile</code> da app de teste interna requer o gemspec de <code>../../../..</code> e os grupos de gems de development e test (incluindo Rspec, Factory Girl, etc)</li>
<li>o gemspec requer as dependências de runtime como &ldquo;activesupport&rdquo;, &ldquo;enumerize&rdquo;, etc</li>
<li>finalmente, o <code>rails_helper.rb</code> listado acima é carregado.</li>
</ul>
<p>Ali na linha 11, o runner executa um comando para fazer <code>cd</code> na pasta raiz da app de teste interna e rodar <code>db:schema:load</code>, então você precisa de um <code>db/schema.rb</code> pronto para ser carregado, assim como um <code>config/database.yml</code>.</p>
<p>O <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/spec_helper.rb"target="_blank" rel="noopener"><code>spec/spec_helper.rb</code></a> é mais padrão, com configurações opcionais para test coverage, etc.</p>
<p>Os models dentro da app de teste interna são as partes importantes, porque são o meio para incluir as concerns extraídas em um formato executável. O <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/support/rails_app/app/models/team.rb"target="_blank" rel="noopener">&lsquo;spec/support/rails_app/app/models/team.rb&rsquo;</a> é um exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Team</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Associations</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Validations</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Scopes</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">DomainValidator</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E com isso, consegui mover os specs sem modificações diretamente do projeto principal (Central), como <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/central/support/team_spec.rb"target="_blank" rel="noopener"><code>spec/central/support/team_spec.rb</code></a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rails_helper&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span> <span class="no">Team</span><span class="p">,</span> <span class="ss">type</span><span class="p">:</span> <span class="ss">:model</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="o">.</span><span class="n">to</span> <span class="n">validate_presence_of</span> <span class="ss">:name</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="o">.</span><span class="n">to</span> <span class="n">have_many</span> <span class="ss">:enrollments</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="o">.</span><span class="n">to</span> <span class="n">have_many</span> <span class="ss">:users</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="o">.</span><span class="n">to</span> <span class="n">have_many</span> <span class="ss">:ownerships</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="o">.</span><span class="n">to</span> <span class="n">have_many</span> <span class="ss">:projects</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você voltar no projeto Central, alguns commits atrás, vai encontrar exatamente esse mesmo arquivo como <a href="https://github.com/Codeminer42/cm42-central/blob/a80eefadf233f4a8c5f88829836c872b199798cd/spec/models/team_spec.rb"target="_blank" rel="noopener"><code>spec/models/team_spec.rb</code></a>. E a principal vantagem dessa abordagem é justamente conseguir mover a maior parte do código para fora do projeto principal, junto com os specs, para uma gem de dependência, sem precisar &ldquo;reescrever&rdquo; nada.</p>
<p>Se eu tivesse que reescrever todo ou um pedaço grande do código, teria sido uma escolha bem mais cara e eu provavelmente teria adiado isso para outro momento e focado em features mais valiosas primeiro.</p>
<p>Essa abordagem está longe de perfeita, mas saiu super barata. Consegui mover toda a lógica de negócio relevante para fora do projeto principal sem precisar reescrever nada além de algum código de &ldquo;fiação&rdquo;. A nova gem de dependência recebeu todas as partes relevantes e os specs, e tudo simplesmente roda.</p>
<p>Então, se você tem 2 ou mais aplicações Rails que poderiam compartilhar os mesmos models, é assim que você pode começar. Claro, sempre tem um monte de ressalvas para manter em mente.</p>
<p>No meu caso, o projeto Central é o que pode ler e escrever no banco. Minha aplicação secundária interna usa os models apenas como read-only. Quando 2 aplicações diferentes escrevem no mesmo banco, você pode ter vários conflitos para lidar.</p>
<p>Essa abordagem é útil quando sua aplicação secundária se parece com um dashboard de Administração, por exemplo. Você precisa ter algumas das mesmas associações, scopes, e até validações para edição eventual, mas isso fica limitado a poucos usuários controlados.</p>
<p>Também é útil quando você está fazendo análise de dados, e novamente pode usar as mesmas associações, scopes, para construir relatórios e dashboards. Essencialmente, quando você precisa de acesso read-only, é decisão fácil.</p>
<p>No <a href="http://www.akitaonrails.com/2016/10/03/compartilhando-models-entre-apps-rails-parte-2"target="_blank" rel="noopener">próximo artigo</a> eu vou explicar como liguei uma aplicação secundária, usando a gem central-support e lidando com 2 bancos de dados diferentes ao mesmo tempo.</p>
]]></content:encoded><category>rails</category><category>arquitetura-de-software</category><category>testes</category></item><item><title>Ex Manga Downloadr - Parte 5: Deixando mais robusto!</title><link>https://akitaonrails.github.io/2015/12/06/ex-manga-downloadr-parte-5-deixando-mais-robusto/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/06/ex-manga-downloadr-parte-5-deixando-mais-robusto/</guid><pubDate>Sun, 06 Dec 2015 19:20:00 -0200</pubDate><description>&lt;p&gt;E lá vou eu de novo. Sei que alguns de vocês já podem estar entediados com essa ferramenta, mas como projeto de brincadeira, ainda quero deixar o código bom. Só que tem 2 problemas grandes agora.&lt;/p&gt;
&lt;p&gt;Quando eu testava só com o MangaReader.net como fonte, tudo funcionava quase sem falhas. Mas ao adicionar o MangaFox na &lt;a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener"&gt;Parte 3&lt;/a&gt;, com suas regras mais restritivas contra ferramentas de scrapping como a minha (timeouts mais frequentes, não permitindo muitas conexões da mesma origem, etc), o processo simplesmente quebrava o tempo todo e eu tinha que reiniciar manualmente (as features de retomada que adicionei na &lt;a href="http://www.akitaonrails.com/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring"target="_blank" rel="noopener"&gt;Parte 4&lt;/a&gt; ajudaram bastante, mas a ferramenta deixou de ser confiável).&lt;/p&gt;</description><content:encoded><![CDATA[<p>E lá vou eu de novo. Sei que alguns de vocês já podem estar entediados com essa ferramenta, mas como projeto de brincadeira, ainda quero deixar o código bom. Só que tem 2 problemas grandes agora.</p>
<p>Quando eu testava só com o MangaReader.net como fonte, tudo funcionava quase sem falhas. Mas ao adicionar o MangaFox na <a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener">Parte 3</a>, com suas regras mais restritivas contra ferramentas de scrapping como a minha (timeouts mais frequentes, não permitindo muitas conexões da mesma origem, etc), o processo simplesmente quebrava o tempo todo e eu tinha que reiniciar manualmente (as features de retomada que adicionei na <a href="http://www.akitaonrails.com/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring"target="_blank" rel="noopener">Parte 4</a> ajudaram bastante, mas a ferramenta deixou de ser confiável).</p>
<p>Recapitulando, o Workflow só organiza cada passo do processo. Suas funções são parecidas com isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">process_downloads</span><span class="p">(</span><span class="n">images_list</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">images_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">page_download_image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">directory</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="n">directory</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele lida com uma lista grande, faz um map sobre cada elemento mandando para uma função Worker rodar, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">page_download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="vi">@genserver_call_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">,</span> <span class="vi">@task_async_timeout</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele retorna uma Task assíncrona esperando por 2 coisas: o Poolboy liberar um processo livre para usar, e a função Worker/GenServer terminar de rodar dentro daquele processo. Como expliquei na <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Parte 2</a>, isso é para limitar o número máximo de conexões com a fonte externa. Sem essa restrição, mandando dezenas de milhares de requests assíncronos de uma vez, a fonte externa simplesmente falharia todos.</p>
<p>A primeira coisa para se ter em mente é que um &ldquo;<tt>Task.async/2</tt>&rdquo; se linka com o processo chamador, então se algo der errado, o processo pai morre junto.</p>
<p>A coisa certa a fazer é adicionar um <a href="http://elixir-lang.org/docs/stable/elixir/Task.Supervisor.html"target="_blank" rel="noopener">Task.Supervisor</a> e deixar ele cuidar de cada Task filha. Para fazer isso, basta adicionar o Supervisor na nossa árvore supervisionada em &ldquo;pool_management/supervisor.ex&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Supervisor</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Supervisor</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">children</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">supervisor</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="p">,</span> <span class="o">[[</span><span class="nb">name</span><span class="p">:</span> <span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="o">]]</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">child_spec</span><span class="p">(</span><span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">pool_options</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E podemos substituir as chamadas &ldquo;<tt>Task.async/2</tt>&rdquo; por &ldquo;<tt>Task.Supervisor.async(Fetcher.TaskSupervisor, &hellip;)</tt>&rdquo; assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">page_download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="p">,</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="vi">@genserver_call_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">,</span> <span class="vi">@task_async_timeout</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso ainda cria Tasks pelas quais precisamos esperar, e como antes, se a função lá dentro quebrar, ainda derruba o processo principal. Agora minha refatoração chegou num beco sem saída.</p>
<p>Esse é o 2º problema que mencionei no início do artigo: uma <strong>falha no meu design</strong>.</p>
<p>Em vez de só fazer um map sobre cada elemento de uma lista grande, eu deveria ter criado um GenServer baseado em Agent para manter a lista como estado e transformar todo o sistema do Workflow em um novo GenServer supervisionado. Se buscar uma URL quebrasse o GenServer, seu supervisor o reiniciaria e pegaria o próximo elemento da lista.</p>
<p>Mas, como não estou com pique para essa refatoração agora (é tarde de domingo), vou me concentrar num quick fix (sim, uma gambiarra), só para que a função dentro da chamada async não levante exceções.</p>
<h3>OMG! É um bloco Try/Catch!<span class="hx:absolute hx:-mt-20" id="omg-é-um-bloco-trycatch"></span>
    <a href="#omg-%c3%a9-um-bloco-trycatch" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Acontece que tudo o que rodo dentro dos processos do Poolboy são requests HTTP get através do HTTPotion. Felizmente eu já tinha refatorado toda chamada HTTPotion get em uma macro elegante:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmacro</span> <span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Fetching from </span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="n">body</span> <span class="o">|&gt;</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">headers</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora só preciso trocar 1 linha nessa macro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">-</span>    <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="k">case</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">retryable_http_get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">))</span> <span class="k">do</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E definir essa nova lógica de retry no módulo principal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">Logger</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># vai tentar de novo fetches falhos por mais de 50 vezes, dormindo 1 segundo entre cada retry</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@max_retries</span>  <span class="mi">50</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@time_to_wait_to_fetch_again</span> <span class="mi">1_000</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="k">raise</span> <span class="s2">&#34;Failed to fetch from </span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2"> after </span><span class="si">#{</span><span class="vi">@max_retries</span><span class="si">}</span><span class="s2"> retries.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">retries</span> <span class="p">\\</span> <span class="vi">@max_retries</span><span class="p">)</span> <span class="k">when</span> <span class="n">retries</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">try</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Fetching from </span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2"> for the </span><span class="si">#{</span><span class="vi">@max_retries</span> <span class="o">-</span> <span class="n">retries</span><span class="si">}</span><span class="s2"> time.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">response</span> <span class="o">=</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="n">response</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">_</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">_</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="n">status</span> <span class="p">}</span> <span class="k">when</span> <span class="n">status</span> <span class="o">&gt;</span> <span class="mi">499</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="k">raise</span> <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span><span class="p">{</span><span class="ss">message</span><span class="p">:</span> <span class="s2">&#34;req_timedout&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="n">response</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">      <span class="n">error</span> <span class="k">in</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">error</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">          <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span><span class="p">{</span><span class="ss">message</span><span class="p">:</span> <span class="n">message</span><span class="p">}</span> <span class="k">when</span> <span class="n">message</span> <span class="k">in</span> <span class="vi">@http_errors</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="ss">:timer</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="vi">@time_to_wait_to_fetch_again</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">retries</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="n">_</span> <span class="o">-&gt;</span> <span class="k">raise</span> <span class="n">error</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu <a href="http://www.akitaonrails.com/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal"target="_blank" rel="noopener">afirmei</a> com convicção que em Elixir <strong>não</strong> devemos usar blocos &ldquo;try/catch&rdquo;, mas aí está.</p>
<p>Essa é a consequência da falha no meu design inicial do Workflow. Se eu tivesse codificado o módulo Workflow como um GenServer, com cada lista gerenciada por um Agent, cada chamada HTTPotion que falhasse permitiria ao supervisor reiniciá-la e tentar de novo. Sem precisar recorrer ao código feio do &ldquo;try/catch&rdquo;.</p>
<p>Talvez isso me force a escrever a Parte 6 sendo o código para remover esse &ldquo;try/catch&rdquo; feio depois, então considerem isto uma <strong>Dívida Técnica</strong> para fazer tudo funcionar agora e depois refatorar para pagar a dívida.</p>
<p>As chamadas &ldquo;<tt>HTTPotion.get/2</tt>&rdquo; podem levantar exceções &ldquo;HTTPotion.HTTPError&rdquo;. Estou capturando esses erros por enquanto, casando as mensagens contra uma lista de erros que eu já tinha, dormindo por um certo tempo (só uma heurística para ver se as fontes externas respondem melhor desse jeito) e recurso na própria função através de um número limitado de &ldquo;retries&rdquo;, até chegar a zero, caso em que pode até ser que a conexão de internet esteja caída ou algum outro erro grave do qual não conseguiríamos nos recuperar tão cedo.</p>
<p>Com esse código no lugar, agora até buscar do MangaFox, sem precisar diminuir o POOL_SIZE, roda até o fim, e isso resolve minhas necessidades por agora. Se alguém tiver interesse em sugerir um design de Workflow melhor, baseado em GenServer, eu agradeceria muito um Pull Request.</p>
<p>Abraços.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>engenharia-de-software</category><category>testes</category></item><item><title>Ex Manga Downloadr - Parte 4: Aprendendo Através do Refactoring</title><link>https://akitaonrails.github.io/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring/</guid><pubDate>Thu, 03 Dec 2015 17:40:00 -0200</pubDate><description>&lt;p&gt;&lt;a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener"&gt;Ontem adicionei suporte ao Mangafox&lt;/a&gt; na minha ferramenta de download e isso também acabou jogando um pouco de código sujo no meu código que já não estava lá essas coisas. Hora de uma faxina pra valer.&lt;/p&gt;
&lt;p&gt;Você consegue ver tudo o que fiz desde ontem pra limpar a casa através da &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr/compare/59694565592f8a3bea95115b858dd2ddfdc89873...3ae7dd98a8fd7bae47ffd7a24c0c42a2c3777fad"target="_blank" rel="noopener"&gt;excelente página de compare do Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Antes de mais nada: agora a escolha de ter adicionado uma quantidade razoável de testes vai pagar dividendos. Neste refactoring eu mudei assinaturas de funções, formatos de resposta, movi uma boa quantidade de código de lugar, e sem os testes essa empreitada teria me tomado o dia inteiro ou mais, deixando o esforço de refactor questionável desde o início.&lt;/p&gt;</description><content:encoded><![CDATA[<p><a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener">Ontem adicionei suporte ao Mangafox</a> na minha ferramenta de download e isso também acabou jogando um pouco de código sujo no meu código que já não estava lá essas coisas. Hora de uma faxina pra valer.</p>
<p>Você consegue ver tudo o que fiz desde ontem pra limpar a casa através da <a href="https://github.com/akitaonrails/ex_manga_downloadr/compare/59694565592f8a3bea95115b858dd2ddfdc89873...3ae7dd98a8fd7bae47ffd7a24c0c42a2c3777fad"target="_blank" rel="noopener">excelente página de compare do Github</a></p>
<p>Antes de mais nada: agora a escolha de ter adicionado uma quantidade razoável de testes vai pagar dividendos. Neste refactoring eu mudei assinaturas de funções, formatos de resposta, movi uma boa quantidade de código de lugar, e sem os testes essa empreitada teria me tomado o dia inteiro ou mais, deixando o esforço de refactor questionável desde o início.</p>
<p>A cada passo do refactoring eu podia rodar &ldquo;<tt>mix test</tt>&rdquo; e trabalhar até chegar no status verde:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 13.5 seconds (0.1s on load, 13.4s on tests)
12 tests, 0 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Os testes estão demorando bastante porque fiz a escolha de que os testes unitários do MangaReader e do Mangafox fossem de fato online, buscando direto dos sites. Demora mais pra rodar a suíte mas eu sei que se ela quebrar e eu não tiver mexido naquele código, então os sites de origem mudaram seus formatos e eu preciso mudar o parser. Eu poderia ter adicionado fixtures pra fazer os testes rodarem mais rápido, mas o objetivo do meu parser é estar correto.</p>
<h3>Macros pro Resgate!<span class="hx:absolute hx:-mt-20" id="macros-pro-resgate"></span>
    <a href="#macros-pro-resgate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cada módulo de fonte tem 3 sub-módulos: ChapterPage, IndexPage e Page. Todos eles tem uma função principal que parece com este pedaço de código:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">ChapterPage</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">Logger</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Fetching pages from chapter </span><span class="si">#{</span><span class="n">chapter_link</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">,</span> <span class="o">[</span><span class="ss">headers</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="vi">@user_agent</span><span class="p">,</span> <span class="s2">&#34;Accept-encoding&#34;</span><span class="p">:</span> <span class="s2">&#34;gzip&#34;</span><span class="o">]</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="mi">30_000</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Listing 1.1)</p>
<p>Ele chama &ldquo;<tt>HTTPotion.get/2</tt>&rdquo; mandando um monte de opções HTTP e recebe uma struct &ldquo;<tt>%HTTPotion.Response</tt>&rdquo; que então é decomposta pra pegar o body e os headers. Aplica gunzip no body se necessário e vai parsear o HTML em si.</p>
<p>Código parecido existe em 6 módulos diferentes, com links diferentes e funções de parser diferentes. É muita repetição, mas e se a gente fizesse o código acima ficar parecido com o snippet abaixo?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">ChapterPage</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">Logger</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">ExMangaDownloadr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">fetch</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Listing 1.2)</p>
<p>Mudei 9 linhas pra apenas 1. E aliás, essa mesma linha pode ser escrita assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">fetch</span> <span class="n">chapter_link</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Parece familiar? É como todo bloco na linguagem Elixir, você pode escrevê-lo no formato de bloco &ldquo;do/end&rdquo; ou da maneira que ele realmente é por baixo dos panos: uma keyword list com uma chave chamada &ldquo;:do&rdquo;. E a forma como esse macro é definido é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Fetching from </span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="n">body</span> <span class="o">|&gt;</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">headers</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Listing 1.3)</p>
<p>Tem um monte de detalhes pra considerar quando se escreve um macro e eu recomendo ler a documentação sobre <a href="http://elixir-lang.org/getting-started/meta/macros.html"target="_blank" rel="noopener">Macros</a>. O código está basicamente copiando o corpo da função do &ldquo;<tt>ChapterPage.pages/1</tt>&rdquo; (Listing 1.1) e colando dentro do bloco &ldquo;<tt>quote do .. end</tt>&rdquo; (Listing 1.3).</p>
<p>Dentro daquele código a gente tem &ldquo;<tt>unquote(link)</tt>&rdquo; e &ldquo;<tt>unquote(expression)</tt>&rdquo;. Você também precisa ler a documentação sobre <a href="http://elixir-lang.org/getting-started/meta/quote-and-unquote.html"target="_blank" rel="noopener">&ldquo;Quote and Unquote&rdquo;</a>. Ele simplesmente expande esse código &ldquo;externo&rdquo; dentro do código do macro pra adiar a execução até o código do quote do macro de fato ser executado, em vez de rodar naquele exato momento. Eu sei, complicado de embrulhar a cabeça em volta disso na primeira vez.</p>
<p>A linha de fundo é: qualquer código que estiver dentro do bloco &ldquo;quote&rdquo; vai ser &ldquo;inserido&rdquo; onde a gente chamou &ldquo;<tt>ExMangaDownloadr.fetch/2</tt>&rdquo; na função &ldquo;pages/1&rdquo; da Listing 1.2, junto com o código unquoted que você passou como parâmetro.</p>
<p>O código resultante vai parecer com o código original da Listing 1.1.</p>
<p>Pra simplificar, se você estivesse em Javascript isso seria um código parecido:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">eval</span><span class="p">(</span><span class="s2">&#34;doSomething(&#39;&#34;</span> <span class="o">+</span> <span class="nx">url</span> <span class="o">+</span> <span class="s2">&#34;&#39;)&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">pages</span><span class="p">(</span><span class="nx">page_link</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fetch</span><span class="p">(</span><span class="nx">page_link</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>&ldquo;Quote&rdquo; seria como o corpo de string num eval e &ldquo;unquote&rdquo; só concatenando o valor que você passou dentro do código que está sendo eval-ado. É uma metáfora grosseira porque &ldquo;quote/unquote&rdquo; é <strong>muito</strong> mais poderoso e mais limpo que o feio &ldquo;eval&rdquo; (que você não deveria estar usando, aliás!) Mas essa metáfora serve pra você entender o código acima.</p>
<p>Outro lugar onde usei um macro foi pra salvar a lista de imagens num arquivo de dump e carregá-la depois caso a ferramenta dê crash por algum motivo, pra não ter que começar tudo do zero. O código original era assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">                <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read!</span><span class="p">(</span><span class="n">dump_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">              <span class="k">else</span>
</span></span><span class="line"><span class="cl">                <span class="n">list</span> <span class="o">=</span> <span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span>
</span></span><span class="line"><span class="cl">                <span class="no">File</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="n">list</span>
</span></span><span class="line"><span class="cl">              <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Listing 1.4)</p>
<p>E agora que você entende macros, vai entender o que eu fiz aqui:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">managed_dump</span><span class="p">(</span><span class="n">directory</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">          <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read!</span><span class="p">(</span><span class="n">dump_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">          <span class="n">list</span> <span class="o">=</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="no">File</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">          <span class="n">list</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">CLI</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Workflow</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">ExMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="p">{</span><span class="n">_url</span><span class="p">,</span> <span class="n">_source</span><span class="p">}</span> <span class="o">=</span> <span class="n">manga_site</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">images_list</span> <span class="o">=</span> 
</span></span><span class="line"><span class="cl">      <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">managed_dump</span> <span class="n">directory</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">manga_site</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span> 
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pronto! E agora você vê como blocos &ldquo;do .. end&rdquo; são implementados. Ele simplesmente passa a expressão como o valor na keyword list da definição do macro. Vamos definir um macro bobo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Foo</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">foo</span><span class="p">(</span><span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">       <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E agora as seguintes chamadas são todas equivalentes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="no">Foo</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span> <span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="o">[</span><span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="o">[</span><span class="p">{</span><span class="ss">:do</span><span class="p">,</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)}</span><span class="o">]</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é macros combinados com <a href="http://elixir-lang.org/getting-started/maps-and-dicts.html"target="_blank" rel="noopener">Keyword Lists</a> que eu expliquei em artigos anteriores e é simplesmente uma List com tuplas onde cada tupla tem uma chave atom e um valor.</p>
<h3>Mais Macros<span class="hx:absolute hx:-mt-20" id="mais-macros"></span>
    <a href="#mais-macros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra oportunidade pra refatorar foram os módulos &ldquo;mangareader.ex&rdquo; e &ldquo;mangafox.ex&rdquo; que eram só usados nos testes unitários &ldquo;mangareader_test.ex&rdquo; e &ldquo;mangafox_test.ex&rdquo;. Esse é o código antigo do &ldquo;mangareader.ex&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">_opts</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que ele era usado no &ldquo;mangareader_test.ex&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReaderTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExUnit</span><span class="o">.</span><span class="n">Case</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Era só um atalho pra fazer o alias dos módulos pra usá-los diretamente dentro dos testes. Eu simplesmente movi o módulo inteiro como um macro no módulo &ldquo;ex_manga_downloadr.ex&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">mangareader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">mangafox</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">when</span> <span class="n">is_atom</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">apply</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="n">which</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E agora posso usá-lo assim no arquivo de teste:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReaderTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExUnit</span><span class="o">.</span><span class="n">Case</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="p">,</span> <span class="ss">:mangareader</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O macro especial &ldquo;<strong>using</strong>&rdquo; é chamado quando eu dou &ldquo;use&rdquo; num módulo, e eu posso até passar argumentos pra ele. A implementação então usa &ldquo;apply/3&rdquo; pra chamar dinamicamente o macro correto. É exatamente assim que o Phoenix importa os behaviors apropriados pra Models, Views, Controllers, Router, por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">PageController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:controller</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Os macros estão abertos num arquivo Phoenix e disponíveis no módulo &ldquo;web/web.ex&rdquo;, então só copiei o mesmo comportamento. E agora tenho 2 arquivos a menos pra me preocupar.</p>
<h3>Pequenos refactorings<span class="hx:absolute hx:-mt-20" id="pequenos-refactorings"></span>
    <a href="#pequenos-refactorings" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No código anterior eu usava o &ldquo;<tt>String.to_atom/1</tt>&rdquo; pra converter a string do nome do módulo num atom, pra ser usado depois em chamadas &ldquo;apply/3&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">manga_source</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">module</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">source</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mudei pra isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">    <span class="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="ss">:&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="ss">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="ss">:&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="ss">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É só um atalho pra fazer a mesma coisa.</p>
<p>No parser eu também estava usando o <a href="https://github.com/philss/floki"target="_blank" rel="noopener">Floki</a> de forma errada. Então dá uma olhada nesse pedaço de código antigo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_manga_title</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#mangaproperties h1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;h1&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[</span><span class="n">title</span><span class="o">]</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">title</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora usando as funções helper melhores que o Floki provê:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_manga_title</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;#mangaproperties h1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">attribute</span><span class="p">(</span><span class="s2">&#34;href&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse foi um caso de não ter lido a documentação como eu deveria. Bem mais limpo!</p>
<p>Fiz outras pequenas limpezas mas acho que isso cobre as mudanças principais. E finalmente, subi a versão pra &ldquo;1.0.0&rdquo; também!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">   <span class="k">def</span> <span class="nf">project</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="o">[</span><span class="ss">app</span><span class="p">:</span> <span class="ss">:ex_manga_downloadr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>     <span class="ss">version</span><span class="p">:</span> <span class="s2">&#34;0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>     <span class="ss">version</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">elixir</span><span class="p">:</span> <span class="s2">&#34;~&gt; 1.1&#34;</span><span class="p">,</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E falando em versões, estou usando Elixir 1.1 mas preste atenção porque <a href="https://github.com/elixir-lang/elixir/blob/ef5ba3af059f76489631dc26b52ecaeff09af3fe/CHANGELOG.md"target="_blank" rel="noopener">Elixir 1.2</a> está logo aí na esquina e traz algumas gracinhas. Por exemplo, aquele macro que fazia o alias de alguns módulos poderia ser escrito assim agora:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">mangareader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="p">{</span><span class="no">IndexPage</span><span class="p">,</span> <span class="no">ChapterPage</span><span class="p">,</span> <span class="no">Page</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E essa é só 1 feature entre muitas outras melhorias de sintaxe e suporte ao mais novo <a href="http://www.erlang.org/news/88"target="_blank" rel="noopener">Erlang R18</a>. Fique de olho nos dois!</p>
]]></content:encoded><category>elixir-e-erlang</category><category>engenharia-de-software</category><category>testes</category></item><item><title>Começando o Ano com um Ruby Drama: RubySpec vs MRI Core Team</title><link>https://akitaonrails.github.io/2015/01/02/comecando-o-ano-com-um-ruby-drama-rubyspec-vs-mri-core-team/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/01/02/comecando-o-ano-com-um-ruby-drama-rubyspec-vs-mri-core-team/</guid><pubDate>Fri, 02 Jan 2015 12:10:00 -0200</pubDate><description>&lt;p&gt;A comunidade internacional de Ruby vira e mexe surge com alguns &amp;ldquo;dramas&amp;rdquo;. Toda comunidade tem seus &amp;ldquo;dramas&amp;rdquo;, mas os Ruby Dramas particularmente chamam a atenção das demais comunidades então de vez em quando vale a pena explorar alguns deles para explicar porque eles acontecem e quais as ramificações.&lt;/p&gt;
&lt;p&gt;No último dia de 2014, Brian Shirai soltou um post unilateral entitulado &lt;a href="http://rubini.us/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/"target="_blank" rel="noopener"&gt;&amp;ldquo;Matz&amp;rsquo;s Ruby Developers Don&amp;rsquo;t Use RubySpec and It&amp;rsquo;s Hurting Ruby&amp;rdquo;&lt;/a&gt;, onde ele explica porque é ruim o Core Team do MRI não usar regularmente o projeto RubySpec, vale a pena dar uma lida.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A comunidade internacional de Ruby vira e mexe surge com alguns &ldquo;dramas&rdquo;. Toda comunidade tem seus &ldquo;dramas&rdquo;, mas os Ruby Dramas particularmente chamam a atenção das demais comunidades então de vez em quando vale a pena explorar alguns deles para explicar porque eles acontecem e quais as ramificações.</p>
<p>No último dia de 2014, Brian Shirai soltou um post unilateral entitulado <a href="http://rubini.us/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/"target="_blank" rel="noopener">&ldquo;Matz&rsquo;s Ruby Developers Don&rsquo;t Use RubySpec and It&rsquo;s Hurting Ruby&rdquo;</a>, onde ele explica porque é ruim o Core Team do MRI não usar regularmente o projeto RubySpec, vale a pena dar uma lida.</p>
<p>Como contexto, vale explicar que <a href="https://twitter.com/brixen"target="_blank" rel="noopener">Brixen</a> é quem herdou o manto da implementação Rubinius e também do projeto RubySpec, que tenta ser uma suíte de testes que define o comportamento do que é a &ldquo;linguagem Ruby&rdquo; para que implementações alternativas como o próprio Rubinius ou JRuby ou outras possam se basear. Os projetos originaram com Evan Phoenix, atual membro do Ruby Central e organizador das RailsConf. Digamos que Evan é mais &ldquo;politicamente correto&rdquo;, Brian é mais &ldquo;agressivo&rdquo;.</p>
<p>O problema é que o Brian basicamente &ldquo;joga a merda no ventilador e sai fora&rdquo;, que é o tipo de atitude mais infantil e menos efetivo possível. E vindo de um conhecido contribuidor open source como Brian, obviamente isso causou um shit-storm no Twitter e outras redes. Ele simplesmente &ldquo;acusa&rdquo; o Core Team do MRI de sabotar o RubySpec ao não usá-lo como ele gostaria. Um dos principais nomes que vieram para defender o Core Team foi <a href="https://twitter.com/headius/with_replies"target="_blank" rel="noopener">Charles Nutter</a>, que é contribuidor tanto do JRuby quanto do próprio MRI, alguns dos comentários mais interessantes e que explicam o ponto de vista do Core Team, eu cito abaixo:</p>
<blockquote>
@jamie_gaskins RubySpec was designed from the ground up. MRI's suite is decades old. What we need are people to help clean it up.
</blockquote>
<blockquote>
@josh_cheek And what of redoing over a decade of work in MRI's suite? It's there, it works, we use it to test JRuby. Don't buy the FUD.
</blockquote>
<blockquote>
@jamie_gaskins We use it as our primary compatibilty suite for JRuby. We also run RubySpec, but it's far less complete.
</blockquote>
<blockquote>
@josh_cheek I don't think anyone would disagree that MRI's suite needs improvement, but throwing it out is absurd.
</blockquote>
<blockquote>
@jamie_gaskins I also don't find MRI's tests hard to read. When I find ugly ones in the future, I will be fixing them. Help me :-)
</blockquote>
<blockquote>
Folks don't seem to realize JRuby runs MRI's test suite as our primary compatibility suite. It's not pretty, but it's OSS...help us fix it.
</blockquote>
<blockquote>
@jerrydantonio Please don't spread this kind of FUD. I know most of ruby-core personally and they've been very supportive of JRuby.
</blockquote>
<blockquote>
@jerrydantonio If they don't use RubySpec, it's because RubySpec did not meet their needs -- as stated in the article.
</blockquote>
<blockquote>
@jerrydantonio You are going based on what one person says without understanding the complexities involved.
</blockquote>
<blockquote>
@jamie_gaskins You can submit PRs to MRI's github/ruby/ruby mirror (which may turn into the main repo this year).
</blockquote>
<blockquote>
@jamie_gaskins If you start looking into it, copy me on your PRs. I'm a committer, and I can help get them in.
</blockquote>
<blockquote>
@jamie_gaskins Not at all, and ruby-core are all on board with switching to git now too. Like I said...that post is a massive dose of FUD.
</blockquote>
<blockquote>
Another reminder: I'm a member of the MRI core team and a committer. Got a problem? I'm listening. Let's collaborate, friends.
</blockquote>
<blockquote>
@samsaffron It doesn't matter if their suite serves their and our needs. It's aesthetics at that point. Aesthetics can be fixed.
</blockquote>
<blockquote>
@iainbeeston It just a political move. It's still alive and still OSS...if it's valuable to use it on MRI, we can still use it on MRI.
</blockquote>
<blockquote>
@zedshaw @mentalguy @raganwald The segfault was not reported. And yes, they fix bug reports. ruby-core is still almost all volunteer time.
</blockquote>
<blockquote>
@johnbender Don't take one article's assertions as truth. There's a lot of misinformation there.
</blockquote>
<blockquote>
@jc00ke @_zzak I'd like to know why RubySpec wins and MRI is at fault. Does Rubinius run MRI's suite, like JRuby?
</blockquote>
<blockquote>
@fugueish One person's story. I've been implementing Ruby longer and I have seen steady improvement. It's a community effort.
</blockquote>
<blockquote>
@josh_cheek And please don't spread FUD like this. There are real people working really hard on MRI. Help them instead.
</blockquote>
<blockquote>
@raganwald @codefolio @saturnflyer Indeed. But don't start saying things like "MRI devs don't write tests" because they usually do (now).
</blockquote>
<blockquote>
@chromatic_x The full run of MRI's suite on JRuby includes 5941 tests with 1091418 assertions (we exclude parts). They're indispensable.
</blockquote>
<blockquote>
@iainbeeston Either that, or it inspires ruby-core and us to improve MRI's suite. Positive things can come from a negative post. :-)
</blockquote>
<blockquote>
@chromatic_x In any case, MRI is the reference impl, so expecting them to drop their decades-old suite for someone else's is rather bold.
</blockquote>
<blockquote>
@headius Personally, I think that RubySpec is useful and valuable, but throwing MRI under the bus for not using a tool is pretty petty.
</blockquote>
<blockquote>
@r343l @sunnyrjuneja No, it doesn't. I don't agree with technical articles that don't take comments. Might as well be a press release.
</blockquote>
<blockquote>
Lesson for today: here's what you do when you find a bug in a project, rather than just blogging about it: https://bugs.ruby-lang.org/issues/10685 
</blockquote>
<p>Shyouhei Urabe, um dos principais voluntários japoneses do Core Team disse <a href="https://twitter.com/shyouhei/status/550557396376879104"target="_blank" rel="noopener">a coisa mais óbvia</a> logo que o post do Brian saiu:</p>
<blockquote>
Release of #ruby happens Dec. 25th EVERY YEAR.  If rubyspec found a SEGV, why didn't they report that and blame core devs instead?
</blockquote>
<p>Logo em seguida Nate Berkopec fez um apanhado geral e <a href="https://gist.github.com/nateberkopec"target="_blank" rel="noopener">uma explicação/especulação das razões</a> pelas quais o artigo de Brian Shirai está errado no que ele quer atingir.</p>
<p>Em resumo, existe uma grande frustração no time do Rubinius em geral. Ao contrário do JRuby que tem um nicho de usuários e projetos em produção onde ele faz diferença, o Rubinius em si nunca alcançou nenhum uso significativo. Em <a href="http://rubini.us/2013/10/15/introducing-rubinius-x/"target="_blank" rel="noopener">Outubro de 2013</a>, ele havia anunciado que o Rubinius começaria a experimentar mais e quebrar compatibilidade com o MRI o que, pra mim pelo menos, soa mais como um tiro no pé. Mas eles acharam que, por méritos técnicos, o Rubinius poderia ser o melhor substituto para o MRI. O controle do RubySpec leva nessa direção e por isso a frustração do Core Team nunca adotá-lo &ldquo;oficialmente&rdquo;.</p>
<p>Hoje (2/1/2015), o próprio Evan Phoenix resolveu endereçar o assunto pessoalmente no Twitter com o Matz, e eis a transcrição:</p>
<blockquote>
@evanphx 
1. I wonder if, all those years ago, I misunderstood Matz. Maybe "I like other ruby implementations" was really "I don't mind they exist"
</blockquote>
<blockquote>
@evanphx 
2. I thought it was "I want to help them" but Matz really didn't mean that. He just mean "good luck friends!"
</blockquote>
<blockquote>
@yukihiro_matz 
@evanphx Sorry for confusion & disappointment. I still don't know what kind of help you wanted. You do want to control your project as I do.
</blockquote>
<blockquote>
@evanphx 
@yukihiro_matz I should have asked: Could the CRuby team help track new features so other impls could know what they are missing.
</blockquote>
<blockquote>
@yukihiro_matz
@evanphx the MRI team maintains the forked version of RubySpec https://github.com/nurse/rubyspec  I think it can be a good start.
</blockquote>
<p>Existe muito mérito nos esforços que as pessoas contribuíram aos projetos Rubinius e RubySpec ao longo dos anos, porém eles falharam ao tentar forçar sua visão do que deveria ser &ldquo;o certo&rdquo; em vez de colaborar com o projeto que já existia muito antes deles chegarem que é o Ruby MRI. É como se você fizesse um kernel alternativo ao Linux e começasse a dizer nas listas por aí que o Linus Torvalds deveria seguir essa alternativa porque é o &ldquo;jeito mais certo&rdquo;. Sabe qual seria a resposta do Linus? &ldquo;Fuck You!&rdquo;. É que o Matz é <strong>bem</strong> mais educado do que isso, mas seria a resposta mais adequada a esse tipo de atitude.</p>
<p>Qual seria a alternativa? Charles Nutter já demonstrou: ele é ao mesmo tempo líder da implementação alternativa JRuby e também contribui diretamente no Ruby MRI. Ele roda as suítes do MRI e do RubySpec, em paralelo, no projeto JRuby. Não há atrito entre JRuby e MRI. Mas há atrito entre Rubinius e MRI. O problema não é técnico, não é sequer político como muitos declararam, é um problema de caráter e atitude. O problema é que o estilo do Core do Rubinius sempre foi &ldquo;somos os melhores, eventualmente todo mundo vai notar isso&rdquo;. Essa atitude é altamente destrutiva, frustrante e acaba com declarações suicidas como a do Brian Shirai.</p>
<p><a href="http://www.akitaonrails.com/2012/08/15/off-topic-o-mito-do-legado"target="_blank" rel="noopener">Uma coisa que eu sempre digo</a>: fazer um projeto green field (do zero) é muito fácil, qualquer criança faz isso. Agora, pegar um projeto brown field (velho, cheio de débitos técnicos), é bem mais complicado, bem menos recompensador a curto prazo e por isso poucos (só os melhores) conseguem a disciplina para tentar transformar um brown field num green field. O Rubinius e RubySpec são green fields que tentaram enfiar goela abaixo do Core Team. Não é assim que se prova um ponto de vista. Releia os comentários do Nutter acima, é o que ele tenta explicar.</p>
<p>E é uma pena, pois tecnicamente Brian e o Core Team do Rubinius são muito bons e seria bom ver esse talento dentro do MRI. Porém, entre ter alguém tecnicamente bom mas de caráter duvidoso, é melhor não ter. Essa sempre é a melhor alternativa.</p>
<p>Em resumo: nada muda do lado do Ruby MRI, não é nenhuma crise ou algo assim. O MRI deve continuar como sempre: com Matz como seu único ditador benevolente e talvez, com alguma boa vontade, eles permitam o projeto RubySpec ser incorporado dentro do MRI sob a tutela do Core Team do Japão, como deve ser.</p>
]]></content:encoded><category>ruby</category><category>testes</category><category>open-source</category></item><item><title>[Small Bite] Um pouco tarde: O Grande Debate Sobre TDD</title><link>https://akitaonrails.github.io/2014/08/23/small-bite-um-pouco-tarde-o-grande-debate-sobre-tdd/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/08/23/small-bite-um-pouco-tarde-o-grande-debate-sobre-tdd/</guid><pubDate>Sat, 23 Aug 2014 17:21:00 -0300</pubDate><description>&lt;p&gt;Estou eu neste instante (23/8), fazendo minhas pesquisas e reflexões, escrevendo a história que quero contar no fechamento da &lt;a href="http://www.rubyconf.com.br"target="_blank" rel="noopener"&gt;Rubyconf Brasil 2014&lt;/a&gt;, no próximo dia 29/8 (todos aqui estarão lá, certo!?), e eis que decido fazer um pequeno post.&lt;/p&gt;
&lt;p&gt;Obviamente eu soube que o DHH fez um keynote na última Railsconf em Chicago, que se tornou bastante controverso por levantar o assunto de &amp;ldquo;TDD está morto?&amp;rdquo;. Também soube que aconteceu um debate via Hangout entre o próprio David, Martin Fowler e ninguém menos que o criador do TDD, Kent Beck.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Estou eu neste instante (23/8), fazendo minhas pesquisas e reflexões, escrevendo a história que quero contar no fechamento da <a href="http://www.rubyconf.com.br"target="_blank" rel="noopener">Rubyconf Brasil 2014</a>, no próximo dia 29/8 (todos aqui estarão lá, certo!?), e eis que decido fazer um pequeno post.</p>
<p>Obviamente eu soube que o DHH fez um keynote na última Railsconf em Chicago, que se tornou bastante controverso por levantar o assunto de &ldquo;TDD está morto?&rdquo;. Também soube que aconteceu um debate via Hangout entre o próprio David, Martin Fowler e ninguém menos que o criador do TDD, Kent Beck.</p>
<p>Finalmente hoje assisti tanto o keynote quanto os vídeos do debate, que eu não havia assistido até agora. Devo dizer que enquanto eu estava assistindo meus dedos estavam coçando para escrever sobre o assunto. Porém, devo dizer que desisti - pelo por enquanto - de fazer um artigo para dar minha opinião detalhada.</p>
<p>Em resumo, devo dizer que concordo com tudo que o DHH disse em seu keynote. Sinceramente não vi nada de &ldquo;controverso&rdquo;. Na verdade acho que nenhum programador experiente viu qualquer problema no que ele disse, mas ficou preocupado com o que todo o resto poderia interpretar o que ele disse. E de fato foi o que aconteceu.</p>
<p>Também não foi nada surpreendente ver que Martin, Kent e David compartilham exatamente da mesma opinião sobre o assunto, variando apenas nos detalhes menores.</p>
<p>Eu evito ao máximo debates sobre tecnologia, sinceramente os que eu assisti até hoje foram grandes perdas de tempo, com muita falácia, muito superficialismo, muitos jovens inexperientes com testosterona alta fazendo de tudo para medir quem tem o maior pau.</p>
<p>E este foi o primeiro debate que me fez sentir muito bem em continuar assistindo (tanto que assisti as 6 partes do debate de uma vez só). Foi um belo debate, de rara qualidade e, depois de ter terminado, me fez decidir não detalhar o assunto mais do que já foi discutido. Acho que todos eles apresentaram os argumentos que mais importam de forma que eu demoraria muitas centenas de milhares de palavras para descrever novamente.</p>
<p>Portanto, deixo aqui os links para vocês mesmos assistirem, lerem e chegarem às suas próprias conclusões:</p>
<ul>
<li><a href="http://www.confreaks.com/videos/3315-railsconf-keynote-writing-software"target="_blank" rel="noopener">Keynote da Railsconf 2014</a></li>
<li><a href="http://david.heinemeierhansson.com/2014/test-induced-design-damage.html"target="_blank" rel="noopener">Post do DHH: Test-induced design damage</a></li>
<li><a href="https://www.youtube.com/watch?v=z9quxZsLcfo"target="_blank" rel="noopener">Is TDD Dead? Parte 1</a></li>
<li><a href="https://www.youtube.com/watch?v=JoTB2mcjU7w"target="_blank" rel="noopener">Is TDD Dead? Parte 2</a></li>
<li><a href="https://www.youtube.com/watch?v=YNw4baDz6WA"target="_blank" rel="noopener">Is TDD Dead? Parte 3</a></li>
<li><a href="https://www.youtube.com/watch?v=dGtasFJnUxI"target="_blank" rel="noopener">Is TDD Dead? Parte 4</a></li>
<li><a href="https://www.youtube.com/watch?v=gWD6REVeKW4"target="_blank" rel="noopener">Is TDD Dead? Parte 5 e 6</a></li>
</ul>
<p>Desde sempre, mas principalmente depois desse episódio, muita gente me pergunta sobre o que eu acho e minha resposta desde sempre e também agora depois de assistir o debate (ninguém disse nada que já não soubéssemos), concluo dizendo:</p>
<blockquote>
Não importa se é Test-First ou Test-After. Apenas faça testes.
</blockquote>
<p>Programação é uma profissão de <strong>prática</strong>! Se você ainda está raivosamente querendo defender o ponto a favor, o ponto contra, entenda uma coisa: lhe falta prática. Pois mesmo com toda a HackerNews e Reddit discutindo, nunca senti um pingo de vontade de entrar na discussão. Não porque não me importo, pelo contrário, eu me importo com código bem escrito e bem testado. Só que ao discutir num fórum sobre os méritos de testar ou não eu só tenho 2 certezas:</p>
<ol>
<li>Não estou ficando melhor no meu código com isso.</li>
<li>Eu já sei como me sinto com um código não testado (dica: mal)</li>
</ol>
<p>Então vá praticar: lhe falta prática, por isso falta segurança para lidar esse tipo de assunto.</p>
<p>Querem que eu discuta algum aspecto em particular do assunto? Pergunte nos comentários e, talvez, eu decida escrever um post mais detalhado sobre o assunto se ver que alguma coisa ainda ficou mal explicada depois de todo o esforço do Martin, Kent e DHH.</p>
]]></content:encoded><category>testes</category><category>engenharia-de-software</category></item><item><title>CodeClimate, Qualidade de Código e os Rubistas Sádicos</title><link>https://akitaonrails.github.io/2014/01/30/codeclimate-qualidade-de-codigo-e-os-rubistas-sadicos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/01/30/codeclimate-qualidade-de-codigo-e-os-rubistas-sadicos/</guid><pubDate>Thu, 30 Jan 2014 19:59:00 -0200</pubDate><description>&lt;p&gt;Análise estática de qualidade de código executado automaticamente é uma daquelas coisas que quando você se acostuma a usar passa a pensar &lt;strong&gt;&amp;ldquo;como diabos eu sobrevivia antes sem isso?&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se ainda não conhece, trate de experimentar o excelente &lt;a href="http://codeclimate.com"target="_blank" rel="noopener"&gt;Code Climate&lt;/a&gt; criado por &lt;a href="https://github.com/brynary"target="_blank" rel="noopener"&gt;Bryan Helmkamp&lt;/a&gt;, um rubista muito conhecido na comunidade por contribuições em diversos projetos open source, incluindo o próprio Rails e bibliotecas que já foram muito usadas algum tempo atrás como webrat.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Análise estática de qualidade de código executado automaticamente é uma daquelas coisas que quando você se acostuma a usar passa a pensar <strong>&ldquo;como diabos eu sobrevivia antes sem isso?&rdquo;</strong></p>
<p>Se ainda não conhece, trate de experimentar o excelente <a href="http://codeclimate.com"target="_blank" rel="noopener">Code Climate</a> criado por <a href="https://github.com/brynary"target="_blank" rel="noopener">Bryan Helmkamp</a>, um rubista muito conhecido na comunidade por contribuições em diversos projetos open source, incluindo o próprio Rails e bibliotecas que já foram muito usadas algum tempo atrás como webrat.</p>
<p>O que o Code Climate faz é muito simples: você cadastra seu projeto público ou privado do Github e ele vai começar a processá-lo até surgir com uma nota que vai de zero a quatro. Eu ainda não vi como é um projeto nota Zero mas considerando que já trabalhamos em alguns projetos de resgate que começou em notas como 2 - e já era lamentável - zero significa <strong>&ldquo;mude de profissão, você não serve para isso.&rdquo;</strong></p>
<p>Veja um dos meus projetos de cliente (e todo projeto de cliente tem o mesmo nível):</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/393/big_Screen_Shot_2014-01-30_at_7.40.13_PM.png" alt="Nota de todo projeto de cliente no Code Climate"  loading="lazy" /></p>
<p>Aliás, quando recebo código dos outros pra melhorar, rodar no Code Climate é a primeira coisa que eu faço para mostrar a profundidade do buraco de uma forma numérica que todo cliente consegue entender.</p>
<p>Mas como ele faz isso? Ele analiza todos os arquivos Ruby do projeto calculando itens de Complexidade (quantidade de assignments (mudança de valores de variáveis), branches (ifs, ifs dentro de ifs)), Complexidade por Método, Duplicação de código, quantidade de vezes que o arquivo foi modificado no Git, quantidade de linhas de código.</p>
<p>Além disso ele faz um scan de segurança, buscando por 20 buracos comuns de segurança como SQL Injection, Cross Site Request Forgery, Denial of Service, Session Setting e assim por diante.</p>
<p>E finalmente, ele se integra com o <a href="http://travis-ci.com/"target="_blank" rel="noopener">Travis-CI</a> e checa por cobertura de testes.</p>
<p>Melhor ainda: a cada <tt>git push</tt> que você dá no seu repositório, o Code Climate automaticamente puxa a atualização e processa tudo de novo e rapidamente envia um email a todos da equipe dizendo se a nota subiu ou caiu, incentivando a consertar o que piorou de qualidade imediatamente. É o mesmo conceito de um continuous integration como o Travis-CI que também executa em paralelo e imediatamente diz se um teste quebrou ou se a cobertura de testes diminuiu.</p>
<p>O correto é sempre estar a 100% de qualidade &ldquo;conhecida&rdquo; e 100% de segurança &ldquo;conhecida&rdquo; e 100% de cobertura de testes. Começando com tudo a 100%, agora é só manter o nível e consertar imediatamente quando ela cair. Não existe forma melhor de desenvolver. O errado é a equipe trabalhar com um código &ldquo;opaco&rdquo;, uma caixa preta que ninguém tem certeza se tem algum mínimo de qualidade, onde as pessoas &ldquo;acham&rdquo; que o código deve estar bom, onde os testes &ldquo;provavelmente&rdquo; rodam e passam. Esse é o jeito mais errado de se desenvolver.</p>
<p>E é errado porque a nota do Code Climate não significa que o código está realmente bom, ele apenas indica o que é tão óbvio que é errado que até um processo automatizado é capaz de encontrar. O Code Climate não é um auditor de código, ele meramente detecta alguns dos muitos <a href="http://www.codinghorror.com/blog/2006/05/code-smells.html"target="_blank" rel="noopener">&ldquo;code smells&rdquo;</a>. Um smell pode ser um falso-positivo, ou seja, não há outra maneira mesmo de se desenvolver de outra forma.</p>
<p>Ou seja, ter nota perto de 4 no Code Climate é o mínimo que todo projeto deve ter. Notas 2 e abaixo disso são projetos vergonhosos, dignos de se jogar fora.</p>
<h2>Rubistas Sádicos<span class="hx:absolute hx:-mt-20" id="rubistas-sádicos"></span>
    <a href="#rubistas-s%c3%a1dicos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Por coincidência, a primeira palestra/screencast que eu já fiz foi uma tradução literal de outra excelente palestra. Ela se chama <a href="http://www.akitaonrails.com/2008/06/14/machucando-c-digo-por-divers-o-e-lucro#.UurIonmpy5c"target="_blank" rel="noopener">&ldquo;Machucando Código por Diversão e Lucro&rdquo;</a> que publiquei em 24 de Junho de 2008.</p>
<p>A palestra original foi apresentado por Ryan Davis, mais conhecido como <a href="http://zenspider.com"target="_blank" rel="noopener">@zenspider</a>, um dos rubistas mais antigos e não tão conhecido. Ele criou algumas ferramentas muito importantes junto de um dos melhores grupos de Ruby que já existiu, o <a href="http://www.seattlerb.org"target="_blank" rel="noopener">Seattle.rb - Seattle Ruby Brigade</a> que apresentou ao mundo alguns dos melhores rubistas da primeira geração, incluindo o próprio Ryan Davis, Evan Phoenix (criador do projeto Rubinius), Eric Hodel (mantenedor do Rubygems, RDoc), Geoffrey Grosenbach (do Peepcode), Aaron Petterson (mais conhecido como @tenderlove). Aliás, a <a href="http://zenspider.com/Languages/Ruby/QuickRef.html"target="_blank" rel="noopener">Referência Rápida de Ruby</a> do Seattle.rb contém dicas importantes que muitos ainda ignoram.</p>
<p>O que o Bryan fez no Code Climate foi criar uma camada web de usabilidade Software-as-a-Service por cima de algumas bibliotecas bem antigas. Duas delas do Ryan Davis, o <a href="http://ruby.sadi.st/Flog.html"target="_blank" rel="noopener">Flay</a> e o <a href="http://ruby.sadi.st/Flay.html"target="_blank" rel="noopener">Flog</a>, duas das ferramentas de tortura essenciais de um <a href="http://ruby.sadi.st/Ruby_Sadist.html"target="_blank" rel="noopener">Rubista Sádico</a>.</p>
<p>Assista a minha palestra traduzida do Ryan que explica esse assunto em mais detalhes. Mas a nota que o Code Climate gera é baseado nas notas que o Flay e o Flog devolvem ao torturar seu código. Exatamente como descrito no site dos Rubistas Sádicos, o Flog lhe mostra os códigos mais tortuosos que você escreveu, quanto mais doloroso o código, maior a nota. O Flay analiza a estrutura do código Ruby por similaridades estruturais; diferenças de nomes de variáveis, espaços em branco, e estilo de programação são ignorados.</p>
<p>Além do Flay e do Flog, o Bryan adicionou o projeto <a href="http://brakemanscanner.org"target="_blank" rel="noopener">Brakeman</a>, que é o scanner de segurança. O Bryan explica como ele calcula a <a href="https://codeclimate.com/docs#quality-metrics"target="_blank" rel="noopener">métrica do Code Climate</a> em seu site.</p>
<p>Uma coisa que todo bom rubista já fez foi executar essas ferramentas manualmente em seus projetos. O Flay, Flog, Heckle, <a href="https://github.com/colszowka/simplecov"target="_blank" rel="noopener">SimpleCov</a>, Brakeman, <a href="https://github.com/ruby-prof/ruby-prof"target="_blank" rel="noopener">RubyProf</a> e outros analisam seu código geram relatórios que lhe dão indicativos de como melhorar cada vez mais seus projetos. Você pode inclusive fazer o mesmo que o Bryan: amarrar a execução dessas ferramentas em alguma runner como o Jenkins, por exemplo. Vale o exercício, quem vai criar o primeiro Code Climate open source?</p>
<p>Seja um Rubista Sádico e machuque seu código por diversão e lucro também. Código não revida, quanto mais você machucá-lo, mais vai se divertir. Código que não é massacrado o tempo todo é muito frágil, e merece ser destruído.</p>
]]></content:encoded><category>engenharia-de-software</category><category>testes</category><category>ruby</category></item><item><title>A Controvérsia Test::Unit vs RSpec/Cucumber</title><link>https://akitaonrails.github.io/2011/04/17/a-controversia-test-unit-vs-rspec-cucumber/</link><guid isPermaLink="true">https://akitaonrails.github.io/2011/04/17/a-controversia-test-unit-vs-rspec-cucumber/</guid><pubDate>Sun, 17 Apr 2011 15:26:00 -0300</pubDate><description>&lt;p&gt;Alguns dias atrás, o bom e velho &lt;a href="http://www.twitter.com/dhh"target="_blank" rel="noopener"&gt;@dhh&lt;/a&gt; começou uma controvérsia na comunidade. Eu diria até que foi uma discussão saudável. Leia a cobertura do acontecido na &lt;a href="http://www.rubyinside.com/dhh-offended-by-rspec-debate-4610.html"target="_blank" rel="noopener"&gt;RubyInside&lt;/a&gt; mas em resumo ele fez a seguinte sequência de tweets:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Pergunta: que framework de teste vocês usam na 37signals? Resposta: Test::Unit com uso ocasional do mocha. (Isso é tudo que você precisa para bons testes.)”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;“Eu respeito os caras por trás disso e sou totalmente a favor de experimentação, mas a proliferação de rSpec e Cucumber me deixa triste.”&lt;/p&gt;</description><content:encoded><![CDATA[<p>Alguns dias atrás, o bom e velho <a href="http://www.twitter.com/dhh"target="_blank" rel="noopener">@dhh</a> começou uma controvérsia na comunidade. Eu diria até que foi uma discussão saudável. Leia a cobertura do acontecido na <a href="http://www.rubyinside.com/dhh-offended-by-rspec-debate-4610.html"target="_blank" rel="noopener">RubyInside</a> mas em resumo ele fez a seguinte sequência de tweets:</p>
<blockquote>
  <p>“Pergunta: que framework de teste vocês usam na 37signals? Resposta: Test::Unit com uso ocasional do mocha. (Isso é tudo que você precisa para bons testes.)”</p>

</blockquote>
<blockquote>
  <p>“Eu respeito os caras por trás disso e sou totalmente a favor de experimentação, mas a proliferação de rSpec e Cucumber me deixa triste.”</p>

</blockquote>
<blockquote>
  <p>“RSpec me ofende esteticamente com nenhum benefício discernível pela sua complexidade adicionada sobre Test::Unit.”</p>

</blockquote>
<blockquote>
  <p>“Cucumber não faz sentido a menos que você tenha clientes lendo os testes. Por que você construiria um parser específico de testes para inglês?”</p>

</blockquote>
<blockquote>
  <p>“A coisa importante, é claro, é que consigamos fazer as pessoas testarem, então ferramentas não deveriam importar muito. Mas a complexidade extra ainda me chateia.”</p>

</blockquote>
<p>Sendo sincero, eu também compartilho da mesma opinião do @dhh. E não, não é <a href="http://akitaonrails.com/2008/12/16/off-topic-m-todo-cient-fico-vs-cargo-cult"target="_blank" rel="noopener">cargo cult</a>, antes que algum engraçadinho faça o comentário: lembro de 1 ou 2 anos atrás onde eu e o Carlos Brando estávamos conversando justamente como voltar pra Test::Unit era até um alívio. Também lembro de discutir o assunto do Cucumber com o Daniel V. Lopes, que pelo menos na época preferia Steak justamente porque nenhum cliente iria ler os testes escritos em inglês e portanto isso era redundante.</p>
<p>Só para adiantar a conclusão: se alguém estava levando a discussão para o nível de qual sintaxe é “melhor”, ou qual é mais “elegante”, ou qual é mais “suscinta”, você está indo na direção errada e vai tirar conclusões sem nenhum fundamento também. Então vamos com calma que, pra variar, a leitura será longa.</p>
<h2>Introdução<span class="hx:absolute hx:-mt-20" id="introdução"></span>
    <a href="#introdu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>É importante que as pessoas primeiro coloquem a imagem dos códigos na cabeça antes de continuarmos a discussão. Este é um exemplo pequeno de um teste unitário escrito com RSpec:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span> <span class="no">Cart</span><span class="p">,</span> <span class="s2">&#34;.sub_total&#34;</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="n">before</span><span class="p">(</span><span class="ss">:each</span><span class="p">)</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@cart</span> <span class="o">=</span> <span class="no">Factory</span><span class="p">(</span><span class="ss">:cart_with_two_5_dollar_items</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">it</span> <span class="s2">&#34;should calculate subtotal correctly&#34;</span> <span class="k">do</span> <span class="vi">@cart</span><span class="o">.</span><span class="n">sub_total</span><span class="o">.</span><span class="n">should</span> <span class="o">==</span> <span class="mi">10</span><span class="o">.</span><span class="mo">00</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Este é um pequeno trecho de um teste do arquivo <tt>cart_spec.rb</tt> do projeto <a href="https://github.com/drhenner/ror_ecommerce"target="_blank" rel="noopener">ror_ecommerce</a>. O que uma DSL como RSpec fornece é uma descrição suscinta em “quase-inglês” que poderíamos ler assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span> <span class="no">Cart</span><span class="p">,</span> <span class="s2">&#34;.sub_total&#34;</span> <span class="k">do</span> <span class="c1"># descrevendo o sub total de um Cart  </span>
</span></span><span class="line"><span class="cl"> <span class="n">before</span><span class="p">(</span><span class="ss">:each</span><span class="p">)</span> <span class="k">do</span> <span class="c1"># antes de cada teste execute:  </span>
</span></span><span class="line"><span class="cl"> <span class="vi">@cart</span> <span class="o">=</span> <span class="no">Factory</span><span class="p">(</span><span class="ss">:cart_with_two_5_dollar_items</span><span class="p">)</span> <span class="c1"># faça @cart ser o resultado da Fábrica com 2 ítens de $5  </span>
</span></span><span class="line"><span class="cl"> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">it</span> <span class="s2">&#34;should calculate subtotal correctly&#34;</span> <span class="k">do</span> <span class="c1"># o cart deve calcular o subtotal corretamente @cart.sub_total.should == 10.00 # sub total do @cart deve ser igual a 10.00 end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vejamos um trecho de cucumber:</p>
<hr>
<p>Feature: adding a comment</p>
<p>As a Ruby developer I would like to comment on a gem So that I can help the community track which gems work with Ruby 1.9 Scenario: adding a comment Given an initialised database And a gem called “rubynuts” When I visit the page for “rubynuts” Then I see the comment form When I add a comment And I press “submit comment” Then I see my comment on the page</p>
<hr>
<p>Este trecho foi retirado do projeto <a href="http://isitruby19.com/"target="_blank" rel="noopener">isitruby19</a>. Acho que não preciso traduzir, com certeza todos vocês consegue ler o que está escrito, certo??</p>
<p>Mas este arquivo de Cucumber, sozinho, não faz tudo. Ele precisa necessariamente de outro código que faz par com ele por baixo dos panos, este sim, escrito em Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">general_steps</span><span class="o">.</span><span class="n">rb</span>  
</span></span><span class="line"><span class="cl"><span class="no">Given</span> <span class="n">an</span> <span class="n">initialised</span> <span class="n">database</span><span class="err">$</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="no">Platform</span><span class="o">.</span><span class="n">load_defaults</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">code_steps</span><span class="o">.</span><span class="n">rb</span>  
</span></span><span class="line"><span class="cl"><span class="no">Given</span> <span class="n">a</span> <span class="n">gem</span> <span class="n">called</span> <span class="s2">&#34;(.*)&#34;</span><span class="err">$</span> <span class="k">do</span> <span class="o">|</span> <span class="nb">name</span> <span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="n">code</span> <span class="o">=</span> <span class="no">Code</span><span class="o">.</span><span class="n">find_by_name</span> <span class="nb">name</span>  
</span></span><span class="line"><span class="cl"> <span class="n">code</span><span class="o">.</span><span class="n">destroy</span> <span class="k">unless</span> <span class="n">code</span><span class="o">.</span><span class="n">nil?</span>
</span></span><span class="line"><span class="cl"> <span class="n">code</span> <span class="o">=</span> <span class="n">a_saved</span> <span class="no">Code</span><span class="p">,</span> <span class="ss">:name</span> <span class="o">=&gt;</span> <span class="nb">name</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">When</span> <span class="n">I</span> <span class="n">visit</span> <span class="n">the</span> <span class="n">page</span> <span class="k">for</span> <span class="s2">&#34;(.*)&#34;</span><span class="err">$</span> <span class="k">do</span> <span class="o">|</span> <span class="nb">name</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="n">visit</span> <span class="s2">&#34;/</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">comment_steps</span><span class="o">.</span><span class="n">rb</span>  
</span></span><span class="line"><span class="cl"><span class="no">Then</span> <span class="n">I</span> <span class="n">see</span> <span class="n">the</span> <span class="n">comment</span> <span class="n">form</span><span class="err">$</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="n">response</span><span class="o">.</span><span class="n">should</span> <span class="n">have_tag</span><span class="p">(</span><span class="s1">&#39;div#new-comment-form&#39;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">When</span> <span class="n">I</span> <span class="n">add</span> <span class="n">a</span> <span class="n">comment</span><span class="err">$</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="n">fill_in</span> <span class="s2">&#34;Name&#34;</span><span class="p">,</span> <span class="ss">:with</span> <span class="o">=&gt;</span> <span class="s1">&#39;Henry the Tester&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">fill_in</span> <span class="s2">&#34;Email&#34;</span><span class="p">,</span> <span class="ss">:with</span> <span class="o">=&gt;</span> <span class="s1">&#39;&lt;henry@testing.com&gt;&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">choose</span> <span class="ss">:comment_works_for_me_true</span>  
</span></span><span class="line"><span class="cl"> <span class="n">fill_in</span> <span class="s2">&#34;Version&#34;</span><span class="p">,</span> <span class="ss">:with</span> <span class="o">=&gt;</span> <span class="s1">&#39;1.0&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="nb">select</span> <span class="s1">&#39;Mac OSX&#39;</span><span class="p">,</span> <span class="ss">:from</span> <span class="o">=&gt;</span> <span class="s1">&#39;Platform&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">fill_in</span> <span class="ss">:comment_body</span><span class="p">,</span> <span class="ss">:with</span> <span class="o">=&gt;</span> <span class="s1">&#39;Here is my test comment&#39;</span> <span class="c1"># have to request via ID rather than label because of the span around the optional, making it hard to find  </span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">webrat_steps</span><span class="o">.</span><span class="n">rb</span>  
</span></span><span class="line"><span class="cl"><span class="no">When</span> <span class="n">I</span> <span class="n">press</span> <span class="s2">&#34;(.*)&#34;</span><span class="err">$</span> <span class="k">do</span> <span class="o">|</span><span class="n">button</span><span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="n">click_button</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">comment_steps</span><span class="o">.</span><span class="n">rb</span>  
</span></span><span class="line"><span class="cl"><span class="no">Then</span> <span class="n">I</span> <span class="n">see</span> <span class="n">my</span> <span class="n">comment</span> <span class="n">on</span> <span class="n">the</span> <span class="n">page</span><span class="err">$</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="n">response</span><span class="o">.</span><span class="n">should</span> <span class="n">include_text</span><span class="p">(</span><span class="s1">&#39;Here is my test comment&#39;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que sobre cada trecho eu tirei em que arquivo ele estava. Sem explicar muito, se vocês lerem a especificação em inglês e depois ler com calma o código Ruby abaixo, verá que cada frase tem um equivalente em Ruby. Esse código equivalente é chamado de “Step Definition” no Cucumber, e pode estar em qualquer arquivo no <tt>load path</tt>.</p>
<p>O objetivo do Cucumber é que essas “User Stories” sejam escrita em inglês em conjunto com o cliente final. E a partir daí elas se tornam “especificações executáveis” em vez de um mero papel que não tem como validar automaticamente. Mas depois precisamos reescrever tudo em Ruby. Por isso muitos dizem que, se você não pretende usar isso com um cliente de verdade, parece um trabalho duplicado.</p>
<p>Finalmente vejamos um teste no antigo Test::Unit (em particular com suporte do Rails):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Cart</span> <span class="o">&lt;</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">TestCase</span>  
</span></span><span class="line"><span class="cl"> <span class="k">def</span> <span class="nf">setup</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@cart</span> <span class="o">=</span> <span class="no">Factory</span><span class="p">(</span><span class="ss">:cart_with_two_5_dollar_items</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">test</span> <span class="s2">&#34;should calculate subtotal correctly&#34;</span> <span class="k">do</span> <span class="n">assert_equal</span> <span class="mi">10</span><span class="o">.</span><span class="mo">00</span><span class="p">,</span> <span class="vi">@cart</span><span class="o">.</span><span class="n">sub_total</span> <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Este eu fiz de propósito. Se você comparar somente no escopo deste micro-teste, tanto a versão em Test::Unit quanto em RSpec não tem praticamente diferença nenhuma. Mas claro que não é só isso e existem dezenas de outras funcionalidades que o RSpec tem sobre o Test::Unit.</p>
<p>Para iniciar em cada uma das ferramentas leia:</p>
<ul>
<li><a href="http://pragprog.com/titles/achbd/the-rspec-book"title="que cobre Cucumber"target="_blank" rel="noopener">Livro RSpec Book</a></li>
<li><a href="http://guides.rubyonrails.org/testing.html"target="_blank" rel="noopener">Documentação Test::Unit para Rails</a></li>
</ul>
<h2>Refletindo sobre a História<span class="hx:absolute hx:-mt-20" id="refletindo-sobre-a-história"></span>
    <a href="#refletindo-sobre-a-hist%c3%b3ria" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A comunidade Ruby, Agile e outras ao redor se concentraram bastante nos assuntos Test Driven Development e Behavior Driven Development. Mas não se esqueçam que testar software é um assunto <a href="http://en.wikipedia.org/wiki/Software_testing"target="_blank" rel="noopener">muito mais amplo</a> do que isso. Muitos outros vieram antes de nós explorando, pesquisando, documentando e refinando esse conhecimento. Resumidamente a história passou já por esses estágios:</p>
<ul>
<li>Até 1956 – orientado a debugging</li>
<li>1957-1978 – orientado a demonstração</li>
<li>1979-1982 – orientado a destrução</li>
<li>1983-1987 – orientado a avaliação</li>
<li>1988-2000 – orientado a prevenção</li>
</ul>
<p>O que conhecemos hoje como níveis de testes, que vai de unitário, integrado e de sistema, além de definições, terminologias, classificações, estão todas descritas no Software Engineering Body of Knowledge (SWEBOK) e você pode ler o capítulo específico sobre <a href="http://www.computer.org/portal/web/swebok/html/contentsch5#ch5"target="_blank" rel="noopener">Software Testing</a> na versão gratuita do livro disponível online.</p>
<p>Os exemplos de código que dei acima assumem muita coisa. Assumem que você entende todos os conceitos e terminologias de testes, que entende porque eles são assim. Assumem que você entende qual a diferença entre “Use Cases”, “User Stories” e “Narrative”. Assume que você entende como e porque TDD existe e também como se evoluiu para BDD. Se você não entende, leia os links acima nesta seção e também este artigo <a href="http://www.rbcs-us.com/images/documents/User-Stories-and-Test-Driven-Development.pdf"target="_blank" rel="noopener">A Story about User Stories and Test-Driven Development</a>.</p>
<p>O Test::Unit que conhecemos hoje do Ruby é herança da idéia original de TDD do Kent Beck. Foi escrito por <strong>Nathaniel Talbott</strong> originalmente com o nome de <a href="http://lapidary.sourceforge.net/"target="_blank" rel="noopener">Lapidary</a> e demonstrado na primeira e lendária RubyConf de 2001. Ele se inspirou na documentação original de Kent Beck, <a href="http://www.xprogramming.com/testfram.htm"target="_blank" rel="noopener">Simple Smalltalk Testing With Patterns</a> que é o pai de todos os frameworks de TDD atuais. O objetivo do Lapidary era o mesmo de todo framework TDD:</p>
<blockquote>
  <p>Testes Unitários está crescendo em todo lugar, principalmente pelo fato de ser uma prática essencial do XP. Embora XP seja grande, testes unitários existem há um longo tempo e sempre foi uma boa idéia. Uma das chaves para bons testes unitários, não é somente escrever testes, mas ter testes. Qual a diferença? Bem, se você apenas escreve um teste e joga fora, você não tem garantias que alguma coisa não vai mudar mais tarde e quebrar seu código. Se, por outro lado, você tem testes (obviamente você precisa escrevê-los primeiro), e os executa tão frequentemente quanto possível, você lentamente constrói uma parede de coisas que não podem quebrar sem você imediatamente saber disso. É quanto testes unitários atingem seu pico de utilidade.</p>

</blockquote>
<p>Então, em maio de 2002, o Lapidary foi substituído por <a href="http://test-unit.rubyforge.org/"target="_blank" rel="noopener">Test::Unit</a>, o pacote oficial de testes unitários que vem pré-instalado junto de todo Ruby – e por isso é particularmente útil em comparação com outros pacotes de programação que não tem um pacote padrão de testes.</p>
<p>Depois de Alistair Cockburn, depois de Kent Beck, depois de TDD e XP, quem já estava dentro do assunto anos atrás começou a pensar em evolução. Um deles foi Dave Astels que escreveu este artigo <a href="http://techblog.daveastels.com/2005/07/05/a-new-look-at-test-driven-development/"target="_blank" rel="noopener">A New Look at Test Driven Development</a>. Ele explica os problemas do TDD e como se transita para um novo paradigma chamado BDD. Na mesma época Dan North estava evoluindo o conceito de User Stories do Alistair para o formato canônico que conhecemos hoje, por exemplo, com o famoso artigo <a href="http://dannorth.net/whats-in-a-story/"target="_blank" rel="noopener">What’s in a Story</a>, criando as fundações para o Behavior Driven Development, ao mesmo tempo implementando <a href="http://dannorth.net/introducing-bdd/"target="_blank" rel="noopener">JBehave</a> para ser usado em vez de JUnit.</p>
<p>O foco passaria a ser centrado em “comportamento” e o Astels explica:</p>
<blockquote>
  <p>Uma grande diferença é o vocabulário. Em vez de fazer subclasses de <tt>TestCase</tt>, você faz subclasses de <tt>Context</tt>. Em vez de escrever métodos que começam com <tt>test</tt> você começa com <tt>should</tt>, ou preferencialmente você não precisa se preocupar com o padrão de nomenclatura de forma a poder escolher o nome mais apropriado. Em vez de fazer verificação com <tt>assertions</tt> (ex. <tt>assertEquals(expected, actual)</tt>) você especifica pós-condições com algo como <tt>shouldBeEqual(actual, expected)</tt>.</p>

</blockquote>
<p>O livro do David Chelimsky, o recém-lançado <a href="http://pragprog.com/titles/achbd/the-rspec-book"target="_blank" rel="noopener">The RSpec Book</a> continua a história:</p>
<blockquote>
  <p>RSpec foi criado por Steven Baker em 2005. Steven ouvira falar sobre BDD a partir do Aslak Hellesøy, que trabalhara em um projeto com Dan North quando a idéia surgiu da primeira vez. Steven já estava interessado na idéia quando Dave Astels sugeriu que com linguagens como Smalltalk e Ruby, nós poderíamos mais facilmente explorar novos frameworks de TDD que poderiam encorajar o foco em comportamento. E RSpec nasceu.</p>
<p>Embora os detalhes sintáticos tenham evoluído desde a versão original do RSpec do Steven, a premissa básica permanece. Nós usamos RSpec para escrever exemplos executáveis de comportamento esperado de um pequeno pedaço de código em um contexto controlado.</p>

</blockquote>
<p>Por isso eu insisto tanto em pesquisar novamente definições e história. RSpec, não é nada radicalmente diferente de um Test::Unit. Mas seu foco é tentar <strong>encorajar</strong> os desenvolvedores a pensar na forma de <strong>comportamento</strong> (“Behavior”) e o primeiro pequeno passo é renomear algumas palavras-chaves na forma que definimos testes unitários para tentar guiar nessa direção. Ou seja, se estiver consciente disso, obviamente você pode escrever no estilo BDD usando a mesma sintaxe do Test::Unit atual (por isso os dois testes que escrevi acima, com Test::Unit e depois com RSpec, não parecem tão diferente, olhando superficialmente).</p>
<p>Vou pegar algumas coisas emprestadas do livro do Chelimsky, a começar pela definição de “Behavior Driven Development”</p>
<blockquote>
  <p>Behavior Driven Development tem como objetivo implementar uma aplicação através da descrição de seu comportamento a partir da perspectiva de seus stakeholders.</p>

</blockquote>
<p>Quando praticamos somente TDD, sem atenção às outras práticas de XP que certamente cobrem User Stories, repriorização de Backlog, acabamos nos focando apenas na parte técnica. No final temos um excelente sistema, maravilhosamente coberto com testes unitários, testes de integração, porém o objetivo do projeto – o objetivo dos stakeholders que pagam por esse projeto – não foi atingido. Isso é uma coisa difícil de explicar para programadores cabeça-dura: em bom francês, o cliente está c@gando e andando se seu software tem 400% de cobertura de testes unitários se o objetivo dele era lançar 4 meses atrás na metade do custo e ainda está faltando funcionalidade que ele precisava.</p>
<p>A idéia do BDD, do ponto de vista de implementação é tentar direcionar os programadores em direção à implementação do que realmente interessa ao stakeholder – e não ao seu ego de hacker.</p>
<p>Seguindo esse raciocínio, uma coisa importante num processo Ágil é a definição de <a href="http://agilesoftwaredevelopment.com/2006/05/definition-of-done"target="_blank" rel="noopener">Done</a> (Pronto). Sem isso não existe parâmetro para saber se um software está pronto ou não. Para o programador é quando ele “acha” que o código está bom, para o stakeholder é quando ele “acha” que funciona como ele “acha” que queria. Rastrear User Stories ou Use Cases ou o que for, manualmente, é muito trabalhoso.</p>
<p>Novamente retornando ao livro do Chelimsky:</p>
<blockquote>
  <p>Em 2003, Chris Stevenson, que estava trabalho com Aslak na ThoughtWorks na época, criou uma pequena ferramenta em Java chamada <a href="http://agiledox.sourceforge.net/"target="_blank" rel="noopener">TestDox</a>. O que ele fazia era simples: lia código fonte em Java com testes em JUnit e produzia documentação textual a partir dele. O seguinte código Java:</p>
<hr>
<p>java</p>
<h2>public class AccountDepositTest extends TestCase {
public void testAddsTheDepositedAmountToTheBalance() { … }<br>
}<span class="hx:absolute hx:-mt-20" id="heading"></span>
    <a href="#heading" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>produziria o seguinte texto:</p>
<hr>
<h2>Account Deposit
– adds the deposited amount to the balance<span class="hx:absolute hx:-mt-20" id="-adds-the-deposited-amount-to-the-balance"></span>
    <a href="#-adds-the-deposited-amount-to-the-balance" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Era uma ferramenta bem simplista, mas teve um efeito profundo nas equipes apresentadas a ele. Eles começaram a publicar relatórios de TestDox para todos verem, encorajando os programadores a escrever sentenças reais em seus testes, ou os relatórios de TestDox pareceriam grego.</p>
<p>Tendo sentenças reais em seus testes, os programadores começaram a pensar sobre comportamento e o que o código deveria fazer, e a bola de neve do BDD começou a rolar.</p>

</blockquote>
<p>E especificamente sobre o Cucumber:</p>
<blockquote>
  <p>Mesmo antes de começar a explorar estrutura e sintaxe para RSpec, Dan North estava explorando um modelo completamente diferente para uma ferramenta BDD.</p>
<p>Ele queria documentar e dirigir o comportamento em uma linguagem simplificada que poderia facilmente ser entendida pelos clientes, desenvolvedores, testadores, analistas de negócio e assim por diante. O resultado inicial dessa exploração foi a biblioteca JBehave, que ainda está em uso ativo e em desenvolvimento.</p>
<p>Dan portou o JBehave para Ruby como RBehave, e o mesclou dentro do RSpec como o Story Runner. Ele suportava somente cenários escritos em Ruby no começo, mas depois adicionamos suporte a textos em inglês puro, abrindo todo um novo mundo de expressividade e acesso. Mas à medida que novas possibilidades eram reveladas, também apareciam limitações.</p>
<p>Na primavera de 2008, Aslak Hellesøy se colocou para reescrever o Story Runner do Rspec com uma gramática real definida com a biblioteca Treetop do Nathan Sobo. Aslak o chamou de Cucumber, em sugestão da sua noiva, Patricia Carrier, imaginando que seria um título temporário até ser mesclado de volta no RSpec. Mas eles nem imaginavam que Cucumber desenvolveria uma vida própria.</p>

</blockquote>
<p><a href="http://pragprog.com/titles/achbd/the-rspec-book"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/17/Screen%20shot%202011-04-17%20at%203.23.56%20PM_original.png?1303064641" alt=""  loading="lazy" /></a></p>
<p>Cucumber, Rspec, implementando os conceitos de BDD, se tornam não somente a especificação das User Stories, mas praticamente a própria implementação da definição de “Done”. Se a feature do Cucumber passa, quase poderíamos considerá-la “Done” mesmo.</p>
<h2>Retornando à Controvérsia<span class="hx:absolute hx:-mt-20" id="retornando-à-controvérsia"></span>
    <a href="#retornando-%c3%a0-controv%c3%a9rsia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa história parece longa, mas no meu artigo eu apenas coloquei um micro-resumo. Se você não conhecia esses episódios, significa que ainda tem muito a aprender sobre esta prática muito específica de Engenharia de Software chamada “Software Testing”. Ela vai muito mais longe do que meros “asserts” ou “shoulds”. Ela trata de um mundo muito mais amplo chamado “Qualidade”, mas esse assunto fica para outro artigo.</p>
<p>Em resumo, a controvérsia Test::Unit vs RSpec/Cucumber é – pra variar – uma questão de discussão sobre <a href="http://en.wikipedia.org/wiki/Aesthetics"target="_blank" rel="noopener">estética</a>. E como os sábios já diziam:</p>
<blockquote>
  <p>A Beleza está nos olhos de quem vê.</p>

</blockquote>
<p>Portanto, para muitos, a estrutura sintática da DSL do RSpec e Cucumber são praticamente a sétima maravilha. E eu não discuto que a sintaxe é muito elegante, até surpreendente para quem nunca viu e começa a enxergar os potenciais de explorar o tema de <a href="http://my.safaribooksonline.com/book/software-engineering-and-development/ide/9780132107549"target="_blank" rel="noopener">Domain Specific Languges</a>.</p>
<p>Elas são a solidificação das idéias e conceitos de Dave Astels, Dan North e muitos outros que pesquisaram, exploraram e continuam tentando encontrar novas formas de aumentar a qualidade do software e a satisfação final do cliente – que é o que realmente interessa em qualquer projeto de software.</p>
<p>Porém, as ferramentas são auxílios para a implementação de um conceito. Se você não entende o conceito, não importa que ferramenta use: seu projeto vai fracassar miseravelmente, como sempre fracassou.</p>
<p>O valor é mais óbvio para quem já passou muito tempo antes fazendo testes unitário em ferramentas como o JUnit ou mesmo o Test::Unit do Ruby, porque a mudança de nomenclatura, a saída da execução do teste na forma de relatório legível por seres humanos, vai forçá-lo a repensar a maneira como escreve seus testes – mudando de uma forma mais de sistema para uma forma centrada em comportamento.</p>
<p>Quem nunca fez testes antes, dificilmente vai entender porque estamos discutindo – porque ele não viu como era feito antes e não entende a diferença.</p>
<p>Mas se você já entendeu o que significa <em>Behavior-Driven</em>, então você também é capaz de expressar comportamento da mesma forma usando Test::Unit ou qualquer outra ferramenta mais tradicional de testes. Portanto, usar Test::Unit não dificulta em nada o processo e você será capaz de escrever tranquilamente, implementando conceitos da mesma forma que quem usa RSpec.</p>
<p>Existe uma diferença técnica que faz toda a diferença: Ruby já vem por padrão com Test::Unit e Ruby on Rails suporta por padrão Test::Unit. Pode não parecer muita coisa, mas pense assim: você cria uma nova biblioteca, que usa Ruby puro e sequer tem dependências com outras gems. Agora você decide testar com RSpec. Só por causa disso, todo mundo que quiser colaborar vai ter que baixar e instalar todas as dependências do RSpec – que nada tem a ver com o objetivo da sua biblioteca.</p>
<p>Parece pouco, mas é aquela “pequena coisinha” que inconscientemente pode deixar alguns potenciais colaboradores de nariz virado sem saber porque. E isso porque todo bom programador sabe que deve procurar sempre nunca reinventar a roda, mas por outro lado também desenvolver e depender da menor quantidade de dependências externas quanto possível.</p>
<p>Minha conclusão, é: se você ainda é novato no assunto e está em dúvida, comece com Test::Unit. Você já tem ele na sua máquina, os generators de Rails todos geram templates de Test::Unit por padrão, e basta ler o código-fonte do próprio Rails para encontrar centenas de ótimos exemplos de como usar Test::Unit. Sentiu que está confortável com Test::Unit? Agora compre o livro do Chelismky, baixe RSpec, Cucumber, e comece a entender o ciclo diferente que é BDD.</p>
<p>Depois de mais algum tempo, se sentiu confortável com RSpec e a forma BDD de fazer as coisas? Pois bem, eis o desafio: retorne ao Test::Unit e tente desenvolver exatamente da mesma forma como em RSpec. Você vai ver que é praticamente a mesma coisa.</p>
<p>Em paralelo a isso, independente de Test::Unit ou RSpec, não se esqueça que hoje temos um enorme ecossistema próprio somente para testes:</p>
<ul>
<li><a href="http://test-unit.rubyforge.org/"target="_blank" rel="noopener">Test::Unit</a> – o pacote canônico de testes do Ruby</li>
<li><a href="http://rspec.info/"target="_blank" rel="noopener">Rspec</a> – a nova geração de testes unitários com conceitos de BDD</li>
<li><a href="http://cukes.info/"target="_blank" rel="noopener">Cucumber</a> – a famosa ferramenta de BDD</li>
<li><a href="https://github.com/ianwhite/pickle"target="_blank" rel="noopener">Pickle</a> – Cucumber precisa de “steps” para executar as “features” (user stories), este projeto já lhe fornece dezenas de steps reusáveis para facilitar o desenvolvimento</li>
<li><a href="https://github.com/jnicklas/capybara"target="_blank" rel="noopener">Capybara</a> – abstração e simulação do usuário num navegador para testes de aceitação. Se integra com Cucumber, Rspec, Test::Unit, Ruby on Rails, Rack. Tem drivers para Selenium, HtmlUnit (<a href="https://github.com/bernerdschaefer/akephalos"target="_blank" rel="noopener">Akephalos</a> – o melhor -, <a href="https://github.com/sobrinho/capybara-celerity"target="_blank" rel="noopener">Celerity</a>, <a href="https://github.com/sobrinho/capybara-culerity"target="_blank" rel="noopener">Culerity</a>), <a href="http://github.com/smparkes/env-js"target="_blank" rel="noopener">env.js</a>, e <a href="https://github.com/thoughtbot/capybara-webkit"target="_blank" rel="noopener">WebKit</a>.</li>
<li><a href="https://github.com/thoughtbot/factory_girl"target="_blank" rel="noopener">Factory Girl</a> – para criar dados de teste no banco de dados de forma mais fácil do que usando fixtures</li>
<li><a href="https://github.com/jnicklas/evergreen"target="_blank" rel="noopener">Evergreen</a> – é uma ferramenta para testar seu Javascript – coisa que poucos fazem – mas com este pacote ele fornece os meios de testar Javascript com a mesma facilidade que você testa Ruby, mas num ambiente isolado, headless (sem navegador nativo), simulando um HTML DOM (como env.js, por exemplo, ou outros integrados com Capybara)</li>
<li><a href="https://github.com/jscruggs/metric_fu"target="_blank" rel="noopener">Metric-Fu</a> – além de apenas testar cegamente, ferramentas de métricas podem ajudá-lo a encontrar pontos menos testados do que deveriam, complexidade desnecessária no código e assim por diante.</li>
<li><a href="http://wiki.hudson-ci.org/display/HUDSON/Configuring&#43;a&#43;Rails&#43;build"target="_blank" rel="noopener">Hudson</a> – e de nada adianta ter testes se você não os executa constantemente. Fazer isso na sua máquina de desenvolvimento, manualmente o tempo todo, é tedioso. Para isso existem servidores de <a href="http://www.extremeprogramming.org/rules/integrateoften.html"target="_blank" rel="noopener">Integração Contínua</a> como o Hudson/Jenkins, que se integra bem com projetos de Ruby on Rails.</li>
</ul>
<p>Como podem ver, o assunto “Testes” é bastante extenso, complexo e com uma longa história que deve ser entendida para que saibamos o que já foi feito e continuarmos a evolução daqui. TDD e BDD não são dogmas, não são procedimentos obrigatórios. Você vai encontrar exemplos que deram muito certo e muito errado, ou seja, temos somente <a href="http://en.wikipedia.org/wiki/Empirical_research"target="_blank" rel="noopener">dados empíricos</a> que nos levam a crer que esta é uma das melhores formas de se desenvolver software. Porém isto não é uma Teoria geral. Ela deve ser entendida, interpretada, modificada, medida e novas conclusões podem aparecer.</p>
<p>Rspec vs Test::Unit? Isso não é nem uma fração da história toda. Antes de achar que pode discutir a respeito, primeiro desça ao básico: você sabe a <strong>definição</strong> do que é um <a href="http://en.wikipedia.org/wiki/Unit_testing"target="_blank" rel="noopener">teste unitário</a>? (Dica: leia o documento <a href="http://s3.amazonaws.com/akitaonrails/files/std1008-1987.pdf"target="_blank" rel="noopener">ANSI/IEEE Std 1008-1987 – IEEE Standard for Software Unit Testing</a>).</p>
]]></content:encoded><category>testes</category><category>ruby</category></item><item><title>[Off-Topic] As 5 disfunções de equipes em código</title><link>https://akitaonrails.github.io/2009/05/31/off-topic-as-5-disfun--es-de-equipes-em-c-digo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2009/05/31/off-topic-as-5-disfun--es-de-equipes-em-c-digo/</guid><pubDate>Sun, 31 May 2009 16:10:00 -0300</pubDate><description>&lt;p&gt;Eu costumo repetir a todas as equipes que eu gerencio que 99% dos problemas de qualquer projeto vêm de “comunicação”. Estresses que duram dias e poderiam ter sido resolvidos numa conversa de corredor de 30 segundos. Uso a palavra “comunicação”, mas é um pouco mais do que isso: falta de respeito, falta de confiança, falta de &lt;a href="http://pt.wikipedia.org/wiki/Empatia"target="_blank" rel="noopener"&gt;empatia&lt;/a&gt; (empatia mesmo, não “simpatia”). Vamos à &lt;strong&gt;tradução&lt;/strong&gt; do artigo de &lt;a href="http://www.markhneedham.com/blog/2009/05/28/the-5-dysfunctions-of-teams-in-code/"target="_blank" rel="noopener"&gt;Mark Needham&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Eu recentemente esbarrei num &lt;a href="http://www.thekua.com/atwork/2009/05/evidence-in-favour-of-conways-law/"target="_blank" rel="noopener"&gt;post interessante do meu colega Pat Kua&lt;/a&gt; onde ele fala sobre alguns padrões que ele notou em código poderiam ser ligados à &lt;a href="http://en.wikipedia.org/wiki/Conway%27s_Law"target="_blank" rel="noopener"&gt;lei de Conway&lt;/a&gt;, que sugere que as estruturas de sistemas desenvolvidos em organizações vão refletir a estrutura de comunicação dessa organização. (AkitaOnRails: leiam também o &lt;a href="http://en.wikipedia.org/wiki/The_Mythical_Man-Month"target="_blank" rel="noopener"&gt;The Mythical Man-Month&lt;/a&gt;, onde o assunto de Conway também é explorado e entendam &lt;a href="http://www.infoq.com/articles/scaling-lean-agile-feature-teams"target="_blank" rel="noopener"&gt;Cross Functional Teams&lt;/a&gt; para entender uma das soluções.)&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu costumo repetir a todas as equipes que eu gerencio que 99% dos problemas de qualquer projeto vêm de “comunicação”. Estresses que duram dias e poderiam ter sido resolvidos numa conversa de corredor de 30 segundos. Uso a palavra “comunicação”, mas é um pouco mais do que isso: falta de respeito, falta de confiança, falta de <a href="http://pt.wikipedia.org/wiki/Empatia"target="_blank" rel="noopener">empatia</a> (empatia mesmo, não “simpatia”). Vamos à <strong>tradução</strong> do artigo de <a href="http://www.markhneedham.com/blog/2009/05/28/the-5-dysfunctions-of-teams-in-code/"target="_blank" rel="noopener">Mark Needham</a>.</p>
<p>Eu recentemente esbarrei num <a href="http://www.thekua.com/atwork/2009/05/evidence-in-favour-of-conways-law/"target="_blank" rel="noopener">post interessante do meu colega Pat Kua</a> onde ele fala sobre alguns padrões que ele notou em código poderiam ser ligados à <a href="http://en.wikipedia.org/wiki/Conway%27s_Law"target="_blank" rel="noopener">lei de Conway</a>, que sugere que as estruturas de sistemas desenvolvidos em organizações vão refletir a estrutura de comunicação dessa organização. (AkitaOnRails: leiam também o <a href="http://en.wikipedia.org/wiki/The_Mythical_Man-Month"target="_blank" rel="noopener">The Mythical Man-Month</a>, onde o assunto de Conway também é explorado e entendam <a href="http://www.infoq.com/articles/scaling-lean-agile-feature-teams"target="_blank" rel="noopener">Cross Functional Teams</a> para entender uma das soluções.)</p>
<p>Também recentemente li um livro chamado <a href="http://www.markhneedham.com/blog/2009/04/22/the-five-dysfunctions-of-a-team-book-review/"target="_blank" rel="noopener">As Cinco Disfunções de Equipes</a> que descreve alguns comportamentos em equipes que não funcionam de maneira efetiva.</p>
<p>Jogando como o advogado do diabo eu fiquei intrigado se existia algum tipo de ligação entre essas disfunções e se elas se manifestam em nosso código como anti-padrões.</p>
<p>As 5 disfunções são:</p>
<ol>
<li>Inexistência de Confiança – membros da equipe não querem ser vulneráveis dentro do grupo</li>
<li>Medo de Conflito – equipe não consegue engajar debates honestos de idéias</li>
<li>Falta de Comprometimento – membros da equipe raramente se comprometem em decisões</li>
<li>Evitar Responsabilidade – membros da equipe não chamam a atenção de seus pares a respeito de ações/comportamentos que prejudicam a equipe</li>
<li>Falta de Atenção a Resultados – membros da equipe que colocam suas necessidades individuais antes daquelas da equipe</li>
</ol>
<h2>Inexistência de Confiança<span class="hx:absolute hx:-mt-20" id="inexistência-de-confiança"></span>
    <a href="#inexist%c3%aancia-de-confian%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Acho que ter <strong>checagens por null por todo o código</strong> é o indicador mais óbvio que as pessoas não confiam no código com que estão trabalhando.</p>
<p>Se a pessoa escrevendo o código tivesse fé em seus colegas que escreveram o código que ele precisa agora, acho que seria mais provável que ele confiaria que o código fará a coisa certa e não se sentiria na necessidade de <a href="http://www.thekua.com/atwork/2008/08/defensive-programming-depends-on-context/"target="_blank" rel="noopener">ser tão defensivo</a>.</p>
<h2>Medo do Conflito<span class="hx:absolute hx:-mt-20" id="medo-do-conflito"></span>
    <a href="#medo-do-conflito" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Medo do conflito em uma equipe parece se manifestar da maneira mais óbvia em código quando temos <strong>muitas duplicações acontecendo</strong> (síndrome do “copy &amp; paste”) – existem diversas razões para duplicações mas acho que uma delas é quando as pessoas não estão engajadas em discussões quando eles não concordam com alguma coisa que um colega escreveu e por isso acabam escrevendo suas próprias versões de alguma coisa que já foi feita.</p>
<p>Isso provavelmente se manifesta de maneira ainda mais óbvia quando você acaba com múltiplos diferentes frameworks na mesma base de código e todos fazendo as mesmas coisas só porque as pessoas não querem engajar em conversações para escolher qual a equipe toda vai usar.</p>
<h2>Falta de Comprometimento<span class="hx:absolute hx:-mt-20" id="falta-de-comprometimento"></span>
    <a href="#falta-de-comprometimento" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse é um que parece cruzar muito com os dois anteriores, embora uma maneira específica que este se manifesta em código quando vemos <strong>erros básicos ou falta de cuidado demonstrado em código</strong> (síndrome de “fazer nas coxas”) – um exemplo disso pode ser mudar o nome da classe e então não garantir que todos os lugares onde o nome antigo era usado tenham sido atualizados.</p>
<p>Isso deixa o código numa situação meia-boca e torna muito difícil para as outras pessoas trabalharem e eles precisam ficar limpando as coisa antes de conseguir efetivamente fazer algum trabalho.</p>
<h2>Evitar Responsabilidade<span class="hx:absolute hx:-mt-20" id="evitar-responsabilidade"></span>
    <a href="#evitar-responsabilidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O anti-padrão de código que mais salta aos meus olhos é <strong>quando nós permitimos que as pessoas escrevam código sem testes</strong> e colocamos no repositório de código.</p>
<p>Pela minha experiência até agora isso nunca funcionou bem e eu acho que isso demonstra falta de respeito pelo resto da equipe já que não temos uma maneira simples de verificar se o código efetivamente funciona e as outras pessoas não podem usar isso em outro lugar com nenhum grau de segurança.</p>
<h2>Falta de Atenção a Resultados<span class="hx:absolute hx:-mt-20" id="falta-de-atenção-a-resultados"></span>
    <a href="#falta-de-aten%c3%a7%c3%a3o-a-resultados" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Membros da equipe colocando suas necessidades individuais antes da equipe se manifesta no código quando acabamos com <strong>código que foi escrito de tal maneira que apenas quem escreveu o código é capaz de entendê-lo.</strong></p>
<p>Acho que isso se manifesta em “código esperto” que não tem problema se o projeto for só seu, mas no contexto de uma equipe é muito detrimental à medida que você se torna o gargalo quando outras pessoas querem fazer mudanças nessa área do código e não podem porque não conseguem entender o que está acontecendo.</p>
<p>Outra coisa que cai nesta mesma situação é <strong>quando existem convenções a serem seguidas mas decidimos sair por fora e fazer do nosso jeito</strong>. Tudo bem, algumas vezes não tem problema se estamos trabalhando para tornar o código realmente melhor e o resto da equipe sabe e concorda com isso. Caso contrário, não é algo inteligente de se fazer.</p>
<h2>Em Resumo<span class="hx:absolute hx:-mt-20" id="em-resumo"></span>
    <a href="#em-resumo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Acho intrigante que em minha mente, pelo menos, alguns dos problemas que vemos em código parecem ter alguma correlação aos problemas que vemos nas equipes.</p>
<p>Uma coisa que eu me lembro ao ler <a href="http://www.amazon.com/Secrets-Consulting-Giving-Getting-Successfully/dp/0932633013/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1243452602&amp;sr=1-1"target="_blank" rel="noopener">Os Segredos de Consultoria</a>, de Gerald Weinberg é sua afirmação de que <a href="https://web.archive.org/web/20090606022501/http://www.codinghorror.com/blog/archives/001033.html"target="_blank" rel="noopener">não importa qual seja o problema, sempre é um problema de pessoas</a> – se de fato isso for verdade então, em teoria, problemas que vemos em código devem ser indicativos de problemas com pessoas, que eu acho que até certo ponto realmente é verdade.</p>
<p>Eu acho certamente que nem todo problema de código está ligado às disfunções de equipes – certamente alguns anti-padrões entram no seu código devido à inexperiência de membros da equipe, mas de qualquer forma isso também demonstraria a falta de sêniors na equipe trabalhando de forma mais próxima com seus colegas!</p>
<p>Talvez possamos identificar maneiras de como melhorar nossas equipes começando dando uma olhada no seu código.</p>
<h2>Ranting<span class="hx:absolute hx:-mt-20" id="ranting"></span>
    <a href="#ranting" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>por AkitaOnRails:</strong> de fato, estou muito convencido de que problemas que acontecem no código são apenas sintomas de problemas estruturais das equipes e das organizações.</p>
<p>Começa pela falta de respeito. Quando os membros da equipe veem o chefe (não chamem de “líder” um chefe hierárquico; eles quase nunca são líderes de verdade) usando de “carteirada” para conseguir o que quer das outras equipes, entre os próprios desenvolvedores vira uma queda de braço: <em>“eu fiz minha parte, se o outro reclamar mando meu chefe falar com o dele e pronto.”</em> Na minha experiência, quase todo problema de equipe ineficiente e problemática mora no gerente.</p>
<p>Gerentes que exercitam <em>“comando-e-controle”</em> são exatamente os tipos que deviam ser execrados de uma organização. São os que não confiam na equipe, que gritam, que usam a força do cargo, que insistem em ser o gargalo da comunicação e que exigem que tudo passe por eles. Não têm conhecimento real, e isso fica notório na falta de capacidade de argumentação.</p>
<p>Tem também o gerente que não sabe dar feedback honesto e diário, pelo bem ou pelo mal, e guarda tudo para jogar na cara dos outros meses depois. Para mim, é a maior demonstração de <strong>covardia</strong> em alguém que deveria ser um <em>“líder”</em>.</p>
<p>Tem o gerente que adora <em>“micro-gerenciar”</em> quando lhe convém, mas nunca explica quais são suas expectativas. O feedback dele acompanha o humor do dia em vez do trabalho entregue, o que deixa tudo bem cômodo para o lado dele. E tem o gerente que, para não parecer tão ruim, gasta o tempo tentando fazer as outras equipes parecerem piores. São os caçadores de pelo em ovo, que catam motivo trivial (horário, roupa, conversinha de corredor) como desculpa para falar mal, em vez de olhar resultado real como lucro e custo.</p>
<p>Se você está mesmo preocupado com o motivo de suas equipes não renderem como deveriam, olhe para a camada de gerentes. Principalmente os que estão <strong>há muitos anos</strong> na mesma organização, viciados, que já conhecem todos os <em>“jeitinhos”</em> da casa. São perigosos: sorriem para todo mundo, parecem confiantes, parecem eficientes, e a maioria dos subordinados os elogia (sob coerção, obviamente).</p>
<p>A equipe reflete o sistema e a estrutura que colocam sobre ela. Tire a autonomia, trate todo mundo como criança, deixe as pessoas confusas e com medo, e o resultado é exatamente esse: trabalho mal feito, de má qualidade, cheio de defeito, que custa caro.</p>
<p>Alguns acham exagero, principalmente uma diretoria distante e pouco participativa (qualquer coisa diferente de “todo dia” não é participação) em relação ao que acontece no chão de fábrica. Aí se surpreendem ao ver que os funcionários ficaram estagnados, incompetentes e obsoletos, e também distantes e despreocupados com o futuro da organização. A única coisa que os preocupa nesse ponto é o próprio emprego.</p>
<p>Enquanto isso, os tais “gerentes” seguem em situação confortável. Quando as coisas dão certo (por sorte, e só por sorte), ficam com os louros. Quando dão errado, a culpa é dos membros da equipe que ele já ia mandar embora mesmo, ou das outras equipes, ou da organização inteira que não o ajudou, ou das decisões históricas que agora não têm mais jeito. A culpa nunca é dele.</p>
<p>Lembrem-se: anos de casa não podem virar imunidade. Anos de casa deveriam ser irrelevantes quando a organização quer ser eficiente. E não tenha medo de mandar um gerente desses embora achando que <em>“só ele sabe como as coisas funcionam”</em>. As coisas não vão desandar. Confie nas equipes, devolva a autonomia, elas sabem o que fazer.</p>
<p>Portanto, sim: a grande maioria das disfunções de uma equipe é resultado direto das disfunções da organização. Não adianta aplicar técnicas localizadas, pregar pair programming, test driven development, refactoring e afins, se a organização continua a mesma. Quer mudar? Mude tudo. Ou nem se dê ao trabalho.</p>
]]></content:encoded><category>engenharia-de-software</category><category>testes</category><category>gestao</category><category>off-topic</category></item><item><title>Tradução: Scrum Flácido</title><link>https://akitaonrails.github.io/2009/02/03/tradu-o-scrum-fl-cido/</link><guid isPermaLink="true">https://akitaonrails.github.io/2009/02/03/tradu-o-scrum-fl-cido/</guid><pubDate>Tue, 03 Feb 2009 03:32:00 -0200</pubDate><description>&lt;p&gt;5 dias atrás, Martin Fowler &lt;a href="http://martinfowler.com/bliki/FlaccidScrum.html"target="_blank" rel="noopener"&gt;publicou um artigo&lt;/a&gt; que pode soar polêmico para Scrummers. O alvo dele, porém, é quem aplica Scrum da maneira errada e quem não se preocupa em tornar esse problema aparente. A seguir, a tradução do artigo na íntegra; no final, comentários meus.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://martinfowler.com/bliki/FlaccidScrum.html"target="_blank" rel="noopener"&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2009/2/3/Picture_1.png" alt="" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Existe uma bagunça que eu ouço em muitos projetos recentemente. Funciona assim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eles querem usar um processo ágil, e escolhem Scrum&lt;/li&gt;
&lt;li&gt;Eles adotam as práticas Scrum, e talvez até os princípios&lt;/li&gt;
&lt;li&gt;Depois de um tempo o progresso é lento porque a &lt;strong&gt;base de código é uma bagunça&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O que acontece é que eles não prestaram atenção à &lt;strong&gt;qualidade interna&lt;/strong&gt; de seu software. Se você cometer esse erro irá rapidamente descobrir que seu progresso foi desacelerado porque é muito difícil adicionar novas funcionalidades que você gostaria. Você caiu no problema de &lt;a href="http://martinfowler.com/bliki/TechnicalDebt.html"target="_blank" rel="noopener"&gt;Dívida Técnica&lt;/a&gt; e seu scrum caiu de joelhos. (E se você esteve num scrum real, saberá que isso é uma Coisa Ruim).&lt;/p&gt;</description><content:encoded><![CDATA[<p>5 dias atrás, Martin Fowler <a href="http://martinfowler.com/bliki/FlaccidScrum.html"target="_blank" rel="noopener">publicou um artigo</a> que pode soar polêmico para Scrummers. O alvo dele, porém, é quem aplica Scrum da maneira errada e quem não se preocupa em tornar esse problema aparente. A seguir, a tradução do artigo na íntegra; no final, comentários meus.</p>
<p><a href="http://martinfowler.com/bliki/FlaccidScrum.html"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2009/2/3/Picture_1.png" alt=""  loading="lazy" /></a></p>
<p>Existe uma bagunça que eu ouço em muitos projetos recentemente. Funciona assim:</p>
<ul>
<li>Eles querem usar um processo ágil, e escolhem Scrum</li>
<li>Eles adotam as práticas Scrum, e talvez até os princípios</li>
<li>Depois de um tempo o progresso é lento porque a <strong>base de código é uma bagunça</strong></li>
</ul>
<p>O que acontece é que eles não prestaram atenção à <strong>qualidade interna</strong> de seu software. Se você cometer esse erro irá rapidamente descobrir que seu progresso foi desacelerado porque é muito difícil adicionar novas funcionalidades que você gostaria. Você caiu no problema de <a href="http://martinfowler.com/bliki/TechnicalDebt.html"target="_blank" rel="noopener">Dívida Técnica</a> e seu scrum caiu de joelhos. (E se você esteve num scrum real, saberá que isso é uma Coisa Ruim).</p>
<p>Mencionei Scrum porque quando vemos esse problema, Scrum parece ser particularmente comum como o processo nominativo que a equipe segue. Para muitas pessoas, a situação é exacerbada pelo Scrum porque Scrum é um processo centrado em técnicas de gerenciamento de projetos e deliberadamente omite qualquer prática técnica, em contraste (por exemplo) com Extreme Programming.</p>
<p>Defendendo o Scrum, é importante apontar que só porque ele não inclui nenhuma atividade técnica dentro de seu escopo isso não deve levar ninguém a concluir que ele não acha isso importante. Sempre que ouvi Scrummers prominentes eles sempre enfatizaram que você deve ter <strong>boas práticas técnicas</strong> para ter sucesso com um projeto Scrum. Eles não dizem quais práticas técnicas devem ser, mas você precisa delas. Afinal projetos enfrentam problemas por causa de qualidade interna pobre o tempo todo, e o fato que muitos entram abaixo da bandeira do Scrum parece ser mais pelo fato de Scrum ser popular no momento do que qualquer coisa particular no Scrum. Popularidade e <a href="http://martinfowler.com/bliki/SemanticDiffusion.html"target="_blank" rel="noopener">Difusão Semântica</a> tendem a andar juntos.</p>
<p>Então, o que fazer a respeito?</p>
<p>A comunidade Scrum precisa redobrar seus esforços em garantir que as pessoas entendam a importância de práticas técnicas fortes. Certamente qualquer tipo de revisão de projeto deve incluir o exame de quais práticas técnicas estão presentes. Se você estiver envolvido ou conectado a esse tipo de projeto, faça um barulho se o lado técnico estiver sendo negligenciado.</p>
<p>Se estiver introduzindo Scrum, garanta que está prestando atenção às práticas técnicas. Tendemos a aplicar muitas do Extreme Programming e elas se encaixam muito bem. Os XPers costumam brincar que, com alguma justificativa, Scrum é apenas XP sem as práticas técnicas que o fazem funcionar. Provocações à parte, as práticas de XP são um bom ponto de partida – e certamente serão muito melhores do que nada.</p>
<p>Eu sempre gosto de apontar que não são metodologias que levam ao sucesso ou fracasso. Usar um processo pode ajudar uma equipe a subir no jogo, mas no fim é a equipe que importa e que carrega a responsabilidade de fazer o que funciona para elas. Estou certo que muitos dos projetos Scrum Flácidos em andamento prejudicarão a reputação do Scrum, e provavelmente a reputação maior de Ágil. Mas já que vejo <a href="http://martinfowler.com/bliki/SemanticDiffusion.html"target="_blank" rel="noopener">Difusão Semântica</a> como algo inevitável não estou desproporcionadamente alarmado. Equipes que fracassam provavelmente vão fracassar seja qual for a metodologia que apliquem mal; equipes que têm sucesso construirão suas práticas sobre boas ideias e o papel da comunidade scrum é espalhar essas boas ideias.</p>
<p>Muitas pessoas estão olhando para Lean como a <em>Próxima Grande Coisa Ágil</em>. Mas quanto mais popular lean se tornar mais vai incorrer nos mesmos problemas que Scrum está enfrentando agora mesmo. Isso não torna Lean (ou Scrum) sem valor, apenas nos lembra que Indivíduos e Interações são mais valiosos que Processos e Ferramentas.</p>
<h3>Observações do Akita<span class="hx:absolute hx:-mt-20" id="observações-do-akita"></span>
    <a href="#observa%c3%a7%c3%b5es-do-akita" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na prática, o que percebo é que as partes de planejamento e gerenciamento encontram menos resistência na implementação: <a href="http://www.extremeprogramming.org/rules/userstories.html"target="_blank" rel="noopener">User Stories</a>, <a href="http://www.extremeprogramming.org/rules/planninggame.html"target="_blank" rel="noopener">Release Planning</a>, <a href="http://www.extremeprogramming.org/rules/releaseoften.html"target="_blank" rel="noopener">Small Releases</a>, <a href="http://www.extremeprogramming.org/rules/velocity.html"target="_blank" rel="noopener">Project Velocity</a>, <a href="http://www.extremeprogramming.org/rules/iterative.html"target="_blank" rel="noopener">Iterations</a>, <a href="http://www.extremeprogramming.org/rules/iterationplanning.html"target="_blank" rel="noopener">Iteration Planning</a>, <a href="http://www.extremeprogramming.org/rules/standupmeeting.html"target="_blank" rel="noopener">Stand Up Meeting</a>. A razão é simples. Quando uma empresa decide adotar Scrum, a decisão veio de algum nível de cima; sem isso, nem sai do papel. E gerentes e chefes costumam entender e engolir esses conceitos razoavelmente bem, principalmente se já passaram por metodologias tradicionais de gerenciamento de projeto sem êxito.</p>
<p>E é aqui que a maioria <strong>erra</strong>: se a equipe ainda não tem maturidade para ser autogerenciável, ou seja, para gerar código de qualidade de maneira independente, é obrigação da alta chefia liderar nessa direção. Isso é difícil porque muitos gerentes não são técnicos, o que os deixa alheios ao problema da falta de boas práticas técnicas.</p>
<p>A bagunça a que Martin Fowler se refere está no design, na codificação e nos testes. Quem mais tem dificuldade com essas práticas é o desenvolvedor que veio dos jeitos antigos de desenvolver, no estilo &ldquo;cowboy suicida&rdquo;, ou que tem pouca experiência e pouco estudo de desenvolvimento de software em geral.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2009/2/3/22124.jpg" alt=""  loading="lazy" /></p>
<p>A maioria dos desenvolvedores não entende <a href="http://www.extremeprogramming.org/rules/simple.html"target="_blank" rel="noopener">Simplicidade</a>, o famoso &ldquo;fazer a coisa mais simples que funciona&rdquo;, ou <a href="http://en.wikipedia.org/wiki/You_Ain%27t_Gonna_Need_It"target="_blank" rel="noopener">YAGNI</a> (You Ain&rsquo;t Gonna Need It). Nos mais jovens, o espírito &ldquo;cowboy&rdquo; é difícil de domar: querem sempre fazer as coisas de forma mais complexa do que precisam. Isso leva justamente ao problema de <a href="http://www.extremeprogramming.org/rules/early.html"target="_blank" rel="noopener">adicionar coisas cedo demais</a>, complexidade pelo prazer da complexidade. A equipe precisa se autopoliciar contra isso. Felizmente as <a href="http://www.extremeprogramming.org/rules/spike.html"target="_blank" rel="noopener">Spike Solutions</a> ajudam: a equipe para um instante, entende o problema e estuda alternativas de solução.</p>
<p>Outra confusão que já vi muito é misturar <a href="http://www.extremeprogramming.org/rules/refactor.html"target="_blank" rel="noopener">Refactoring</a> com Rewrite. Rewrite em si não é um problema, mas se torna um quando aplicado com a noção de &ldquo;só porque é novo, é melhor&rdquo;. Antes de mais nada, Refactoring é uma série de técnicas que tem como objetivo rejuvenescer o código, torná-lo mais gerenciável, sem modificar seu comportamento.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2009/2/3/cowboy.jpg" alt=""  loading="lazy" /></p>
<p>Isso leva ao calcanhar de aquiles da maioria dos desenvolvedores amadores: a aversão a testes. <em>&ldquo;Sou bom demais para errar, não preciso de testes.&rdquo;</em> É o desenvolvedor que efetivamente levará seu trabalho ao fracasso certo. <a href="http://www.extremeprogramming.org/rules/testfirst.html"target="_blank" rel="noopener">Testar antes</a> é uma maneira efetiva de refinamento do design e também leva à simplicidade, fazendo apenas o que realmente se precisa.</p>
<p>Sem bons testes, é impossível fazer um refactoring efetivo: como você garante que a mudança não alterou o comportamento do código? O corolário disso é que esses desenvolvedores também <strong>não</strong> praticam Refactoring, o que leva à massa de código bagunçado. A ironia é que até em rewrites, onde se assumiu que o novo seria melhor, o código acaba virando um &ldquo;legado&rdquo; bem rápido.</p>
<p>Pior ainda, quando um <a href="http://www.extremeprogramming.org/rules/bugs.html"target="_blank" rel="noopener">bug é encontrado</a>, raramente se criam testes para evitar a regressão ao mesmo bug. E todos já devem ter visto bugs que supostamente já estavam corrigidos retornando pouco tempo depois. Outro problema é que muitas equipes não encontraram uma boa definição de &ldquo;história finalizada&rdquo; e portanto também têm dificuldade em manter <a href="http://www.extremeprogramming.org/rules/functionaltests.html"target="_blank" rel="noopener">testes de aceitação</a> dessas histórias. Uma das razões: em vez de escreverem User Stories (&ldquo;como X, quero Y por causa de Z&rdquo;, definindo o que implementar, para quem e qual valor isso traz), escrevem tarefas (&ldquo;fazer X&rdquo;), pulando o para quem e o porquê.</p>
<p>Ainda no espírito de &ldquo;cowboy&rdquo;, programadores sem experiência não entendem o conceito de <a href="http://www.extremeprogramming.org/rules/collective.html"target="_blank" rel="noopener">propriedade coletiva do código</a>. O que acontece é que cada desenvolvedor tenta se limitar apenas ao código que ele acha que é responsabilidade dele e não se preocupa com o todo. Deveria acontecer o contrário: todo desenvolvedor tem que se sentir responsável por todo o código. Isso também explica mais um motivo da importância de testes unitários: sem eles é impossível ajudar em código que você não fez, e impossível saber se seu próprio código não quebra algo que outra pessoa fez. Em resumo: isso leva a um acúmulo imensurável de &ldquo;Dívida Técnica&rdquo; que só se vê quando já é tarde demais, tornando seu &ldquo;novo sistema&rdquo;, prematuramente, um &ldquo;legado&rdquo; ingerenciável.</p>
<p>Nesse mesmo espírito, os desenvolvedores &ldquo;cowboys&rdquo; não <a href="http://www.extremeprogramming.org/rules/integrateoften.html"target="_blank" rel="noopener">integram com frequência</a> como deveriam. Não é raro ver desenvolvedores que passam a semana toda com código novo apenas na própria máquina e só no final da semana fazem o commit no repositório e consideram terminado. Nada de criar testes, nada de rodar a suíte completa de testes. Mais um desenvolvedor criando Dívida Técnica deliberadamente.</p>
<p>Para piorar, não é difícil encontrar equipes que sequer entendem o valor de um repositório: para elas, basta alterar direto no servidor de produção ou editar arquivos diretamente lá. É a pré-história do desenvolvimento. Um repositório de versionamento de código é mandatório, e tratá-lo como um santuário é a maior responsabilidade de uma equipe. Isso significa que todo código no repositório deve ser sempre código sem nenhum problema acrescentado de forma deliberada, por não fazer testes, não integrar frequentemente, não refatorar onde precisa. Bugs sempre vão existir e devem ser corrigidos, mas erros deliberados tornam o desenvolvedor um irresponsável e um problema para toda a equipe.</p>
<p>Finalmente, é comum ver desenvolvedores que se acham muito espertos <a href="http://www.extremeprogramming.org/rules/optimize.html"target="_blank" rel="noopener">otimizando</a> muito cedo no projeto, baseados apenas em &ldquo;chute&rdquo;, sem medições. Aliás, a maioria dos programadores que conheci tem aversão a medições tanto quanto tem aversão a testes. E otimizar sem estar baseado em métrica é a mais pura perda de tempo. Você pode fazer um certo trecho de código ficar 100 vezes mais rápido, mas se no tempo geral isso não representa um ganho nem de 0,5%, foi uma perda de tempo. Novamente, falta de experiência.</p>
<p>Ainda sobre os &ldquo;cowboys suicidas&rdquo;, normalmente eles são apegados às suas limitadas ferramentas, às quais já estão acostumados. Têm preguiça de aprender coisas novas, seja por incapacidade ou por falta de vontade, e sempre tentam ir pelo caminho aparentemente mais fácil, como usar geradores automáticos de código, que geralmente geram código difícil de dar manutenção e fora dos padrões mais modernos aceitos como boas práticas.</p>
<p>No final, o resultado é o mesmo: desenvolvedores inexperientes que acham que sabem tudo, ou desenvolvedores com muito tempo de casa viciados em anti-práticas e teimosos demais para aprender as boas práticas. Montar uma equipe eficiente e verdadeiramente ágil de desenvolvimento é muito difícil. E a realidade é que simplesmente nem todo mundo serve para o trabalho. Um desenvolvedor ágil é alguém proativo, autodidata, sociável e comunicativo. E tudo isso é mais importante que sua suposta competência técnica.</p>
<p>PS: algumas pessoas podem achar que isso é algo direto a elas. Mas na realidade, isso é mais comum do que se imagina, já vi em muitos clientes e vou continuar vendo. Sendo justo, eu mesmo já fiz muito projeto (mais do que gostaria) que imediatamente se tornou um &ldquo;legado&rdquo;, um código mal testado e difícil de manter. Reclamar é fácil, fazer algo para melhorar é que é o difícil. E o desenvolvedor cowboy de ontem tem todas as condições de se tornar um bom programador amanhã, basta querer.</p>
]]></content:encoded><category>agile</category><category>engenharia-de-software</category><category>testes</category><category>off-topic</category></item><item><title>Por que Testar?</title><link>https://akitaonrails.github.io/2007/08/25/por-que-testar/</link><guid isPermaLink="true">https://akitaonrails.github.io/2007/08/25/por-que-testar/</guid><pubDate>Sat, 25 Aug 2007 20:04:00 -0300</pubDate><description>&lt;p&gt;Pessoalmente eu nunca me vi nem li ninguém fazer uma das perguntas mais óbvias: &lt;em&gt;“Por que testar?”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;O que eu normalmente presencio é que programadores tem consciência de que precisam testar. A reflexão que eu esperaria seria algo na seguinte linha: &lt;em&gt;“Temos super-computadores multi-processados com muitos gigahertz sobrando, porque o computador não é capaz de me dizer se meu programa finaliza ou não?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/Usability_Testing.jpg" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Infelizmente, não se trata de um dogma: ou poderíamos quebrá-lo. De fato, precisamos cobrir nossos programas com a maior quantidade possível de testes de qualidade, e nenhum sistema será capaz de confirmar, com 100% de certeza, em 100% dos casos, se um determinado programa irá funcionar corretamente. É uma limitação matemática que não pode ser contornada. Como sabemos disso?&lt;/p&gt;</description><content:encoded><![CDATA[<p>Pessoalmente eu nunca me vi nem li ninguém fazer uma das perguntas mais óbvias: <em>“Por que testar?”</em>.</p>
<p>O que eu normalmente presencio é que programadores tem consciência de que precisam testar. A reflexão que eu esperaria seria algo na seguinte linha: <em>“Temos super-computadores multi-processados com muitos gigahertz sobrando, porque o computador não é capaz de me dizer se meu programa finaliza ou não?”</em></p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/Usability_Testing.jpg" alt=""  loading="lazy" /></p>
<p>Infelizmente, não se trata de um dogma: ou poderíamos quebrá-lo. De fato, precisamos cobrir nossos programas com a maior quantidade possível de testes de qualidade, e nenhum sistema será capaz de confirmar, com 100% de certeza, em 100% dos casos, se um determinado programa irá funcionar corretamente. É uma limitação matemática que não pode ser contornada. Como sabemos disso?</p>
<h3>Momento Rails: ZenTest<span class="hx:absolute hx:-mt-20" id="momento-rails-zentest"></span>
    <a href="#momento-rails-zentest" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes de pular para a resposta (mais abaixo), primeiro vou dar uma dica aos Railers de plantão. Como vou falar de testes, gostaria de recomendar uma ferramenta extremamente útil escrita pro Ryan Davis chamado <a href="http://nubyonrails.com/articles/autotest-rails"target="_blank" rel="noopener">ZenTest</a>. Para instalar apenas digite:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo gem install ZenTest y  
</span></span><span class="line"><span class="cl">sudo gem install redgreen -y  
</span></span><span class="line"><span class="cl">gem install win32console -y <span class="c1"># somente para usuários Windows  </span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>&ndash;</p>
<p>Baixem <a href="http://topfunky.com/clients/blog/autotest-tm.mov"target="_blank" rel="noopener">este vídeo</a> gratuito de Geoffrey Grosenbach, que demonstra o autotest em funcionamento. Também comprem os screencast <a href="http://peepcode.com/products/test-first-development"target="_blank" rel="noopener">Test-First development for Rails</a>, <a href="http://peepcode.com/products/rspec-basics"target="_blank" rel="noopener">rSpec Basics</a> e <a href="http://peepcode.com/products/rspec-mocks-and-models"target="_blank" rel="noopener">rSpec Mocks and Models</a> (amostras grátis, <a href="http://topfunky.com/clients/peepcode/previews/peepcode-004-testing-tips.mov"target="_blank" rel="noopener">aqui</a>, <a href="http://peepcode.com/system/previews/peepcode-011-rspec-i-preview.mov"target="_blank" rel="noopener">aqui</a> e <a href="http://peepcode.com/system/previews/peepcode-013-rspec-ii-preview.mov"target="_blank" rel="noopener">aqui</a>) para aprender a fazer bons testes em Rails.</p>
<p>O <a href="http://www.zenspider.com/ZSS/Products/ZenTest/"target="_blank" rel="noopener">ZenTest</a> vem com o utilitário ‘autotest’. A partir da raíz do seu projeto Rails, apenas digite:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">autotest rails  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>&ndash;</p>
<p>Fazendo isso, os utilitários do pacote ZenTest ficarão monitorando seu projeto: toda vez que você modificar algum código ele irá rodar os testes. Quando estiver modificando os testes, ele rodará apenas o método de teste que você acabou de modificar. A vantagem: você estará constante monitorando se está tudo correndo bem com seu projeto, não precisará o rodar manualmente o ‘rake’ toda hora.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/autotest_red_green.jpg" alt=""  loading="lazy" /></p>
<p>Existem alguns plugins e técnicas para que o autotest chame uma janela de notificação, para ficar ainda mais fácil visualizar os testes. Este <a href="http://blog.codefront.net/2007/04/01/get-your-testing-results-via-growl-notifications/"target="_blank" rel="noopener">link</a> tem receitas para Mac. Eu sei que existe maneira para integrar com Gnome no Linux e no Windows, mas não me recordo da URL (alguém se lembra?)</p>
<p>Observando o seu console constantemente você terá rapidamente e em primeira mão onde sua modificação pode estar quebrando alguma coisa. Claro, partindo do princípio que você cobriu seu sistema suficientemente de testes. E quanto é suficiente? Como eu sei quanto do meu projeto já está coberto?</p>
<p>Para sabe isso, instale também o <a href="http://eigenclass.org/hiki.rb?rcov"target="_blank" rel="noopener">rCov</a> que é o pacote de cobertura de código para Ruby. Basta fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gem install rcov  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois instale o plugin <a href="http://blog.codahale.com/2006/05/26/rails-plugin-rails_rcov/"target="_blank" rel="noopener">rails_rcov</a> no seu projeto assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./script/plugin install x &lt;http://svn.codahale.com/rails_rcov&gt;  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>&ndash;</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/hiki.rb.png" alt=""  loading="lazy" /></p>
<p>Com este plugin você ganha os seguintes task rake:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rake" data-lang="rake"><span class="line"><span class="cl"><span class="o">|</span> <span class="ss">doc</span><span class="p">:</span><span class="ss">plugins</span><span class="p">:</span><span class="n">rails_rcov</span> <span class="o">|</span> <span class="no">Create</span> <span class="n">plugin</span> <span class="n">documentation</span> <span class="k">for</span> <span class="s1">&#39;rails_rcov&#39;</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:functionals:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">functional</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:functionals:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">functional</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:integration:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">integration</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:integration:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">integration</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:all:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">all</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:all:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">all</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">plugin</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:functionals:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">functional</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:functionals:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">functional</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:integration:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">integration</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:integration:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">integration</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">plugin</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:setup_plugin_fixtures:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">setup_plugin_fixture</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:setup_plugin_fixtures:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">setup_plugin_fixture</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:units:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">unit</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:plugins:units:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="ss">plugins</span><span class="p">:</span><span class="n">unit</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:recent:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">recent</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:recent:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">recent</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:test:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="nb">test</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:test:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="nb">test</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:uncommitted:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">uncommitted</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:uncommitted:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">uncommitted</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:units:clobber_rcov</span> <span class="o">|</span> <span class="no">Remove</span> <span class="no">Rcov</span> <span class="n">reports</span> <span class="k">for</span> <span class="n">unit</span> <span class="n">tests</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="nb">test</span><span class="ss">:units:rcov</span> <span class="o">|</span> <span class="no">Run</span> <span class="n">all</span> <span class="n">unit</span> <span class="n">tests</span> <span class="n">with</span> <span class="no">Rcov</span> <span class="n">to</span> <span class="n">measure</span> <span class="n">coverage</span> <span class="o">|</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Existem diversas outras ferramentas para ajudá-lo nos testes, como <a href="http://rspec.rubyforge.org/documentation/rails/install.html"target="_blank" rel="noopener">rSpec</a>. Mas apenas estas já devem aumentar e muito sua cobertura adequada de testes. Recomendo que todos os Railers leiam a respeito de <a href="http://en.wikipedia.org/wiki/Test-driven_development"target="_blank" rel="noopener">Test-Driven Development</a> (que Rails incentiva) e <a href="http://behaviour-driven.org/"target="_blank" rel="noopener">Behaviour-Driven Development</a> (que rSpec implementa).</p>
<p>Agora voltamos à pergunta: <em>“Para quê, todo esse trabalho? Por que testar?”</em></p>
<h3>Momento Wikipedia – Halting Dilemma<span class="hx:absolute hx:-mt-20" id="momento-wikipedia--halting-dilemma"></span>
    <a href="#momento-wikipedia--halting-dilemma" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="http://en.wikipedia.org/wiki/Turing_machine"target="_blank" rel="noopener">Turing Machines</a> são dispositivos de manipulação de símbolos abstratos básicos que, apesar de sua simplicidade, podem ser adaptadas para simular lógica de qualquer computador que possivelmente seja construído. Foram descritos em 1936 por <a href="http://en.wikipedia.org/wiki/Alan_Turing"target="_blank" rel="noopener">Alan Turing</a>. Todos os nossos micro-processadores são, em essência, Turing Machines.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/church.gif" alt=""  loading="lazy" /></p>
<p>Uma Turing Machine que é capaz de simular qualquer outra Turine machine é chamada de <a href="http://en.wikipedia.org/wiki/Universal_Turing_machine"target="_blank" rel="noopener">Universal Turing Machine</a> (UTM, ou simplesmente universal machine). Uma definição mais matemática com uma natureza similarmente “universal” foi apresentada por <a href="http://en.wikipedia.org/wiki/Alonzo_Church"target="_blank" rel="noopener">Alonzo Church</a>, cujo trabalho em <a href="http://en.wikipedia.org/wiki/Lambda_calculus"target="_blank" rel="noopener">lambda calculus</a> cruzou com o de Turing em uma teoria formal da computação conhecida como <a href="http://en.wikipedia.org/wiki/Church-Turing_thesis"target="_blank" rel="noopener">Tese Church-Turing</a>. Esta tese afirma que Turing machines de fato capturam a noção informal de método efetivo em lógica e matemática, dada uma definição precisa de um <a href="http://en.wikipedia.org/wiki/Algorithm"target="_blank" rel="noopener">algorítmo</a> ou “mecânica procedural”.</p>
<p>Um <a href="http://en.wikipedia.org/wiki/Algorithm"target="_blank" rel="noopener">algoritmo</a> é uma lista finita de instruções para a execução de alguma tarefa que, dada uma condição inicial (parâmetros de uma função, por exemplo), irá <a href="http://en.wikipedia.org/wiki/Termination"target="_blank" rel="noopener">terminar</a> em um estado final definido. Um programa de computador – do tipo que fazemos – é um algoritmo ou um conjunto de algoritmos.</p>
<p>Algoritmos são extremamente úteis. Matemáticos podem definir problemas matemáticos instruindo computadores a executá-los, por exemplo, para determinar a trilionésima casa decimal do Pi, ou o quadrilionésimo número primo. O problema: se o tamanho do processo não é conhecido em antemão, então “tentar” pode não ser conclusivo, porque se o processo continuar infinitamente – então em nenhum momento do tempo seremos capazes de ter certeza de uma “Resposta” (Minsky 1967:105).</p>
<p>Portanto a resposta é: inconclusivo. Nunca saberemos, nem poderemos analisar de antemão pra descobrir. A análise de algoritmos à procura da possibilidade de terminarem é chamada <a href="http://en.wikipedia.org/wiki/Termination_analysis"target="_blank" rel="noopener">Análise de Terminação</a>. Mas existe um problema primordial, conhecido como <a href="http://en.wikipedia.org/wiki/Halting_problem"target="_blank" rel="noopener">Halting Problem</a>.</p>
<p>O Halting Problem, ou Problema de Finalização começa com este enunciado: <em>“Dada uma descrição de um programa e parâmetros finitos, decidir se o programa termina de rodar ou se rodará infinitamente.”</em></p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/8/25/cope1.jpg" alt=""  loading="lazy" /></p>
<p>Alan Turing provou em 1936, em estudo paralelo e independente de Alonzo Church, chegando ambos à conclusão que um algoritmo genérico para resolver o Halting Problem para todos os parâmetros possíveis não pode existir. Nós dizemos que o Halting Problem é inconclusivo em Turing machines.</p>
<p>A prova para isso vocês podem ver nos links acima, existe uma demonstração que segue outros teoremas matemáticos chamados <a href="http://en.wikipedia.org/wiki/G%c3%b6del%27s_incompleteness_theorem"target="_blank" rel="noopener">Gödel’s incompleteness theorems</a>, onde as provas são similares.</p>
<p>Poderia parecer que <a href="http://en.wikipedia.org/wiki/Human"target="_blank" rel="noopener">humanos</a> poderiam resolver o Halting Problem. Afinal, um programador normalmente consegue olhar para um programa e dizer se ele vai ou não vai terminar. É útil entender <strong>porque isso não é verdade</strong>. Para simplicidade, vamos considerar um programa sem parâmetros de entrada, que também é inconclusivo.</p>
<p>“Resolver” o Halting Problem significa ser capaz de olhar para qualquer programa e dizer se ele termina. Não é suficiente ser capaz de olhar para alguns programas e decidir. Humanos podem não se capazes de resolver halting problems por causa do tamanho da entrada (um programa com milhões de linhas de código). Mesmo para programas curtos, não é claro que humanos possam sempre dizer se um programa termina. Por exemplo, poderíamos nos perguntar se o seguinte programa em Ruby (que é um Turing Machine e é <a href="http://en.wikipedia.org/wiki/Turing-complete"target="_blank" rel="noopener">Turing Complete</a>), vai terminar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">procurar_por_numero_impar_perfeito</span>  
</span></span><span class="line"><span class="cl"> <span class="n">n</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># inteiro de precisão arbitrária  </span>
</span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="kp">true</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="n">soma_dos_fatores</span> <span class="o">=</span> <span class="mi">0</span>  
</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">fator</span><span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="n">soma_dos_fatores</span> <span class="o">+=</span> <span class="n">fator</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="n">fator</span> <span class="o">==</span> <span class="mi">0</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"> <span class="k">break</span> <span class="k">if</span> <span class="n">soma_dos_fatores</span> <span class="o">==</span> <span class="n">n</span>  
</span></span><span class="line"><span class="cl"> <span class="n">n</span> <span class="o">+=</span> <span class="mi">2</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"> <span class="n">n</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Este programa procura até encontrar um “numéro ímpar perfeito”, e só então terminar (break). Ou seja, ele termina se, e somente se, tal número existe. O problema: sabemos hoje que existem números pares perfeitos. Por exemplo 6 = 1 + 2 + 3. 28 = 1 + 2 + 4 + 7. O próximo número perfeito é 496 e depois é 8218. Porém, ainda não sabemos qual é o primeiro número ímpar perfeito e também não sabemos demonstrar a existência ou ausência de tal número. Portanto: é impossível determinar se o programa acima um dia vai terminar ou se rodará indefinidamente. Mesmo se deixarmos esse programa rodar 100 anos e ele não encontrar um ímpar perfeito, não quer dizer que ela não exista: existem infinitos número, você pode tentar infinitamente sem concluir.</p>
<p>Agora por que tudo isso é importante? Nossos pequenos algoritmos são determinísticos e simples na maior parte dos casos. Bom, pelo menos eles parecem ser. A maioria dos programadores está acostumado a criar um algoritmo e testá-lo para uma ou duas dúzias de casos, se funciona para esses casos eles <em>assumem</em> que funciona para todos os casos. E não é sempre assim.</p>
<p>Um <em>excessivamente bom</em> anti-vírus poderia eventualmente ser capaz de detectar todo e qualquer vírus já feito ou que venha a ser feito, considerando simplificadamente que a definição de um vírus seja <em>software que se auto-replica</em>?</p>
<p>É impossível. E como temos certeza? Porque o algoritmo de anti-vírus pode ser reduzido a um Halting Problem. Se fosse possível criar um anti-virus como o que descrevi, significa que, por <a href="http://en.wikipedia.org/wiki/Reduction_%28complexity%29"target="_blank" rel="noopener">redução</a>, também seria possível resolver o Halting Problem. Como já foi provado por Church e Turing que nenhum método pode resolver esse problema, já sabemos de ante-mão que o novo problema (anti-virus) também não pode ser resolvido. É uma das técnicas de determinar se um novo algoritmo é ou não inconclusivo.</p>
<p>Isso significa que jamais poderemos dizer se um programa funciona ou não? Cuidado com a semântica, o Halting Problem afirma apenas que é impossível dizer se <em>todos</em> os programas terminam ou entram em loop infinito. Não que <em>nenhum</em> programa possa ser determinístico. Quando fazemos testes unitários de pequenos trechos de código, conseguimos imaginar de cabeça que aquele trecho é determinístico e possivelmente funciona em todos os casos que queremos.</p>
<p>Quando juntamos dezenas de pedaços diferentes – todos os eles unitariamente testados -, colocamos tudo num programa gigantesco, é muito difícil predizer que este novo programa funcionará corretamente, mesmo tendo testado unitariamente cada pedaço. Por isso mesmo temos testes funcionais. E podemos pegar cada conjunto maior anterior e combiná-lo num programa ainda maior. Para isso mesmo temos testes integrados.</p>
<p>Porém, quanto mais avançamos, mais e mais nossos programas chegam perto de um <a href="http://en.wikipedia.org/wiki/Non-deterministic_Turing_machine"target="_blank" rel="noopener">Turing Machine não-determinístico</a> e mais complicado é dizer qual serão os resultados.</p>
<p>A <a href="http://en.wikipedia.org/wiki/Computer_science"target="_blank" rel="noopener">Ciência da Computação</a> (bem como o campo de Física e Matemática) é muito rico, muito amplo. Nós, meros programadores não-acadêmicos, mal começamos a raspar a ponta do iceberg. Quem é interessado em criptografia deve conhece o conceito de <a href="http://en.wikipedia.org/wiki/One-way_function"target="_blank" rel="noopener">funções de mão-única</a> (one-way function), uma função fácil de executar mas custoso para reverter. Elas dão origem à criptografia assimétrica ou ou <a href="http://en.wikipedia.org/wiki/Public_key_cryptography"target="_blank" rel="noopener">Criptografia de Chave Pública</a>. O problema: ainda é uma questão em aberto se one-way functions realmente existem! Ou seja, é uma questão matemática ainda não provada ou desprovada.</p>
<p>Para a grande maioria dos programadores nada disso tem a menor importância. Este artigo é destinado às pessoas que tem <strong>curiosidade</strong> e <strong>vontade</strong> para aprender mais sobre o campo onde atuamos e mostrar que existe muito mais do que nossa visão alcança. A única maneira de sair com soluções criativas e inovadoras é conhecer o que já foi estudado, subir no ombro de gigantes como Church e Turing.</p>
]]></content:encoded><category>testes</category><category>ciencia-da-computacao</category></item></channel></rss>