Engenharia de IA Aplicada à Programação Web
Aula 6 de 6
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 3.1 Introdução ao TypeScript
  2. 3.2 Interfaces, aliases e contratos
  3. 3.3 Unions, narrowing e generics
  4. 3.4 Configuração, lint e formatação
  5. 3.5 Git, branches e pull requests
  6. 3.6 Testes, documentação e projeto

Módulo 3 · Aula 3.6

Testes, documentação e projeto final

2 horas Oficina integradora TypeScript, Git e qualidade
Ver fonte Markdown

Identificação

Introdução

O que é um teste de software?

Um teste executa uma parte do sistema sob condições conhecidas e compara o resultado observado com o resultado esperado.

const result = calculateCompletionRate(2, 4);
assert.equal(result, 50);

O teste registra uma expectativa verificável. Se o resultado mudar para 40, a verificação falha e mostra onde investigar.

Testar é somente abrir a página e clicar?

Clicar manualmente é uma forma de teste e continua sendo útil. Porém, um teste automatizado pode repetir os mesmos cenários rapidamente, de forma consistente e sem depender da memória de quem testa.

Os dois se complementam:

  • teste automatizado verifica regras repetíveis;
  • teste manual e exploratório encontra comportamentos inesperados e problemas de experiência;
  • revisão visual verifica layout, legibilidade e adaptação a telas.

Um teste aprovado prova que não existem bugs?

Não. Ele prova somente que os cenários descritos produziram o resultado esperado naquele ambiente. Se um caso importante não foi escrito, a suíte não o verifica.

Cobertura alta também não é garantia de qualidade. É possível executar muitas linhas sem avaliar corretamente o comportamento.

O que é documentação?

Documentação é informação que permite entender, executar, utilizar, manter e justificar o projeto. Para esta etapa, o principal documento será o README.md.

Um README não deve dizer apenas “projeto de tarefas”. Ele deve responder:

  • qual problema o projeto resolve;
  • quais tecnologias utiliza;
  • como instalar e executar;
  • como rodar verificações e testes;
  • como a estrutura está organizada;
  • quais decisões e limitações existem.

README é documentação do código?

É uma parte. Comentários, nomes, tipos, contratos, diagramas, decisões de arquitetura e documentação de API também comunicam o sistema. O README funciona como porta de entrada.

O que é um projeto reproduzível?

É um projeto que outra pessoa consegue preparar e executar seguindo instruções registradas, usando versões e comandos previsíveis.

npm ci
npm run quality
npm run build

Se somente o autor sabe quais passos secretos executar, a entrega ainda não está completa.

Onde os testes rodam?

Podem rodar:

  • na máquina do desenvolvedor;
  • pelo editor;
  • antes de um commit;
  • no fluxo de integração contínua;
  • durante a compilação;
  • em um navegador, para testes de interface.

Nesta aula veremos testes unitários de regras TypeScript e um laboratório visual no navegador.

Como isso fecha o módulo 3?

As aulas anteriores produziram tipos, contratos, unions, configuração, qualidade e fluxo Git. Agora vamos reuni-los em uma entrega que outra pessoa consegue instalar, verificar, revisar e continuar.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar o propósito de um teste;
  2. diferenciar testes manual e automatizado;
  3. diferenciar unidade, integração e E2E;
  4. estruturar testes com Arrange, Act e Assert;
  5. testar casos felizes, limites e falhas;
  6. testar comportamento sem acoplar à implementação;
  7. interpretar uma falha;
  8. explicar limites de cobertura e mocks;
  9. organizar arquivos de teste;
  10. escrever um README reproduzível;
  11. documentar decisões e limitações;
  12. entregar o projeto final do módulo.

Pré-requisitos

  • Aulas 3.1 a 3.5 concluídas;
  • funções, módulos, TypeScript estrito e scripts npm;
  • noções de Git, branches e pull requests.

Pergunta orientadora

Como entregar um projeto que outra pessoa consiga compreender, executar, verificar e modificar com segurança?

Roteiro sugerido

Etapa Duração
Fundamentos de testes 25 min
Testes unitários TypeScript 30 min
Estratégia e interpretação 20 min
Documentação reproduzível 20 min
Laboratório e entrega final 20 min
Revisão 5 min

1. Pirâmide de testes

Uma estratégia comum organiza:

  • muitos testes unitários rápidos;
  • alguns testes de integração;
  • poucos testes E2E para fluxos essenciais.

