230 testes verdes e um sistema que não funcionava
O primeiro marco do sistema fechou com 230 testes verdes, CI verde e a imagem de produção subindo. Subi tudo, entrei pelo navegador, e nada além do login funcionava. Toda tela com dado da clínica respondia 401 ou 500. Numa sessão de uso apareceram onze falhas, e uma delas deixava qualquer pessoa na internet criar uma conta no sistema.
O detalhe que importa: esse sistema é 100% vibecodado. É um sistema para clínicas médicas, e eu não encostei no código. Os agentes de IA escreveram tudo: o código, os testes, as migrations, o CI. Meu papel foi decidir o que construir e usar o que voltava.
Nenhum dado real tinha passado por ali, era tudo dado de teste. As onze falhas foram corrigidas e cada uma ganhou um teste que impede a volta. Mesmo assim ficou a pergunta: como 230 testes não pegaram nada disso?
Onde os testes erravam
O que me chamou atenção foi que quase nenhuma falha era falta de teste. Cada peça tinha cobertura. O que falhava era o jeito como os testes falavam com o sistema, quase sempre por um caminho que nenhum usuário usa.
Acho que isso tem muito a ver com quem escreveu. O agente escreve o código e o teste com a mesma cabeça. Se ele entendeu errado como uma coisa funciona, o teste nasce com o mesmo entendimento, e os dois concordam entre si. A suíte fica verde porque confirma o que o agente acreditava. Se isso bate com o que o sistema faz, ninguém verificou.
Separei sete padrões. Dá pra checar qualquer um deles na tua suíte hoje, escrita por gente ou por IA.
1. O teste usa um banco com mais poder que a aplicação
A suíte de autorização conectava no Postgres como superusuário. Superusuário ignora Row Level Security. As tabelas de papel e permissão tinham regra por clínica, e o código lia essas tabelas fora do contexto da clínica. No teste voltava tudo. Rodando de verdade voltava vazio, e o usuário entrava sem permissão nenhuma.
Quando a conexão do teste passou a ser a mesma da aplicação (NOBYPASSRLS), dois testes que passavam começaram a falhar.
O que checar: o teste usa o mesmo usuário de banco, com as mesmas permissões, que a aplicação usa em produção?
2. O teste monta à mão o que a aplicação monta sozinha
Cada teste montava o DataSource com uma lista explícita de entidades. A aplicação carrega por glob (*.entity.ts). Dois arquivos nasceram no plural, authz.entities.ts e mfa.entities.ts, e ficaram fora do glob. A aplicação subia, o healthcheck respondia 200, e a primeira consulta de autorização estourava com No metadata for Membership.
Todos os testes passavam, porque nenhum exercitava o carregamento real.
O que checar: o que o teste monta na mão é o mesmo que a aplicação monta sozinha?
3. O teste confirma o comentário em vez do comportamento
Esse foi o mais grave. Tinha um teste garantindo que toda rota declara o que exige. Ele lia o arquivo inteiro procurando a palavra @Public(). Bastava a palavra aparecer em qualquer lugar pro controller inteiro passar.
O @Public() estava na classe do controller de autenticação. O @RequirePermission do método não desfazia isso, porque no Nest são chaves diferentes de metadado. Resultado: a rota de convite respondia sem autenticação e devolvia o token do convite no corpo da resposta. Com esse token, qualquer pessoa definia uma senha e ficava com conta no sistema.
O comentário no código dizia que a rota era protegida. O teste concordava com o comentário.
Hoje o teste lê decorator por decorator, e um segundo teste consulta o metadado real e chama a rota sem token.
O que checar: o teste verifica o efeito, ou verifica que alguém escreveu a intenção certa?
4. O dado de teste é mais limpo que o dado real
O teste de busca de paciente por nome usava “jose antonio” contra “josé antônio pereira”. Passa. Só que ninguém digita assim. Na recepção a pessoa digita “maria”, e “maria” contra “maria aparecida souza” dava 0,27 de similaridade, abaixo do limite de 0,3 do pg_trgm. A busca dizia que a paciente não existia.
É daí que nasce cadastro duplicado. E juntar dois prontuários depois é praticamente irreversível.
O que checar: o dado do teste parece com o que a pessoa digita, ou com o que é conveniente pro teste passar?
5. Nenhum teste espera o tempo passar
O bug que mais me incomodou só aparecia 15 minutos depois do login, quando o access token expirava. A renovação rodava durante a renderização, onde o Next não deixa gravar cookie. Só que a rotação já tinha acontecido na API. O par novo se perdia, o token velho voltava na requisição seguinte, e a API tratava aquilo como roubo de sessão: derrubava todas as sessões da pessoa e disparava um evento crítico de segurança.
Nenhum teste esperava nada expirar. Pra reproduzir, rodei com o token durando 90 segundos.
O que checar: tem alguma coisa no sistema que muda de comportamento com o tempo? Tem algum teste que espera isso acontecer?
6. Só um lado do campo opcional é testado
Um campo do pedido de exame aceitava o código do exame no catálogo, e o código era descartado em silêncio quando não existia. Todos os testes mandavam só o nome livre, sem código, que também é um caminho válido e o mais comum. O caminho com código nunca rodou, e nele o vínculo com o catálogo se perdia sem nenhum aviso.
Um campo opcional cria dois caminhos. Testar só o vazio é testar metade.
O que checar: pra cada campo opcional que muda o comportamento quando vem preenchido, existe teste com ele preenchido?
7. A verificação não olha pra onde o problema mora
O typecheck do projeto cobria só src. Nenhum arquivo de teste era verificado por tipo. Uma assinatura de serviço mudou, o typecheck passou, e o erro só apareceu quando aquele teste rodou.
Quando a checagem passou a incluir os testes, apareceram 34 erros acumulados. Entre eles, testes acessando um campo que o tipo não garante. Passavam porque o dublê usado no teste nunca produzia o outro caso.
O que checar: typecheck, lint e cobertura olham pra todo o código, ou só pra parte que alguém lembrou de incluir?
O que mudou no meu jeito de trabalhar com IA
Fechar uma entrega agora inclui usar o que foi construído. Sem etapa formal de QA: abrir o sistema e clicar como a pessoa que vai usar.
Uma hora de uso encontrou o que 230 testes não encontraram, e as falhas mais graves apareceram nos primeiros dez minutos. Repeti isso em cada marco seguinte. O segundo teve 7 falhas e o terceiro teve 8, sempre com a suíte verde. O sistema chegou ao fim do MVP com mais de mil testes, e boa parte deles nasceu de uma falha que alguém só viu usando.
A IA escreve teste muito bem e muito rápido. Só que um teste escrito pela mesma cabeça que escreveu o código confirma o que essa cabeça acredita. Alguém precisa olhar de fora, e o jeito mais barato que encontrei de fazer isso é usar o sistema.
Uma regra que ficou junto: o que o sistema não entende, ele recusa na entrada. Três das falhas eram dado aceito e descartado em silêncio. Quem chamou acha que salvou, o sistema guardou nulo, nada falha, e a consequência aparece meses depois, quando outra função precisa do vínculo que nunca existiu.