APRENDIZADO & COGNIÇÃO

92% de cobertura numa base herdada: contei as asserções antes de acreditar no número

Recebi acesso de leitura a um monolito em Node com TypeScript, algo perto de 200 mil linhas, seis anos de…

Recebi acesso de leitura a um monolito em Node com TypeScript, algo perto de 200 mil linhas, seis anos de histórico e quatro fornecedores diferentes no caminho. A primeira informação que me passaram sobre qualidade foi uma linha: “a cobertura está em 92%”.

Cobertura mede linha executada durante a suíte. Ela não mede verificação. Um teste que chama a função, não explode e termina conta a linha como coberta exatamente igual a um teste que compara o resultado com o esperado. Antes de abrir qualquer arquivo de produção, rodei um contador de asserções.

Primeira passada: por arquivo, com regex

Comecei pelo mais burro possível, porque roda em qualquer repositório sem instalar nada.

// conta-arquivos.jsconst { readFileSync } = require('node:fs');const { execSync } = require('node:child_process');const files = execSync( "find . -path ./node_modules -prune -o ( -name '*.spec.ts' -o -name '*.test.ts' ) -print", { encoding: 'utf8' }).trim().split('n').filter(Boolean);const ASSERCAO = /b(expect|assert|should|toMatchSnapshot)s*(/;const CASO = /b(it|test)s*(/g;let casos = 0;const mudos = [];for (const file of files) { const src = readFileSync(file, 'utf8'); casos += (src.match(CASO) || []).length; if (!ASSERCAO.test(src)) mudos.push(file);}console.log(`arquivos de teste: ${files.length}`);console.log(`casos (it/test): ${casos}`);console.log(`arquivos sem nenhuma asserção: ${mudos.length}`);mudos.slice(0, 5).forEach((f) => console.log(' ' + f));

Saída:

$ node conta-arquivos.jsarquivos de teste: 214casos (it/test): 1318arquivos sem nenhuma asserção: 37 src/billing/invoice.reprocess.spec.ts src/integrations/erp.sync.spec.ts src/reports/closing.spec.ts src/queue/retry-policy.spec.ts src/auth/session.cleanup.spec.ts

37 arquivos subiam módulo, chamavam função, esperavam promessa e não verificavam nada. Eles passam sempre. E cada linha que eles tocam entra na conta dos 92%.

Segunda passada: por caso, com AST

O número por arquivo é otimista, porque um arquivo com uma asserção no primeiro caso já sai da lista. O que importa é o caso, e aí a regex empaca. Troquei por ts-morph.

// conta-casos.jsconst { Project, SyntaxKind } = require('ts-morph');const project = new Project({ tsConfigFilePath: './tsconfig.json' });const alvos = new Set(['it', 'test', 'it.only', 'test.only']);let total = 0;const mudos = [];for (const sf of project.getSourceFiles(['**/*.spec.ts', '**/*.test.ts'])) { for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) { if (!alvos.has(call.getExpression().getText())) continue; total++; const corpo = call.getArguments()[1]; if (!corpo) continue; const verifica = corpo .getDescendantsOfKind(SyntaxKind.CallExpression) .some((c) => /^(expect|assert|chai.expect)/.test(c.getExpression().getText())); if (!verifica) { mudos.push(`${sf.getBaseName()} :: ${call.getArguments()[0].getText()}`); } }}const pct = ((mudos.length / total) * 100).toFixed(1);console.log(`casos: ${total}`);console.log(`casos sem asserção: ${mudos.length} (${pct}%)`);mudos.slice(0, 5).forEach((m) => console.log(' ' + m));
$ node conta-casos.jscasos: 1318casos sem asserção: 241 (18.3%) invoice.reprocess.spec.ts :: 'reprocessa fatura vencida' erp.sync.spec.ts :: 'sincroniza pedidos do dia' closing.spec.ts :: 'gera fechamento mensal' retry-policy.spec.ts :: 'respeita o backoff' session.cleanup.spec.ts :: 'limpa sessões expiradas'

