← Todos os artigos

O suporte me ensinou mais sobre qualidade do que qualquer certificação

Antes de escrever a primeira asserção eu atendia o telefone. Do outro lado tinha um produtor bravo que não sabia dizer qual tela quebrou — e é essa pessoa que a suíte de testes deveria estar imitando.

Seis e quarenta e sete da manhã. Do outro lado da linha, o escritório de uma fazenda de soja no sudoeste de Goiás — alguém que já tinha tomado café, já tinha ido no talhão e já estava irritado antes de eu atender. A frase, na íntegra: "o sistema tá errado, o estoque tá negativo". Perguntei qual tela. Ela respondeu "a tela do sistema".

Levei dezenove minutos pra descobrir que ninguém estava olhando estoque coisa nenhuma. Estavam no relatório de custo por talhão, viram um número entre parênteses e parêntese, ali, significa "tá faltando produto". O que tinha acontecido de verdade: a saída de um defensivo foi lançada no dia 12, a nota de entrada só foi digitada no dia 19 com data retroativa, o sistema aceitou os dois sem reclamar e o custo médio da movimentação ficou negativo. O relatório, obediente, imprimiu o negativo entre parênteses.

Nada disso era bug de tela. E nada disso ia aparecer numa suíte que testa "cadastra produto → dá entrada → dá saída → confere saldo", nessa ordem, com data de hoje.

Fui analista de suporte, depois gerente de suporte, depois analista de suporte de novo, e só então QA Automation. Essa ordem não foi plano de carreira, foi o que aconteceu. Mas ela me deu uma coisa que curso nenhum dá: eu ouvi o usuário antes de escrever teste pra ele. E a tese que eu defendo desde então é chata de engolir — QA que nunca atendeu suporte testa o software; QA que atendeu suporte testa o dia do usuário. A diferença aparece exatamente nos bugs que a suíte não pega.

O bug não é o que o usuário diz que é

Traduzir relato em reprodução é habilidade treinável, não dom. Tem um método e ele é curto.

Pergunta que funciona: "me conta o que você fez antes de aparecer isso". Não "o que deu errado" — o que ela fez. Usuário descreve conclusão, não ação. "O sistema apagou meu lançamento" é conclusão. "Eu abri a nota, mudei o fornecedor, o campo de produto limpou sozinho e aí eu fechei sem salvar" é ação, e ação vira cy.get().

Segunda pergunta que funciona: "qual o número do documento?". Nota, pedido, ordem de serviço, código do talhão. Com um identificador eu vou no banco e leio o estado real em vez de acreditar na narrativa. Metade das vezes o registro contradiz o relato, e essa contradição é o bug.

Terceira: "isso já funcionou alguma vez? quando parou?" — isso me dá janela de deploy sem eu precisar dizer a palavra deploy.

Agora as que não funcionam. "Qual navegador você usa?" quase nunca é a primeira pergunta certa, e não é porque navegador não importa. É porque essa pergunta é sobre a minha hipótese, não sobre o dia dela — e a resposta vem errada com frequência assustadora ("o azulzinho"). Nas raras vezes em que o navegador importa mesmo, o user-agent já está no log do servidor e eu podia ter olhado sozinho. "Você consegue reproduzir?" também é ruim: é pedir pro cliente fazer o meu trabalho, e ainda por cima na frente do prejuízo dele.

Na prática

Duas perguntas antes de qualquer outra: o que você fez, e qual o número do documento. Com isso eu chego no registro; com o registro eu reconstruo o passo-a-passo. Tudo que for pergunta sobre ambiente vem depois, e boa parte nem precisa ser feita — está no log.

A ponte com automação é mais direta do que parece: um passo-a-passo de reprodução bem escrito já é o rascunho do teste. Se o ticket diz "logado como usuário do escritório, no talhão TL-118, com a nota 4471 já lançada, altero a data da aplicação para antes da entrada e salvo", isso é literalmente um beforeEach, um it e uma asserção. Quando eu escrevo o passo-a-passo pensando "isso vai virar código", eu paro de escrever "o sistema apresentou inconsistência" e passo a escrever o valor exato que apareceu na tela.

Sobre sair mal na foto: no meu primeiro mês como QA eu fechei quatro chamados como "não reproduz". Dois voltaram. Um deles voltou com vídeo — o cliente filmou a tela com o celular, tremendo, e o bug estava lá, nítido, no minuto 0:41. Eu, que tinha passado anos implorando pra que QA não fizesse exatamente isso comigo, virei o cara que fez isso. O que eu tinha feito de errado era testar com o meu usuário admin, no meu banco limpo, com o meu jeito de preencher. Nunca mais fechei "não reproduz" sem antes restaurar um dump do cliente.

