Passou em todo teste localmente. Estava quebrado no segundo em que foi deployado.
Local-green é um proxy rápido da verdade, nunca a verdade, a superfície deployada é o único juiz.
Apollo Space Research
Apollo Space
A suíte de testes estava verde. Todo unit test, todo integration test, o run end-to-end local, tudo passou na máquina do engenheiro, duas vezes, com o tipo de saída limpa que faz você ir atrás do botão de deploy. Então deployamos. E a primeira pessoa real que tocou a superfície ao vivo bateu num muro que o laptop nunca uma vez produziu.
Nada estava errado com o código. Nada estava errado com os testes também. O que estava errado era a crença de que um havia provado o outro.
Local-green é um proxy rápido da verdade, nunca a verdade, a superfície deployada é o único juiz.
Este post é sobre esse gap: por que uma máquina cheia de testes passando pode entregar algo quebrado, por que a resposta não é “escrever mais testes”, e a disciplina que construímos para que local-green pare de fingir que está pronto.
A versão ingênua: verde na minha máquina significa pronto
O modelo mental óbvio é o que todo framework de teste silenciosamente te ensina. Você escreve o código. Você escreve os testes. Os testes ficam verdes. Verde significa correto. Correto significa entregável. A barra de progresso enche, os checkmarks se alinham, e a parte satisfeita do seu cérebro arquiva a tarefa como terminada.
É uma sensação maravilhosa e é, surpreendentemente frequente, uma mentira.
Não porque os testes sejam ruins. Os testes estão fazendo exatamente o que foram pedidos para fazer, estão confirmando que o código se comporta do jeito que o autor do código esperava, dentro do mundo que a máquina do autor construiu. Esse mundo é o problema. O laptop tem seu próprio banco de dados que alguém semeou à mão, sua própria configuração com alguns valores silenciosamente ajustados meses atrás, seu próprio relógio, sua própria rede onde nada nunca está longe e nada nunca é lento. Os testes passam porque o código e seu ambiente de origem cresceram juntos e concordam um com o outro.
Verde diz que o código concorda com seu teste. Não diz que o código concorda com o mundo.
A superfície deployada é um mundo diferente. O banco de dados tem uma linha que o script de seed nunca criou. Um valor de configuração que estava presente localmente está ausente no ambiente em execução. Uma chamada que retornava em um milissegundo no loopback agora cruza uma rede real e às vezes não retorna de jeito nenhum. O usuário clica numa ordem que nenhum teste imaginou, porque o usuário não leu o teste. Cada uma dessas diferenças é um lugar onde verde-na-minha-máquina e funciona-em-produção silenciosamente discordam, e a discordância é invisível até alguém real esbarrar nela.
Essa é a armadilha, e vale declará-la claramente para podermos parar de cair nela. A versão ingênua não é “o engenheiro foi descuidado”. O engenheiro rodou os testes e os testes passaram. A versão ingênua é acreditar que um teste passando numa máquina local é o mesmo tipo de fato que um fluxo funcionando na deployada. Não é. Um é um proxy. O outro é a verdade.
Por que “escrever mais testes” não fecha o gap
O instinto, depois de você se queimar, é escrever mais testes. Se a suíte local perdeu a falha de produção, a suíte deve estar incompleta, então preencha os buracos. Adicione um caso para a config ausente. Adicione um caso para a chamada lenta. Adicione um caso para a linha que o script de seed esqueceu.
Isso é progresso real e também é uma esteira que você não consegue terminar de correr.
A razão é simples uma vez que você a diz em voz alta. Cada teste que você escreve codifica uma falha que você já imaginou. Você só pode testar para a diferença de produção em que pensou, e a categoria inteira de bug sobre a qual estamos falando é a diferença em que você não pensou. O dado de seed que você esqueceu é, por definição, o que você não sabia que precisava adicionar. A config que estava ausente em produção estava ausente precisamente porque ninguém imaginou precisar dela. Você não pode escrever um teste para a suposição que você não sabe que está fazendo, e o trabalho inteiro do ambiente local, prestativa e traiçoeiramente, é fazer cem suposições por você para que você não tenha que pensar nelas.
Local-green é um proxy rápido da verdade, nunca a verdade, a superfície deployada é o único juiz. Mais testes deixam o proxy melhor. Não o tornam a verdade. Uma suíte local mais rica ainda roda no mesmo mundo conveniente, ainda herda os mesmos acordos escondidos entre o código e sua origem, e ainda não consegue te dizer o que acontece quando o código está num lugar que não concorda com ele.
Então paramos de tentar tornar o proxy perfeito. Um proxy perfeito ainda é um proxy. Fizemos outra coisa em vez disso: tornamos o proxy rápido e barato e o rodamos constantemente, e depois colocamos um segundo juiz, mais lento, depois dele que roda no único ambiente cujo veredito de fato conta.
A disciplina de dois loops: um proxy rápido, depois o juiz real
Aqui está a forma à qual chegamos, e a ideia inteira cabe em uma frase: rode a checagem barata cedo e com frequência, e nunca deixe que ela fale pela cara.
Nós os chamamos de inner loop e outer loop, e a distinção entre eles é o ponto inteiro.
O inner loop é local. É tudo o que roda na máquina de um desenvolvedor ou num harness rápido em segundos, os unit tests, os integration tests, um run local do comportamento de fato contra um ambiente quase-real. Seu trabalho é velocidade. Quando você muda algo que poderia quebrar a coisa, você quer saber em segundos, não depois de um deploy, porque uma falha que você pega em segundos custa quase nada e uma falha que você pega depois de entregar custa atenção, um rollback e a confiança de quem esbarrou nela. O veredito do inner loop tem um nome preciso: comportamentalmente verde, pendente o teste real. Não pronto. Pendente.
O outer loop é a superfície deployada. É o ambiente ao vivo, com os dados reais e a rede real e a configuração real, exercitado do jeito que uma pessoa real o exercitaria, um fluxo clicado até o fim na coisa que está de fato rodando. Seu veredito é o único que pode dizer pronto. Não porque o inner loop seja indigno de confiança, mas porque o inner loop, por construção, não consegue ver o mundo em que o outer loop vive.
A disciplina é manter esses dois veredictos como dois números e nunca colapsá-los em um. Inner-green é uma contagem de quantas checagens rápidas passam. Outer-green é uma contagem de quantos fluxos reais funcionam na superfície deployada. O dia em que você deixa o primeiro número valer pelo segundo é o dia em que uma máquina verde entrega um produto quebrado, que é exatamente o dia sobre o qual este post inteiro fala.
O inner loop ganha seu lugar por ser rápido, não por estar certo
Seria fácil ler isso como “testes locais não importam”. O oposto é verdade. O inner loop é a ferramenta mais usada que temos, e ele ganha isso por ser implacavelmente barato.
Considere a alternativa que todo time já viveu. Um bug sutil no comportamento de um sistema em execução, do tipo em que a lógica está quase certa e dá errado apenas na segunda interação, é pego apenas depois do deploy, observando a superfície ao vivo, um ciclo lento de cada vez. Cada ciclo é um push, uma espera pelo ambiente subir, um clique através, um franzir de olhos na saída. Minutos por tentativa. Suponha que um único bug esquivo leva uma dúzia de tentativas para ser fixado; isso é um pedaço de uma tarde gasto esperando a infraestrutura te dizer algo que um run local poderia ter te dito antes do almoço.
Então empurramos o máximo possível do achar para o inner loop, onde um ciclo são segundos, e reservamos o outer loop para o julgar, onde o veredito é real. Ache barato. Julgue verdadeiro. O erro nunca é que rodamos a checagem rápida. O erro é deixar a checagem rápida assinar o release.
Essa é também a razão pela qual a falha, quando o outer loop pega uma, não é um lamento, é um endereço. Um fluxo que quebra na superfície deployada é um gap localizado: este ambiente, este passo, esta diferença do local. E a coisa mais valiosa que fazemos com esse endereço é transformá-lo de volta numa checagem do inner loop, para que o proxy que o perdeu uma vez nunca possa perdê-lo de novo. O outer loop não só julga. Ele ensina ao inner loop como o mundo se parece.
A virada: o único veredito que importa é uma pessoa, não um checkmark
Quero ser honesto sobre o que está sob tudo isso, porque não é realmente sobre testes.
Um checkmark é uma promessa de que algo vai funcionar. Um deployment funcionando é a promessa cumprida. Toda cultura de engenharia tem que decidir qual desses ela celebra, e a tragédia silenciosa de muito software é que ele celebra a promessa. Builds verdes, pipelines passando, dashboards cheios de cor reconfortante, tudo isso é a sensação de pronto, fabricada por ferramentas que nunca uma vez conheceram seu usuário. A sensação é real. O estar-pronto não é. E a distância entre eles é medida no número exato de pessoas que clicaram na coisa ao vivo e receberam o muro em vez do resultado.
A razão pela qual mantemos a linha entre os dois loops tão teimosamente é que alguém está parado do outro lado do outer loop. Não uma suíte. Uma pessoa, com uma tarefa que precisava terminada, que não liga para quão verde o laptop estava. Ela vai julgar o trabalho no único tribunal que sempre importou, aquele onde o software está de fato rodando e ela está de fato usando, e ela vai estar certa, porque esse é o único lugar onde a verdade sempre viveu. Um teste passando é nós dizendo a nós mesmos que o trabalho está pronto. Um deployment funcionando é o mundo concordando.
Local-green é um proxy rápido da verdade, nunca a verdade, a superfície deployada é o único juiz. Construímos nossa forma inteira de entregar em torno de recusar esquecer isso, porque o momento em que uma máquina verde pode declarar vitória é o momento em que a pessoa do outro lado começa a pagar pela nossa confiança.
É isso que estamos construindo na Apollo Space: um sistema operacional que trata a superfície deployada como o juiz e um teste passando como a opinião educada que ele realmente é. Se você já empurrou algo verde e o viu quebrar na frente da primeira pessoa real que o tocou, você já sabe qual desses dois veredictos você confiaria com seu nome.
A Apollo cuida da operação repetitiva da sua empresa pro seu time não precisar.
Entre na lista de espera: acesso antecipado, preço de usuário fundador e um lugar na primeira fila enquanto a gente constrói.
Entrar na lista de esperaO imposto oculto dos agents em paralelo é um diamante de migrations
Seis agents escrevendo para um schema conflitam no banco de dados, não no código, e a CI morre em "multiple heads".
EngenhariaUm orchestrator que não sobrevive ao próprio crash não é um
Um crash que apaga o raciocínio do orchestrator perde a única coisa que você não consegue reconstruir.
EngenhariaColoque um portão determinístico na frente do seu revisor mais esperto
A pega-defeito mais barata é um script burro que checa se duas branches mergeadas ainda sobem antes de qualquer julgamento.