ConstructiCat Logo
CodeBust.
Browse section ▾

Teste Ávido.

Um Teste Ávido verifica vários métodos ou comportamentos distintos da unidade sob teste em um único método de teste, em vez de focar em um único comportamento.

##Signs and Symptoms

Um único método de teste exercita muitos métodos de produção não relacionados e faz asserções sobre cada um, conduzindo o objeto por toda uma sequência de operações em vez de verificar um único resultado.

Sinais reveladores:

  • Um nome de teste vago e genérico (testUserService, it('works')) ou um nome costurado com "and" (creates_and_renames_and_deletes).
  • Vários ciclos de act → assert em um único corpo: você chama um método, faz a asserção, chama outro, faz a asserção de novo.
  • Comentários usados como cabeçalhos de seção dentro do teste (// agora testa o delete) para separar as coisas que estão sendo verificadas.
  • As asserções tocam vários métodos/campos diferentes do SUT que não compartilham um único resultado lógico.
// SMELL: um teste verifica create, rename, delete e list
test('user service', () => {
  const svc = new UserService();

  const user = svc.create({ name: 'Ada' });   // comportamento 1
  expect(user.id).toBeDefined();

  svc.rename(user.id, 'Grace');                // comportamento 2
  expect(svc.get(user.id).name).toBe('Grace');

  svc.delete(user.id);                         // comportamento 3
  expect(svc.get(user.id)).toBeUndefined();

  expect(svc.list()).toHaveLength(0);          // comportamento 4
});

Note a distinção: um Teste Ávido tem a ver com testar múltiplos comportamentos, e não meramente com ter vários expects. Várias asserções sobre um único resultado lógico (por exemplo, verificar vários campos do mesmo objeto retornado) é aceitável e não é este sintoma.

##Reasons for the Problem

Por que acontece

  • Reutilização de setup / preguiça. Preparar o fixture é tedioso ou caro, então é tentador continuar cutucando o objeto já construído e fazer asserções "já que estamos aqui."
  • Pensamento de fluxo de trabalho. O autor testa uma jornada do usuário ("criar, depois editar, depois excluir") como um único script em vez de isolar cada comportamento unitário.
  • Desvio no TDD. Um teste que começou focado acumula asserções extras à medida que novas funcionalidades são acrescentadas em vez de receberem seu próprio teste.

Por que é prejudicial

  • Falsa confiança (o mais grave). A maioria das bibliotecas de asserção interrompe o teste na primeira asserção que falha. Se create quebrar, as verificações de rename, delete e list nunca são executadas — então uma mudança de verde para vermelho esconde quantos comportamentos estão de fato quebrados, e um teste que passava nunca exercitou de verdade os passos posteriores quando um passo anterior regrediu.
  • Diagnóstico ruim. Uma falha diz a você "o teste do serviço de usuário falhou", não qual comportamento. Você precisa ler o método inteiro para localizar o passo quebrado.
  • Intenção obscurecida / legibilidade. O teste deixa de documentar um único fato sobre o sistema; quem lê precisa segmentá-lo mentalmente nos comportamentos que ele agrupa.
  • Fragilidade e acoplamento. As asserções posteriores dependem de mutações anteriores, então uma mudança não relacionada perto do início se propaga em cascata e torna o teste frágil e difícil de refatorar.
  • Manutenibilidade. É mais difícil excluir, mover ou renomear a cobertura de um comportamento quando ela está entrelaçada com outras três em um único método.

##Treatment

Divida o teste ávido em vários testes focados — um comportamento por teste — e eleve o passo de arrange compartilhado para o setup, de modo que a divisão não duplique código repetitivo.

Passos:

  1. Liste os comportamentos que o teste agrupa (aqui: create, rename, delete, list após o delete).
  2. Extraia um teste por comportamento, cada um com um nome descritivo que revele a intenção.
  3. Mova o setup compartilhado para um beforeEach ou um método de fábrica/Creation Method, de modo que cada teste ainda tenha um SUT limpo sem código de arrange copiado e colado.
  4. Mantenha um único Act por teste e faça asserções apenas sobre o resultado daquele comportamento (vários expects sobre o mesmo resultado são aceitáveis).
  5. Se você realmente precisar validar um fluxo de trabalho de ponta a ponta, mantenha isso como um único teste de cenário/integração explicitamente nomeado — mas ainda assim cubra cada comportamento unitário em seu próprio teste, em vez de depender do fluxo de trabalho para a cobertura.
  6. Proteja-se contra regressões habilitando uma regra de máximo de asserções (veja os detectores) como uma aproximação barata.
// DEPOIS: testes focados, setup compartilhado
let svc;
beforeEach(() => { svc = new UserService(); });

test('create() assigns an id', () => {
  expect(svc.create({ name: 'Ada' }).id).toBeDefined();
});

test('rename() updates the stored name', () => {
  const { id } = svc.create({ name: 'Ada' });
  svc.rename(id, 'Grace');
  expect(svc.get(id).name).toBe('Grace');
});

test('delete() removes the user', () => {
  const { id } = svc.create({ name: 'Ada' });
  svc.delete(id);
  expect(svc.get(id)).toBeUndefined();
});

Agora qualquer comportamento isolado pode falhar de forma independente, o nome do teste que falha aponta exatamente a quebra, e cada teste se lê como um fato documentado sobre o SUT.

##Detected by

  • eslint-jest jest/max-expectsImponha um número máximo de chamadas expect() por teste
  • eslint-vitest vitest/max-expectsImponha um número máximo de expect por teste