O que o usuário disse O que estava acontecendo O teste que passou a existir
"O estoque tá negativo" Saída lançada com data anterior à nota de entrada; custo médio ficou negativo Movimentação retroativa a um saldo já fechado deve ser bloqueada, com asserção no custo médio resultante
"Sumiu o cadastro do produtor" CPF gravado com zero à esquerda pela importação, busca normalizava só um dos lados Busca por documento com máscara, sem máscara e com zero à esquerda
"Não salva, fica rodando" Planilha de 4.212 linhas estourava o timeout de 30s do gateway Importação de arquivo grande devolve 202 e o status conclui de forma assíncrona
"Salvei duas vezes porque não tinha certeza" Dois POST idênticos criavam dois lançamentos e baixavam estoque duas vezes Duplo clique em salvar + chave de idempotência
"O relatório não bate com a tela" Tela arredondava em 4 casas, exportação em 2, e a diferença aparecia no total Comparação de casas decimais entre tela, API e arquivo exportado

Dado real é feio

Ambiente de teste é um lugar higienizado onde todo produtor se chama João da Silva, toda fazenda tem 100 hectares e todo CNPJ é válido pelo dígito verificador. Produção não é assim. Produção tem Fazenda D'Ávila, tem campo obrigatório preenchido com um ponto porque a pessoa precisava passar da tela, tem CNPJ digitado com máscara em um cadastro e sem máscara no outro, tem planilha exportada do Excel em pt-BR com vírgula decimal, tem dois cadastros do mesmo produtor porque no primeiro alguém errou uma letra e no segundo consertou.

E aqui vem a parte em que eu apanho: a massa de dados sintética e bonitinha da nossa suíte foi ideia minha. Eu defendi com a palavra "determinístico", que é uma palavra ótima pra ganhar discussão. Funcionou lindamente por uns oito meses e não pegou um único dos bugs que estavam derrubando o suporte, porque o gerador nunca produzia nada que um humano cansado produziria. Determinístico e inútil não são opostos.

O que eu passei a fazer foi manter uma lista de valores adversariais — todos tirados de ticket, nenhum inventado — e rodar cada campo de texto e cada campo numérico contra ela por padrão:

cypress/e2e/cadastro-produtor.cy.ts
// Nenhum desses valores foi inventado. Todos vieram de chamado.
type CasoAdversarial = {
  rotulo: string;
  valor: string;
  esperado: 'aceita' | 'rejeita';
};

const NOMES_DE_VERDADE: CasoAdversarial[] = [
  { rotulo: 'apóstrofo',      valor: "Fazenda D'Ávila",      esperado: 'aceita' },
  { rotulo: 'acento composto', valor: 'Sítio São João Grãos', esperado: 'aceita' },
  { rotulo: 'espaco NBSP do Word',  valor: 'Sítio\u00A0Boa\u00A0Vista',  esperado: 'aceita' },
  { rotulo: 'só espaço',       valor: '   ',                   esperado: 'rejeita' },
  { rotulo: 'ponto de teimoso', valor: '.',                    esperado: 'rejeita' },
  { rotulo: 'limite exato',    valor: 'A'.repeat(120),          esperado: 'aceita' },
  { rotulo: 'limite + 1',      valor: 'A'.repeat(121),          esperado: 'rejeita' },
];

const AREAS_EM_HECTARE: CasoAdversarial[] = [
  { rotulo: 'vírgula decimal (pt-BR)', valor: '1.250,75', esperado: 'aceita' },
  { rotulo: 'ponto decimal (planilha)', valor: '1250.75', esperado: 'aceita' },
  { rotulo: 'negativo',                 valor: '-40',      esperado: 'rejeita' },
  { rotulo: 'notação científica',       valor: '1e3',      esperado: 'rejeita' },
];

