Assertion Roulette.
Um teste empacota muitas asserções não documentadas em um único método, então, quando ele falha, você não consegue saber qual asserção disparou nem por quê — é preciso apostar.
##Signs and Symptoms
Um único teste contém uma sequência de asserções nuas, sem mensagens explicativas e sem um objetivo único claro. Quando ele fica vermelho, o relatório de falha (especialmente uma linha de resumo de CI ou um helper de asserção compartilhado) diz que algo quebrou, mas não qual verificação nem por quê — você joga na "roleta" para achar o culpado.
Sinais reveladores:
- Muitas chamadas
expect(...)/assert(...)em um único teste, nenhuma carregando uma mensagem ou rótulo descritivo. - O nome do teste é genérico (
'works','user profile','test register'), então uma falha não comunica nada por si só. - As asserções abrangem várias preocupações não relacionadas (um Eager Test reutilizando uma única fixture para verificar tudo de uma vez).
- Uma falha faz você recorrer ao depurador ou aos números de linha para descobrir o que estava de fato sendo verificado.
- Como a maioria das asserções é fail-fast, a primeira falha interrompe o restante, então você nunca vê as verificações posteriores em uma única execução.
test('user profile', () => {
const user = createUser({ name: 'Ada', age: 36 });
expect(user.name).toBe('Ada');
expect(user.age).toBe(36);
expect(user.isAdult).toBe(true); // se ESTA for a vermelha,
expect(user.slug).toBe('ada'); // o relatório apenas diz
expect(user.roles).toContain('member'); // "expected false to be true"
expect(user.createdAt).toBeInstanceOf(Date);
});
Observação: executores modernos (Jest, Vitest) imprimem a linha que falhou e um diff, o que ameniza o problema do "qual linha?". O smell ainda incomoda quando o teste mistura objetivos, tem um nome vago, itera sobre asserções em laço, as esconde atrás de helpers compartilhados ou roda em um ambiente onde só uma mensagem de resumo sobrevive.
##Reasons for the Problem
Por que acontece
- Eager Test / reúso de fixture. Uma configuração custosa tenta você a acumular verificações do tipo "já que estou aqui" em vez de escrever um segundo teste.
- Verificação de objeto inteiro feita do jeito difícil. Verificar um objeto campo a campo naturalmente produz uma longa sequência de asserções não documentadas.
- Crescimento por copiar e colar. Os testes acumulam asserções ao longo do tempo sem que ninguém os divida.
- Ferramental histórico. As asserções clássicas do xUnit reportavam apenas passou/falhou, então uma asserção sem rótulo não dava contexto algum na falha — a origem do smell original de Meszaros.
- Pressão de prazo. Um teste grande parece mais rápido de escrever do que vários testes focados.
Por que prejudica
- Diagnosticabilidade / confiabilidade. Segundo o testsmells.org, "múltiplas instruções de asserção em um método de teste sem uma mensagem descritiva impactam a legibilidade/compreensibilidade/manutenibilidade, pois não é possível entender o motivo da falha." Você gasta tempo localizando qual asserção disparou.
- Defeitos ocultos (falsa confiança). Asserções fail-fast param na primeira falha, então as asserções posteriores nunca são executadas. Você corrige uma, roda de novo, encontra a próxima — vários bugs ficam mascarados, e um "verde depois de uma correção" parece mais seguro do que de fato é.
- Legibilidade. O teste deixa de documentar um único comportamento; sua intenção fica enterrada em uma lista, e um nome de teste genérico não acrescenta nada.
- Manutenibilidade. Não fica claro se uma nova verificação pertence a este teste ou a um novo, então a pilha continua crescendo e preocupações não relacionadas acabam acopladas.
##Treatment
Busque o princípio por trás de um Single-Condition Test: cada teste verifica um comportamento e pode falhar por exatamente um motivo.
- Divida por objetivo. Quebre o teste abrangente em testes focados com nomes descritivos. O nome então é a mensagem de falha.
- Condense verificações campo a campo em uma única asserção. Use
toEqual/expect.objectContaining/ um snapshot revisado para que N asserções virem um único diff significativo. - Quando o agrupamento for genuinamente coeso, rotule as asserções. O
expectdo Jest/Vitest não tem argumento de mensagem, então use o parâmetro de mensagem donode:assert, oexpect(actual, message)do Vitest ou ojest-expect-message. - Quer que toda verificação rode e reporte em conjunto? Prefira dividir; caso contrário, use asserções leves (
expect.softno Vitest) para que uma falha não esconda as demais. - Proteja-se contra regressões limitando o número de asserções por teste (veja os detectores).
Antes — assertion roulette:
test('register user', () => {
const res = register({ email: 'a@b.com', age: 36 });
expect(res.ok).toBe(true);
expect(res.user.email).toBe('a@b.com');
expect(res.user.isAdult).toBe(true);
expect(res.welcomeEmailSent).toBe(true);
});
Depois — um comportamento por teste, com uma asserção de objeto inteiro:
describe('register', () => {
it('accepts a valid adult signup', () => {
expect(register({ email: 'a@b.com', age: 36 }).ok).toBe(true);
});
it('stores the normalized user record', () => {
const { user } = register({ email: 'a@b.com', age: 36 });
expect(user).toEqual(
expect.objectContaining({ email: 'a@b.com', isAdult: true }),
);
});
it('sends a welcome email on signup', () => {
expect(register({ email: 'a@b.com', age: 36 }).welcomeEmailSent).toBe(true);
});
});
Se você precisar manter um único teste, faça cada asserção ser autoexplicativa:
import { strict as assert } from 'node:assert';
assert.equal(res.ok, true, 'registration should succeed');
assert.equal(res.welcomeEmailSent, true, 'welcome email should be sent');
##Detected by
- eslint-jest jest/max-expects — Sinaliza o indicador baseado em contagem do Assertion Roulette: reporta quando um teste excede N chamadas expect() (padrão 5). A documentação observa que mais asserções tendem a misturar múltiplos objetivos.
- eslint-vitest vitest/max-expects — Equivalente no Vitest: impõe um número máximo de asserções expect() por teste, capturando testes que acumulam verificações em excesso.
- sonar java:S5961 — SonarSource 'Métodos de teste não devem conter asserções demais' — limita as asserções por teste (padrão 25 para JUnit/AssertJ), o indicador padrão de análise estática para esse smell.