Não é uma lei matemática. A distribuição depende dos riscos, arquitetura e custo de manutenção.

2. Teste unitário

Verifica uma unidade pequena e controlável, normalmente uma função ou regra:

export function calculateCompletionRate(
  completed: number,
  total: number,
): number {
  if (total === 0) return 0;
  return Math.round((completed / total) * 100);
}

Esse código pode ser testado sem navegador, banco ou rede.

3. Teste de integração

Verifica componentes trabalhando juntos. Exemplos:

  • serviço e repositório;
  • controller e validação;
  • aplicação e MySQL de teste;
  • cliente HTTP e API controlada.

Uma integração real pode exigir preparação e limpeza de dados.

4. Teste E2E

End-to-end verifica um fluxo pela interface externa do sistema:

usuário entra → cria tarefa → filtra → vê resultado

Ele oferece confiança no fluxo integrado, mas costuma ser mais lento e sensível ao ambiente.

5. Arrange, Act, Assert

test("calcula 50% de conclusão", () => {
  // Arrange
  const completed = 2;
  const total = 4;

  // Act
  const result = calculateCompletionRate(completed, total);

  // Assert
  assert.equal(result, 50);
});
  • Arrange prepara;
  • Act executa;
  • Assert compara.

Separar mentalmente essas etapas melhora a leitura.

6. Nome do teste

Um nome útil descreve comportamento e condição:

retorna zero quando não existem tarefas
rejeita total negativo
preserva somente tarefas concluídas

Evite teste 1, funciona ou nomes que apenas repetem a função.

7. Caso feliz

É o uso válido e esperado:

assert.equal(calculateCompletionRate(3, 4), 75);

Ele é necessário, mas raramente suficiente.

8. Casos de limite

Valores nas bordas revelam erros:

  • array vazio;
  • zero;
  • um item;
  • string vazia;
  • limite máximo;
  • data exatamente no vencimento.
assert.equal(calculateCompletionRate(0, 0), 0);

9. Casos inválidos

O contrato deve decidir o que acontece:

assert.throws(
  () => calculateCompletionRate(-1, 3),
  /não pode ser negativo/,
);

Não escreva apenas um teste de erro; teste também a mensagem ou categoria quando ela faz parte do contrato público.

10. Testar comportamento

Prefira testar a saída pública:

assert.deepEqual(filterTasks(tasks, "done"), [doneTask]);

Evite afirmar detalhes internos como quantidade de loops ou nomes de variáveis. Uma refatoração correta não deveria quebrar testes de comportamento.

11. Testes determinísticos

O mesmo teste deve produzir o mesmo resultado nas mesmas condições. Fontes comuns de instabilidade:

  • hora atual;
  • aleatoriedade;
  • rede externa;
  • ordem não garantida;
  • dados compartilhados;
  • concorrência sem controle.

Injete relógio, gerador ou adaptador quando precisar controlar essas dependências.

12. Testes independentes

Um teste não deve depender da execução anterior. Cada caso prepara seus próprios dados e limpa recursos quando necessário.

Se mudar a ordem altera o resultado, existe estado compartilhado indevido.

13. Node Test Runner

O Node possui um executor de testes integrado:

import test from "node:test";
import assert from "node:assert/strict";

test("retorna zero para coleção vazia", () => {
  assert.equal(calculateCompletionRate(0, 0), 0);
});

Com JavaScript executável:

node --test

Em um projeto TypeScript, é necessário compilar ou usar uma configuração de ambiente de execução compatível com TypeScript.

14. Organização dos arquivos

Alternativas:

src/task-rules.ts
src/task-rules.test.ts

ou:

src/task-rules.ts
tests/task-rules.test.ts

Escolha uma convenção e configure tsconfig, lint e executor de forma coerente.

15. Testes parametrizados

const cases = [
  { completed: 1, total: 4, expected: 25 },
  { completed: 2, total: 4, expected: 50 },
  { completed: 4, total: 4, expected: 100 },
];

for (const item of cases) {
  test(`${item.completed}/${item.total}`, () => {
    assert.equal(
      calculateCompletionRate(item.completed, item.total),
      item.expected,
    );
  });
}

São úteis quando a mesma regra precisa ser verificada com várias entradas.

16. Teste assíncrono

test("carrega tarefas", async () => {
  const tasks = await service.list();
  assert.equal(tasks.length, 2);
});