Quase um em cada cinco casos não faz pergunta nenhuma ao sistema. Repare nos nomes: são justamente os fluxos que mexem em dinheiro e em integração externa. Ninguém escreve teste mudo para o formatador de CPF, porque ali a asserção é óbvia. O teste mudo nasce onde montar o cenário já deu trabalho demais e a pessoa parou quando o vermelho sumiu.

Um exemplar típico:

it('reprocessa fatura vencida', async () => { const fatura = await criarFatura({ status: 'vencida' }); await service.reprocessar(fatura.id);});

Esse caso percorre o serviço de faturamento inteiro, marca todas as linhas como cobertas e só falha se alguém jogar uma exceção não tratada. Troque a regra de juros por outra e ele continua verde.

O primo disso no monitoramento

O mesmo padrão aparece em observabilidade, e nessa base ele estava lá:

@Get('/health')health() { return { status: 'ok', uptime: process.uptime() };}

O endpoint responde 200 porque foi programado para responder 200. O painel fica verde com o banco fora do ar. A métrica existe, a garantia não. Substituí por uma versão que toca a dependência com timeout curto e devolve 503 quando ela não responde, que é uma das poucas mudanças que eu aceito fazer antes de entender o sistema.

O que eu faço com esse inventário

Nada de refatorar, e vou explicar por quê. Os 241 casos mudos são, ao mesmo tempo, o mapa das áreas que ninguém consegue testar direito e a lista do que está sem rede de proteção. Mexer neles antes de medir é trocar de lugar um problema que você ainda não conhece.

O que entra na primeira rodada é aditivo:

  1. Congelar os arquivos de produção que aparecem nos casos mudos e são também os mais alterados no último ano. É onde a regra de negócio vive.
  2. Instrumentar em vez de mexer: log estruturado nas rotas que esses fluxos atendem, captura de erro ligada, alarme no job noturno que falha calado.
  3. Rodar mutation testing num diretório só, não na suíte inteira. Em src/billing o Stryker levou perto de 40 minutos e devolveu um score que não tinha relação nenhuma com os 92%. Na suíte completa levaria a madrugada e ninguém esperaria.
  4. Escrever asserção primeiro nos casos mudos que já existem, aproveitando o cenário montado. É o teste mais barato de escrever que existe, porque o trabalho chato já está feito.

O teste do mutante é o único que responde à pergunta que interessa: se eu mudar o sinal daquela comparação, alguma coisa fica vermelha? Cobertura e contagem de asserção são degraus antes dele, e servem porque rodam em minutos.

Onde isso não serve

Não serve se o sistema está caindo agora. Com o checkout fora do ar, você estanca primeiro e mede depois, aceitando alguns dias às cegas. Também rende pouco em base pequena, coisa de 8 ou 10 mil linhas, onde ler os testes inteiros leva menos tempo que montar o instrumento.

E o script tem limite honesto: ele não enxerga asserção dentro de helper próprio. Se o time escreveu verificaFatura(resultado) com os expects lá dentro, o caso entra na lista de mudos sem ser mudo. Nessa base eu conferi na mão os 37 arquivos do primeiro passo e três eram falso positivo.

Rodei nos dois últimos sistemas que herdei e a faixa ficou entre 12% e 20% de casos sem asserção. Queria saber se isso é o normal do mercado ou azar meu. Se você rodar aí, comenta o número: quantos por cento dos seus casos não verificam nada, e o score de mutação bateu com a cobertura quando você foi conferir?

· · ·

Publicado originalmente no blog da Revin: https://revin.com.br/pt/blog/codigo-espaguete-herdado-7-primeiros-dias

Perguntas frequentes

Respostas institucionais sobre esta cobertura, fontes e limites editoriais.