describe('Cadastro de produtor — valores que chegam do campo', () => {
  beforeEach(() => {
    cy.loginComoEscritorio();
    cy.visit('/produtores/novo');
  });

  NOMES_DE_VERDADE.forEach(({ rotulo, valor, esperado }) => {
    it(`nome (${rotulo}) deve ${esperado}`, () => {
      cy.get('[data-cy=nome-produtor]')
        .clear()
        .type(valor, { parseSpecialCharSequences: false });
      cy.get('[data-cy=area-hectare]').clear().type('420,5');
      cy.get('[data-cy=salvar-produtor]').click();

      if (esperado === 'rejeita') {
        cy.get('[data-cy=erro-nome]').should('be.visible');
        cy.url().should('include', '/produtores/novo');
        return;
      }

      cy.get('[data-cy=toast-sucesso]').should('be.visible');

      // O apóstrofo nunca quebrou o salvar. Quebrou o buscar, três telas depois.
      cy.visit('/produtores');
      cy.get('[data-cy=busca-produtor]').type(valor.trim().slice(0, 12));
      cy.get('[data-cy=lista-produtores] tr').should('have.length', 1);
    });
  });
});

Repare na última asserção. O salvar sempre funcionou — o que quebrava era a busca, porque o apóstrofo entrava numa concatenação de LIKE e o resultado voltava vazio. O cliente ligava dizendo "sumiu meu cadastro". O cadastro estava lá.

O caminho feliz é uma ficção

Existe um usuário imaginário que abre a tela, preenche de cima pra baixo, clica em salvar uma vez, espera o toast e vai embora satisfeito. Esse usuário não existe. O que existe é uma pessoa com internet de rádio que oscila, que clica em salvar duas vezes porque na primeira não aconteceu nada visível, que volta com o botão do navegador porque é mais rápido do que achar o "voltar" da tela, e que deixa duas abas abertas — uma com o lançamento de ontem, outra com o de hoje — porque ela precisa comparar.

Cada uma dessas frases é um teste. Literalmente:

cypress/e2e/lancamento-aplicacao.cy.ts
const preencherAplicacaoValida = () => {
  cy.get('[data-cy=defensivo]').select('DF-402 — Glifosato 480 SL');
  cy.get('[data-cy=dose-ha]').clear().type('2,5');
  cy.get('[data-cy=area-aplicada]').clear().type('87,4');
};

describe('Lançamento de aplicação — usuário real, não persona', () => {
  beforeEach(() => {
    cy.loginComoEscritorio();
    cy.intercept('POST', '/api/aplicacoes').as('salvarAplicacao');
    cy.visit('/talhoes/TL-118/aplicacoes/nova');
    preencherAplicacaoValida();
  });

  it('duplo clique em salvar não gera dois lançamentos', () => {
    // Ninguém espera o spinner. Nem eu, e eu escrevi o spinner.
    cy.get('[data-cy=salvar-aplicacao]')
      .click()
      .click({ force: true });

    cy.wait('@salvarAplicacao');
    cy.get('@salvarAplicacao.all').should('have.length', 1);

    cy.request('/api/talhoes/TL-118/aplicacoes?data=2026-06-24')
      .its('body.itens')
      .should('have.length', 1);
  });

  it('voltar no navegador e salvar de novo não duplica nem baixa estoque duas vezes', () => {
    cy.get('[data-cy=salvar-aplicacao]').click();
    cy.wait('@salvarAplicacao').its('response.statusCode').should('eq', 201);

    cy.go('back'); // volta pro formulário ainda preenchido, via bfcache
    cy.get('[data-cy=salvar-aplicacao]').should('be.visible').click();

    // O segundo POST tem que morrer na chave de idempotência,
    // não numa constraint do banco devolvendo 500 na cara do produtor.
    cy.wait('@salvarAplicacao').its('response.statusCode').should('eq', 409);
    cy.get('[data-cy=aviso-ja-lancado]').should('be.visible');

    cy.request('/api/talhoes/TL-118/aplicacoes?data=2026-06-24')
      .its('body.itens')
      .should('have.length', 1);

    // 999 em estoque, 2,5 L/ha em 87,4 ha = 218,5 baixados. Uma vez só.
    cy.request('/api/estoque/defensivo/DF-402')
      .its('body.saldo')
      .should('eq', 780.5);
  });
});

A asserção que importa nesse segundo teste não é a do 409. É a do saldo. Duplicidade de registro alguém percebe; baixa dupla de estoque só aparece três semanas depois, no inventário, quando já não dá pra saber quem lançou o quê. Sempre que der, feche o teste no dado, não na tela.

Severidade não é técnica, é humana

A priorização que sai de reunião de dev tende a ordenar bug por complexidade e por medo: crash primeiro, exceção não tratada em seguida, "cosmético" no fim da fila. É uma ordenação sobre o código, não sobre o estrago.