Retorne ou aguarde a Promise. Esquecer await pode fazer o teste terminar antes da verificação.

17. Dublês de teste

Termos comuns:

  • stub: fornece resposta controlada;
  • spy: registra chamadas;
  • mock: simula interação esperada;
  • fake: implementação simplificada funcional.

Os nomes variam entre ferramentas. O mais importante é saber qual dependência está sendo substituída e por quê.

18. Quando usar mock

Use para controlar uma fronteira difícil, lenta ou externa, como pagamento, e-mail ou modelo de IA. Não faça mock de toda função interna; isso cria testes que repetem a implementação sem provar o comportamento integrado.

19. Cobertura

Cobertura mede quais partes foram executadas pela suíte. Ela ajuda a encontrar áreas sem exercício, mas não mede a qualidade das afirmações.

Um teste sem assert pode executar 100% da função e provar quase nada.

20. Falha de teste

Ao ler uma falha, identifique:

  1. nome do teste;
  2. valor esperado;
  3. valor recebido;
  4. stack trace;
  5. primeira linha do projeto envolvida;
  6. se código ou expectativa está incorreto.

Não altere o valor esperado apenas para deixar a suíte verde.

21. Regressão

Quando um defeito é corrigido:

  1. crie um teste que reproduz a falha;
  2. confirme que ele falha;
  3. corrija o código;
  4. confirme que passa;
  5. mantenha o teste para evitar retorno do problema.

22. Testes e TypeScript

O compilador verifica contratos estáticos; testes verificam exemplos de comportamento em ambiente de execução. Precisamos dos dois:

tsc --noEmit → tipos
node --test   → comportamento

TypeScript não prova que calculateCompletionRate(2, 4) retorna 50.

23. Porta de qualidade final

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint .",
    "format:check": "prettier . --check",
    "test": "node --test dist/**/*.test.js",
    "build": "tsc",
    "quality": "npm run typecheck && npm run lint && npm run format:check && npm test"
  }
}

O comando exato depende da saída e da versão do Node.

24. O README como porta de entrada

Estrutura recomendada:

# Nome do projeto

## Problema e objetivo
## Funcionalidades
## Tecnologias
## Pré-requisitos
## Instalação
## Execução
## Testes e qualidade
## Estrutura
## Decisões técnicas
## Limitações
## Licença

Nem todo projeto precisa das mesmas seções, mas instalação e execução não devem ficar implícitas.

25. Pré-requisitos claros

Exemplo:

- Node.js 22 ou superior;
- npm 10 ou superior;
- navegador atualizado.

Não escreva apenas “ter Node”. Versões incompatíveis podem gerar erros diferentes.

26. Comandos copiáveis

npm ci
npm run build
npm test

Informe de qual diretório executar e quais arquivos de ambiente preparar. Não inclua credenciais reais.

27. Variáveis de ambiente

Documente nomes em .env.example:

API_BASE_URL=http://localhost:3000

Explique finalidade e obrigatoriedade. Nunca coloque tokens reais no README ou no arquivo de exemplo.

28. Decisões e limitações

Exemplo:

## Decisões técnicas

- TypeScript estrito para contratos mais precisos;
- regras de domínio separadas do DOM para facilitar testes.

## Limitações

- dados ainda não possuem persistência no servidor;
- autenticação será adicionada em módulo posterior.

Limitação documentada é diferente de defeito escondido.

29. Documentação que envelhece

README incorreto pode ser pior que ausência de instrução. Quando scripts, versões ou estrutura mudarem, atualizar a documentação na mesma entrega.

Comandos do README devem ser testados em ambiente limpo sempre que possível.

30. Laboratório guiado

Abra o painel de entrega.

Etapa 1 — Execute a suíte

Alterne entre implementação correta e implementação com regressão. Observe qual teste identifica o problema.

Etapa 2 — Leia cada caso

Relacione Arrange, Act e Assert. Identifique caso feliz, limite e entrada inválida.

Etapa 3 — Audite o README

Use o README completo e depois remova uma seção. O painel deve explicar o que falta, sem avaliar apenas o tamanho do texto.

Etapa 4 — Verifique a entrega

Uma entrega fica pronta apenas quando testes e documentação atendem aos critérios.

31. Projeto final do módulo

