Estratégia e Melhores Práticas de Teste
Escolha os tipos de teste corretos, defina metas de cobertura que importam, construa um pipeline de CI e elimine testes instáveis.
Busque em todas as páginas da documentação
Escolha os tipos de teste corretos, defina metas de cobertura que importam, construa um pipeline de CI e elimine testes instáveis.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida -- pronto para copiar e colar.
Testing Trophy (modelo de Kent C. Dodds para aplicativos React):
┌──────────┐
│ E2E │ Poucos caminhos críticos
┌─┴──────────┴─┐
│ Integração │ A maioria dos testes vive aqui
┌─┴──────────────┴─┐
│ Testes Unitários │ Funções utilitárias, hooks
┌─┴──────────────────┴─┐
│ Análise Estática │ TypeScript, ESLint
└──────────────────────┘
# Test strategy decision matrix
What to test:
- User-visible behavior (renders, interactions, navigation)
- Business logic (calculations, validation, state transitions)
- Edge cases (empty states, errors, loading, boundary values)
- Regression bugs (write a test for every bug fix)
What NOT to test:
- Implementation details (internal state, private methods)
- Third-party library internals
- CSS styling (unless visual regression matters)
- Trivial code (simple prop passthrough components)Quando usar isso: Antes de escrever quaisquer testes -- planeje sua estratégia de teste com base no tamanho do projeto, equipe e tolerância a riscos.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# Stage 1: Fast checks (run on every push)
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
# Stage 2: Unit and integration tests (run on every push)
unit-tests:
runs-on: ubuntu-latest
needs: lint-and-typecheck
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run test:run -- --coverage
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
# Stage 3: E2E tests (run on PRs to main and merges)
e2e-tests:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Build application
run: npm run build
- name: Run E2E tests
run: npx playwright test --project=chromium
env:
CI: true
- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 7
# Stage 4: Deploy (only on main)
deploy:
runs-on: ubuntu-latest
needs: [unit-tests, e2e-tests]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: echo "Deploy step here"// package.json scripts
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}O que isso demonstra:
| Tipo de Teste | Velocidade | Confiança | Usar Para |
|---|---|---|---|
| TypeScript | instantâneo | captura erros de tipo | Todo o código |
| Unitário | rápido (menos de 10ms cada) | correção da lógica | Funções puras, hooks, utilitários, stores Zustand |
| Integração | médio (menos de 100ms cada) | comportamento do componente | Formulários, listas, componentes interativos |
| E2E | lento (segundos cada) | sistema completo funciona | Fluxos de login, checkout, caminhos críticos |
// vitest.config.ts -- practical coverage thresholds
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "lcov"],
thresholds: {
// Set meaningful thresholds, not 100%
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
include: ["src/**/*.{ts,tsx}"],
exclude: [
"src/**/*.test.{ts,tsx}",
"src/**/*.stories.{ts,tsx}",
"src/**/*.d.ts",
"src/app/**/layout.tsx",
"src/app/**/loading.tsx",
"src/app/**/not-found.tsx",
],
},
},
});Quando a cobertura mente:
// Pattern: describe(unit) > it(behavior)
describe("CartStore", () => {
it("adds item to empty cart");
it("increments quantity for duplicate items");
it("removes item by id");
it("calculates total with mixed quantities");
});
// Pattern: should/when for complex behaviors
describe("CheckoutForm", () => {
it("submits order when all fields are valid");
it("shows validation error when email is missing");
it("disables submit button while processing");
});
// Avoid:
it("works"); // too vague
it("test addItem"); // do not start with "test"
it("should call setCount with count + 1"); // tests implementation| Sintoma | Causa | Correção |
|---|---|---|
| Teste passa localmente, falha em CI | Diferenças de tempo, variáveis de ambiente ausentes | Use waitFor em vez de atrasos fixos; verifique a configuração de ambiente |
| Teste falha intermitentemente | Condições de corrida em código assíncrono | Adicione asserções adequadas com retentativa automática |
| Teste falha apenas na primeira execução | Estado do teste anterior vazando | Reinicie o estado em beforeEach; use contextos de navegador isolados |
| Teste falha com "elemento não encontrado" | Elemento ainda não renderizado | Use consultas findBy ou waitFor |
| Teste de captura de tela falha | Diferenças de renderização de fonte entre sistemas operacionais | Execute testes visuais em Docker ou limite a uma plataforma |
CI Mínima para projetos pequenos:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test:runHooks de pré-commit para feedback rápido:
// package.json (com lint-staged)
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "vitest related --run"]
}
}// TypeScript IS a testing tool -- strict mode catches bugs before tests
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
// These TypeScript errors replace entire categories of tests:
// - null/undefined access tests (strictNullChecks)
// - wrong argument type tests (strict function types)
// - missing property tests (exact optional property types)Testar detalhes de implementação -- Afirmar sobre estado interno, classes CSS ou contagem de chamadas de mock acopla testes à estrutura do código. Correção: Teste o que o usuário vê e faz. Se o usuário não pode observar, o teste provavelmente não deve afirmar sobre isso.
Muitos testes E2E -- Testes E2E são lentos, caros de manter e propensos a instabilidade. Correção: Cubra caminhos críticos (login, checkout, cadastro) com E2E. Use testes de integração para todo o resto.
Perseguir 100% de cobertura -- Equipes gastam esforço desproporcional testando código trivial para atingir metas arbitrárias. Correção: Defina limites de cobertura em 75-85%. Concentre o esforço manual em testar código crítico para os negócios.
Não executar testes em CI -- Testes que rodam apenas localmente eventualmente quebram sem que ninguém perceba. Correção: Configure CI desde o primeiro dia. Bloqueie merges em falhas de teste.
Ignorar testes instáveis -- Marcar testes instáveis como skip esconde bugs reais. Correção: Corrija a causa raiz (geralmente tempo ou vazamento de estado). Quarentene testes instáveis em um job separado, se necessário.
Testar bibliotecas de terceiros -- Escrever testes que verificam se sua biblioteca de UI renderiza um dropdown corretamente desperdiça tempo. Correção: Confie que as bibliotecas são testadas. Teste seu uso delas (você passa as props corretas, lida com callbacks).
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Testing Trophy | Você constrói aplicativos React com muita integração de componentes | Você constrói uma biblioteca utilitária (então testes unitários dominam) |
| Pirâmide de Testes | Você tem um aplicativo tradicional com backend pesado e UI fina | Seu aplicativo é principalmente frontend com chamadas de API |
| Sem E2E, apenas integração | Projeto pequeno, iteração rápida, sem fluxos de pagamento críticos | Você lida com dinheiro, autenticação ou dados sensíveis |
| Testes de Contrato (Pact) | Microsserviços com muitas equipes e consumidores de API | Monólito ou equipe única |
Defina limites em 75-85% para linhas, funções e branches. Não persiga 100% -- a cobertura mede quais linhas foram executadas, não quais asserções são significativas.
Não. Um teste que renderiza um componente e não faz nenhuma asserção ainda aumenta a cobertura. A cobertura mede a execução de linhas, não a verificação de comportamento. Concentre-se em testar caminhos críticos e casos extremos.
Use describe(unidade) > it(comportamento):
describe("CartStore", () => {
it("adds item to empty cart");
it("increments quantity for duplicate items");
});Evite nomes vagos como "funciona" ou nomes de detalhes de implementação.
waitFor em vez de atrasos fixos.beforeEach.findBy.skip sem corrigir a causa raiz.Afirmar sobre estado interno, classes CSS ou contagem de chamadas de mock acopla testes à estrutura do código. Ao refatorar, os testes quebram mesmo que o comportamento não tenha mudado. Teste o que o usuário vê em vez disso.
Com strict: true, o TypeScript captura:
Falhe rápido -- execute verificações baratas primeiro.
Sim. Escrever um teste de regressão para cada bug garante que ele nunca retorne. Esta é uma das práticas de teste de maior valor.
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "vitest related --run"]
}
}Isso executa apenas os testes relacionados aos arquivos alterados antes de cada commit.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