Suporte ensina outra hierarquia. Um campo desalinhado que corta o último dígito do valor numa tela usada 200 vezes por dia machuca mais que um crash numa tela de configuração que dois clientes abrem por mês. O primeiro gera erro de digitação silencioso e conferência manual todo santo dia; o segundo gera um chamado bimestral com contorno conhecido.

O critério que eu uso hoje tem três fatores e cabe numa linha: frequência de uso × existência de contorno × reversibilidade do dano. Se o dado estragado só volta com script no banco, é P1 mesmo que atinja um cliente só. Se tem contorno de dez segundos que o usuário descobre sozinho, não é P1 nem que a stack trace seja linda.

Tem gente que discorda disso com um argumento sério, e o argumento é: priorizar por dor de usuário faz a fila virar gestão por reclamação, a dívida técnica nunca é paga e daqui a dois anos o produto não se sustenta. Concordo com o diagnóstico e discordo da conclusão. A resposta não é ignorar a dor — é não deixar a dor governar o backlog inteiro. Fatia fixa da sprint pra dívida, negociada uma vez e defendida sempre; o critério de dor governa só a fila de bug. E, na prática, a dor recorrente costuma ser o melhor mapa de onde a arquitetura está doendo. Quando o mesmo módulo aparece em 30% dos chamados do trimestre, aquilo não é azar do usuário.

Gerência de suporte me ensinou a ler número

Como gerente eu olhava painel o dia inteiro, e demorei pra entender que a maioria daqueles números não falava sobre o produto.

Volume total de chamados é teatro. Sobe quando lança versão, cai em feriado agrícola, dobra em fechamento de safra. CSAT isolado também: mede o atendente, o tom de voz, a velocidade da resposta — praticamente nunca a qualidade do software. Tempo médio de atendimento então é a métrica mais fácil de manipular que existe, basta fechar chamado rápido demais.

Os números que realmente apontam bug são três, e nenhum deles aparece bonito em slide. Primeiro: recorrência do mesmo assunto por clientes distintos. Um cliente reclamando pode ser processo interno dele; sete clientes diferentes reclamando da mesma coisa em noventa dias é defeito de produto, ponto. Segundo: taxa de reabertura. Chamado que volta significa que a correção tratou sintoma. Terceiro, e esse é o meu favorito porque quase ninguém olha: tempo parado em "aguardando informação do cliente". Quando o suporte demora pra dar a primeira resposta útil porque não consegue reproduzir, o defeito não é do atendente — é do produto, que não deixa rastro. Falta id de erro na tela, falta log com contexto, falta o número da transação em algum lugar copiável.

Hoje eu uso isso para decidir a ordem de automação, e a conta é simples: pego os assuntos mais recorrentes por cliente distinto dos últimos 90 dias, cruzo com telemetria de uso das telas, e automatizo de cima pra baixo. No último trimestre os 9 assuntos do topo respondiam por 34% dos chamados; automatizei os 5 primeiros e um deles pegou regressão em produção duas semanas depois. Escrever teste pela cobertura de linha, em comparação, é escolher alvo com os olhos fechados.

Ah, e por que eu voltei de gerente para analista: eu passava a semana inteira em reunião falando sobre trabalho que eu não estava fazendo. Descobri que gosto mais de mexer do que de coordenar, devolvi o cargo e não me arrependi um dia.


Certificação te dá vocabulário — partição de equivalência, valor limite, tabela de decisão. É útil e eu uso todo dia. O que ela não te dá é a lista específica de valores que o seu usuário digita quando está com pressa, nem a noção de que o bug mais caro do trimestre pode ser um arredondamento que ninguém classificaria como bug.

Se você é QA e nunca leu um chamado cru — não o resumo higienizado que chegou no board, mas o texto que o cliente escreveu irritado às seis e quarenta e sete da manhã — você está testando o software que a especificação descreve. O outro, o que roda na fazenda, segue sem cobertura.

Peça acesso à fila do suporte. Leia trinta chamados. Você vai escrever teste diferente na sexta-feira.

Luciano Martins

Luciano Martins da Silva Junior

Bacharel em Engenharia de Software, pós-graduado em Ciência de Dados e Machine Learning. 8+ anos entre suporte, automação de testes e engenharia de dados. Escreve sobre o que quebra em produção.

Tem um problema difícil de qualidade?

Suíte instável, pipeline lento, dado que mente. É esse tipo de coisa que eu gosto de desembaraçar.

Vamos conversar