Migrar o painel de tarefas para TypeScript e entregar:

  • entidades e estados tipados;
  • validação de dados externos;
  • TypeScript estrito;
  • módulos de regras separados da interface;
  • testes de casos felizes, limites e falhas;
  • lint e formatação;
  • porta de qualidade;
  • branch de funcionalidade e commits coerentes;
  • pull request simulado ou real;
  • README reproduzível;
  • demonstração de cinco minutos.

32. Estrutura sugerida do projeto

painel-typescript/
├── src/
│   ├── domain/
│   ├── services/
│   ├── ui/
│   └── app.ts
├── tests/
├── dist/
├── .gitignore
├── eslint.config.js
├── package.json
├── package-lock.json
├── README.md
└── tsconfig.json

33. Evidências da entrega

  • captura ou registro da porta de qualidade;
  • histórico de commits;
  • branch utilizada;
  • descrição do pull request;
  • lista de testes;
  • instruções reproduzidas por outra pessoa;
  • demonstração do fluxo principal;
  • explicação de uma decisão técnica.

34. Erros comuns

Testar somente o caso feliz

Arrays vazios, limites e entradas inválidas ficam sem proteção.

Testar implementação interna

Refatorações corretas quebram a suíte sem alterar comportamento.

Criar mocks demais

O teste passa mesmo que os componentes reais não funcionem juntos.

Perseguir cobertura sem asserts relevantes

Linhas executadas não significam comportamento validado.

README que só funciona na máquina do autor

Faltam versões, variáveis, diretório ou etapas.

Documentação diferente dos scripts

O leitor copia um comando que não existe mais.

Desativar o teste com falha

A causa permanece escondida. Investigue antes de pular.

35. Boas práticas

  • teste comportamento público;
  • inclua casos de limite;
  • mantenha testes determinísticos e independentes;
  • nomeie casos por comportamento;
  • use dublês somente em fronteiras justificadas;
  • execute testes durante o desenvolvimento;
  • transforme defeitos em testes de regressão;
  • mantenha README e scripts sincronizados;
  • documente decisões e limitações;
  • peça para outra pessoa seguir as instruções.

36. Exercícios

Exercício 1 — AAA

Escreva um teste com Arrange, Act e Assert para uma função de prioridade.

Exercício 2 — Limites

Crie três casos para array vazio, um item e muitos itens.

Exercício 3 — Regressão

Introduza um defeito controlado e escreva o teste que o encontra.

Exercício 4 — README

Entregue o projeto a um colega usando apenas o README.

Exercício 5 — Evidências

Registre o resultado de npm run quality sem incluir dados sensíveis.

37. Lista de verificação de conclusão

  • Diferencio teste manual e automatizado.
  • Diferencio unidade, integração e E2E.
  • Estruturo Arrange, Act e Assert.
  • Testo caso feliz, limite e falha.
  • Testo comportamento público.
  • Entendo limites de mocks e cobertura.
  • Interpreto falhas sem alterar expectativas arbitrariamente.
  • Escrevo README reproduzível.
  • Documento decisões e limitações.
  • Executo a porta de qualidade antes da entrega.

38. Critérios de avaliação

Critério Pontos
Modelagem TypeScript 20
Testes e cenários 25
Configuração e qualidade 15
Organização do projeto 10
Fluxo Git e revisão 10
README reproduzível 15
Demonstração e justificativa 5
Total 100

39. Critérios de aceite

  • pelo menos 70 pontos;
  • projeto executável seguindo o README;
  • TypeScript estrito sem any usado para esconder erros;
  • porta de qualidade aprovada;
  • regras essenciais testadas;
  • nenhum segredo versionado;
  • branch e commits demonstráveis;
  • limitações conhecidas documentadas.

Resumo

Nesta aula, aprendemos que:

  • testes transformam expectativas em verificações repetíveis;
  • teste aprovado não prova ausência de todos os bugs;
  • unidade, integração e E2E protegem riscos diferentes;
  • AAA organiza a intenção do caso;
  • limites e falhas importam tanto quanto o caso feliz;
  • cobertura é um indicador, não um objetivo isolado;
  • README é a porta de entrada da entrega;
  • projeto reproduzível não depende de passos secretos;
  • código, testes, configuração, Git e documentação formam uma única entrega.

Próximo módulo

No Módulo 4, iniciaremos Angular e transformaremos as regras e contratos construídos até aqui em uma aplicação front-end organizada por componentes, serviços e rotas.