Sobre o que trata “92% de cobertura numa base herdada: contei as asserções antes…”?
Recebi acesso de leitura a um monolito em Node com TypeScript, algo perto de 200 mil linhas, seis anos de histórico e quatro fornecedores diferentes no caminho. A primeira informação que me passaram sobre qualidade foi…
O que a matéria explica sobre “Primeira passada: por arquivo, com regex”?
Comecei pelo mais burro possível, porque roda em qualquer repositório sem instalar nada.// conta-arquivos.jsconst { readFileSync } = require('node:fs');const { execSync } = require('node:child_process');const files = execSync( "find . -path ./node_modules -prune -o ( -name '*.spec.ts' -o -name '*.test.ts' ) -print", {…
O que a matéria explica sobre “Segunda passada: por caso, com AST”?
O número por arquivo é otimista, porque um arquivo com uma asserção no primeiro caso já sai da lista. O que importa é o caso, e aí a regex empaca. Troquei por ts-morph.// conta-casos.jsconst { Project, SyntaxKind } = require('ts-morph');const project =…
O que a matéria explica sobre “O primo disso no monitoramento”?
O mesmo padrão aparece em observabilidade, e nessa base ele estava lá:@Get('/health')health() { return { status: 'ok', uptime: process.uptime() };}O endpoint responde 200 porque foi programado para responder 200. O painel fica verde com o banco fora do ar. A métrica existe,…
O que a matéria explica sobre “O que eu faço com esse inventário”?
Nada de refatorar, e vou explicar por quê. Os 241 casos mudos são, ao mesmo tempo, o mapa das áreas que ninguém consegue testar direito e a lista do que está sem rede de proteção. Mexer neles antes de medir é trocar…
Quem edita o conteúdo do Estrato Mente?
A cobertura é produzida e revisada pela equipe editorial do Estrato Mente, com editor-chefe responsável pela linha editorial. Conheça a redação em https://mente.estrato.cc/equipe/.
Quais fontes o Estrato prioriza?
Priorizamos fontes primárias (órgãos oficiais, balanços, estudos revisados, documentos públicos) e cruzamos informações antes da publicação, conforme a política editorial.
Como solicitar correção de uma matéria?
Envie o pedido pela página de Correções (https://mente.estrato.cc/correcoes/) ou Contato (https://mente.estrato.cc/contato/). Erros materiais são corrigidos com registro da atualização.
WhatsApp X LinkedIn
Compartilhar: LinkedIn
Rafael Gomes

Rafael Gomes

Rafael Gomes integra a equipe editorial do Estrato Mente, vertical da rede Estrato, na função de Repórter de Produtividade. O escopo permanente de cobertura inclui rotinas e trabalho profundo, com atenção ao leitor brasileiro que busca contexto factual, datas oficiais e implicações práticas. Na produção diária, prioriza fontes primárias (órgãos públicos, balanços, papers e documentos oficiais), cruza pelo menos duas referências independentes quando o tema é contestado e evita manchetes que prometam certeza onde há incerteza. Critérios de qualidade: lead com o fato principal, atribuição clara de autoria da equipe Estrato, atualização com selo quando há mudança material e linguagem acessível sem simplificar demais o risco ou o contexto. Trabalha sob revisão da mesa editorial e do editor-chefe do portal. Matérias YMYL recebem segunda leitura antes da publicação e podem ser atualizadas quando surgem novos dados oficiais. Limites: este perfil descreve o papel editorial na marca Estrato. O conteúdo publicado é informativo e não constitui aconselhamento médico, financeiro, jurídico ou profissional personalizado. Transparência ao leitor: metodologia em /metodologia/, ética em /etica-editorial/, correções em /correcoes/, equipe em /equipe/ e canal em /contato/ ([email protected]). Na página-mãe da rede, a bio ampliada e o arquivo de matérias ficam em estrato.cc/blog/, com link para o arquivo do autor em cada portal.

Deixe um comentário