FNSP Web Page

Mocks: quando ajudam e quando escondem o bug

Um mock serve para cortar uma dependência que não se pode ou não se deve tocar num teste: a rede, o relógio, o fornecedor de pagamentos. Dito assim parece só conveniência. É uma troca — e vale a pena saber o que se dá em troca de quê.

1. O que um mock é, de facto

É uma suposição escrita em código. Ao dizer «quando chamarem isto, devolve aquilo», está a registar-se aquilo que se acredita que o sistema de fora faz.

A partir daí o teste deixa de verificar comportamento e passa a verificar coerência interna: passa porque a suposição é consistente consigo própria. Se a suposição estiver errada, o teste fica verde na mesma.

// O que o mock diz que a API devolve
mock(buscarUtilizador).resolves({ data: { nome: 'Ana' } });

// O que a API devolve mesmo
// { results: [{ name: 'Ana' }] }

Teste verde, produção partida — e a falha aparece longe dali, num sítio onde ninguém se lembra deste ficheiro.

2. A escala, do melhor para o pior

  • Não precisar de nenhum. Uma função que recebe valores e devolve valores testa-se sem cerimónia.

  • A coisa a sério, quando é barata. Um Postgres em contentor arranca em segundos e não mente sobre o que o SQL faz.

  • Uma implementação falsa que nos pertence — um repositório em memória que cumpre a mesma interface. Falha como a verdadeira, e é nossa para corrigir.

  • Um stub que devolve dados enlatados. Já é uma suposição.

  • Um mock que verifica chamadas — «foi chamado uma vez, com estes argumentos». É o mais frágil de todos: prende o teste à forma como o código está escrito, e passa a partir-se em cada reorganização que não muda comportamento nenhum.

3. Este site não tem nenhum

Fui ver os testes e não há um único mock. Não foi disciplina: é consequência de onde a lógica ficou.

contarPalavras(html)        // -> número
primeiraMedia(html)        // -> a imagem ou o vídeo
realcarCodigo(html)        // -> o mesmo html, com cor
resumirHtml(html, 300)     // -> texto
tomMaisLivre([0, 30, 60])  // -> número

Recebem um valor e devolvem outro. Não sabem que existe base de dados, nem pedido, nem sessão — e por isso não há nada para fingir.

É esse o sinal a reter: quando um teste precisa de cinco mocks para arrancar, o problema não está no teste. Está no facto de aquela lógica estar entalada entre coisas de que não precisava.

4. O que tem mesmo de ser falsificado

Sobra sempre alguma coisa: o relógio, o acaso, a rede. Para esses há uma escolha melhor do que substituir a função global — recebê-los como argumento.

// Difícil de testar: o relógio vem de dentro
function expirou(sessao) {
  return Date.now() > sessao.fim;
}

// Fácil: o relógio entra pela porta
function expirou(sessao, agora = Date.now()) {
  return agora > sessao.fim;
}

A segunda versão testa-se em qualquer instante sem biblioteca nenhuma — e continua a escrever-se igual em produção.

5. E o problema do desvio

Nada disto resolve a questão de fundo: a API lá fora pode mudar e os nossos testes continuam verdes. As duas respostas sérias são gravar respostas reais como amostras e voltar a gravá-las de tempos a tempos, ou ter um teste — um só, lento, à parte dos outros — que fala mesmo com o serviço.

Qualquer uma delas é melhor do que a alternativa habitual, que é descobrir o desvio em produção.

O resumo

Antes de escrever um mock, duas perguntas. Consigo pôr esta lógica numa função que não sabe de onde vêm os dados? Se sim, o mock desaparece com o problema. Se não, a coisa a sério é assim tão cara? Muitas vezes não é — e um contentor que arranca em três segundos vale mais do que uma suposição que ninguém revê.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.