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
createquebrar, as verificações derename,deleteelistnunca 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:
- Liste os comportamentos que o teste agrupa (aqui: create, rename, delete, list após o delete).
- Extraia um teste por comportamento, cada um com um nome descritivo que revele a intenção.
- Mova o setup compartilhado para um
beforeEachou 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. - 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). - 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.
- 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-expects — Imponha um número máximo de chamadas expect() por teste
- eslint-vitest vitest/max-expects — Imponha um número máximo de expect por teste