Engenharia de Software

Testes de Software

Professor: Gabriel Soares Baptista

Por que essa aula importa?

Quando uma equipe deixa os testes para o fim, vários problemas aparecem:

  • o código já cresceu
  • muitas partes já dependem umas das outras
  • corrigir fica mais caro
  • descobrir a origem da falha fica mais difícil

O ponto de partida do Sommerville

Segundo Sommerville, testar tem dois objetivos principais:

  1. mostrar que o software faz o que deveria fazer
  1. revelar comportamentos incorretos, indesejados ou diferentes do especificado

A pergunta central

Reflita

Se uma função passa em todos os testes unitários, isso já prova que o sistema inteiro está pronto para entrega?

Resposta curta

Não.

Teste unitário mostra apenas que uma unidade pequena se comportou bem nos cenários cobertos.

Ainda faltam, por exemplo:

  • integração entre partes
  • comportamento do sistema completo
  • requisitos não funcionais
  • critérios de aceitação do cliente

O que cada nível de teste responde

Nível Pergunta principal
Unitário Esta função, método ou classe pequena funciona?
Integração / componente Essas partes funcionam bem juntas?
Sistema O sistema completo se comporta como esperado?
Release Esta versão está pronta para sair da equipe?
Aceitação O cliente considera o sistema adequado para uso real?

Testes de desenvolvimento

No capítulo 8, Sommerville organiza os testes de desenvolvimento em três níveis:

  1. Teste unitário
  1. Teste de componente ou integração
  1. Teste de sistema

Onde está o foco desta aula?

Hoje vamos focar em três ideias:

  • teste unitário
  • TDD
  • teste de aceitação

Passo 1. Entender o teste unitário

Teste unitário é o teste de uma unidade pequena e isolada.

Exemplos:

  • função
  • método
  • classe pequena

Por que o teste unitário é útil?

  • é rápido
  • é barato de executar
  • ajuda a encontrar defeitos cedo
  • isola regras pequenas de negócio

O que o teste unitário não resolve sozinho

Passar em testes unitários não garante:

  • que a API conversa certo com o banco
  • que módulos integrados trocam dados corretamente
  • que a interface apresenta o resultado certo
  • que o sistema está pronto para aceite contratual

Estrutura básica de um teste automatizado

Sommerville resume um teste automatizado em três partes:

  1. configuração
  1. chamada
  1. afirmação

Exemplo 1: a função

Passo 1. Escreva uma regra simples.

def calcular_desconto(valor: float, percentual: float) -> float:
    if valor < 0:
        raise ValueError("valor nao pode ser negativo")
    if percentual < 0 or percentual > 100:
        raise ValueError("percentual invalido")
    return valor * (percentual / 100)

Exemplo 1: o primeiro teste

Passo 2. Teste o comportamento normal.

from desconto import calcular_desconto

def test_calcula_desconto_de_10_porcento():
    assert calcular_desconto(200.0, 10.0) == 20.0

Exemplo 1: além do caminho feliz

Passo 3. Teste a entrada inválida.

import pytest
from desconto import calcular_desconto

def test_lanca_erro_para_valor_negativo():
    with pytest.raises(ValueError):
        calcular_desconto(-50.0, 10.0)

O que um bom teste unitário tenta cobrir

  1. comportamento normal esperado
  1. casos de borda
  1. entradas inválidas

Passo 2. Escolher melhor os casos de teste

Se a regra for:

  • pedidos com valor maior ou igual a R$ 200,00 têm frete grátis

quais grupos de entrada você deveria testar?

Partições e limites

  • abaixo do limite
  • exatamente no limite
  • acima do limite
  • entrada inválida

Exemplo 2: começando a função

Passo 1. Escreva a regra.

def calcular_frete(total_pedido: float) -> float:
    if total_pedido < 0:
        raise ValueError("total invalido")
    if total_pedido >= 200:
        return 0.0
    return 15.0

Exemplo 2: cobrindo vários valores

Passo 2. Use parametrize para cobrir grupos diferentes.

import pytest

@pytest.mark.parametrize(
    "total, frete_esperado",
    [
        (50.0, 15.0),
        (199.99, 15.0),
        (200.0, 0.0),
        (350.0, 0.0),
    ],
)
def test_calcular_frete(total, frete_esperado):
    assert calcular_frete(total) == frete_esperado

Exemplo 2: o que o teste cobre?

  • 50.0 e 199.99 cobrem abaixo do limite
  • 200.0 cobre o caso de borda
  • 350.0 cobre acima do limite
  • ainda falta o caso inválido explícito

Exemplo 2: completando a cobertura

Passo 3. Adicione o erro esperado.

def test_total_negativo_dispara_erro():
    with pytest.raises(ValueError):
        calcular_frete(-1.0)

uv + pytest

Fluxo simples para executar os testes em Python:

uv venv
source .venv/bin/activate
uv pip install pytest
pytest -q

Estrutura mínima:

projeto/
├── desconto.py
└── tests/
    └── test_desconto.py

E quando a unidade depende de algo externo?

Às vezes ela depende de:

  • banco de dados
  • API externa
  • relógio do sistema
  • fila ou serviço remoto

O que fazer nesses casos?

Você pode usar objetos de teste como:

  • mock
  • stub
  • fake
A ideia é isolar a unidade testada.

Passo 3. Entender o TDD

No TDD, o desenvolvimento acontece em passos pequenos, intercalando:

  • teste
  • implementação
  • refatoração

Antes e depois do TDD

Sem TDD, muita gente faz assim:

  1. implementar bastante
  1. testar depois

Com TDD, a lógica muda:

  1. escrever um teste pequeno
  2. ver falhar
  3. implementar o mínimo
  4. refatorar

O ciclo vermelho, verde e azul

Nesta aula, vamos usar a convenção:

  1. Vermelho: o teste falha
  1. Verde: o mínimo foi implementado para fazê-lo passar
  1. Azul: o código é refatorado sem mudar o comportamento

O que o vermelho significa?

No TDD, o vermelho inicial não é fracasso.

Ele mostra que:

  • o teste está exercitando algo novo
  • a funcionalidade ainda não existe ou ainda está errada

O fluxo do TDD

  1. identificar um incremento pequeno
  1. escrever o teste automatizado
  1. executar e observar a falha
  1. implementar a funcionalidade
  1. executar novamente
  1. refatorar
  1. repetir o ciclo

Exemplo 3: TDD passo 1

Requisito pequeno: pedido com valor maior ou igual a R$ 200,00 tem frete zero.

Primeiro, escrevemos o teste:

from checkout import calcular_frete

def test_pedido_com_200_reais_ou_mais_tem_frete_gratis():
    assert calcular_frete(200.0) == 0.0

Ao rodar, ele falha.

Vermelho.

Exemplo 3: TDD passo 2

Agora implementamos o mínimo:

def calcular_frete(total_pedido: float) -> float:
    return 0.0

O teste passa.

Verde.

Exemplo 3: TDD passo 3

Agora escrevemos um segundo teste:

def test_pedido_abaixo_de_200_reais_paga_frete_padrao():
    assert calcular_frete(150.0) == 15.0

Ele falha.

Vermelho novamente.

Exemplo 3: TDD passo 4

Implementamos o mínimo para os dois testes:

def calcular_frete(total_pedido: float) -> float:
    if total_pedido >= 200.0:
        return 0.0
    return 15.0

Agora os dois passam.

Verde novamente.

Exemplo 3: TDD passo 5

Agora refatoramos sem mudar o comportamento:

FRETE_PADRAO = 15.0
LIMITE_FRETE_GRATIS = 200.0

def calcular_frete(total_pedido: float) -> float:
    if total_pedido >= LIMITE_FRETE_GRATIS:
        return 0.0
    return FRETE_PADRAO

Todos os testes continuam passando.

Azul.

O que esse ciclo ensina

  • uma regra pequena por vez
  • um teste que explicita o comportamento esperado
  • o mínimo de código para passar
  • refatoração protegida pela suíte de testes

Por que TDD ajuda?

Sommerville destaca benefícios como:

  • melhor clareza sobre o que o código deve fazer
  • maior cobertura de código
  • regressão mais barata
  • depuração mais simples
  • testes funcionando como documentação executável

Limite importante do TDD

TDD ajuda muito, mas não substitui:

  • teste de integração
  • teste de sistema
  • teste de desempenho
  • teste de aceitação

Passo 4. Entender testes de release

Sommerville separa o teste de release do teste de desenvolvimento.

Diferenças principais:

  1. idealmente, outra equipe testa a versão
  1. o foco sai da descoberta local de bugs e vai para a validação da versão

Testes baseados em requisitos

Uma abordagem central do teste de release é:

  1. pegar um requisito
  1. derivar testes a partir dele
  1. manter rastreabilidade entre requisito e teste
Ponto importante: um requisito raramente é coberto por um único teste.

Testes de cenário

Sommerville também destaca testes de cenário.

Eles partem de histórias realistas de uso, por exemplo:

  1. cliente monta carrinho
  1. subtotaliza R$ 230,00
  1. avança ao checkout
  1. sistema aplica frete grátis
  1. resumo final mostra total correto

Passo 5. Entender o teste de aceitação

Teste de aceitação é parte dos testes de usuário.

A pergunta central aqui é:

o sistema está bom o suficiente para uso real pelo cliente?

O que a aceitação observa?

Não apenas funcionalidade, mas também:

  • desempenho
  • usabilidade
  • adequação ao fluxo de trabalho

Os seis estágios da aceitação

  1. definir critérios de aceitação
  1. planejar os testes de aceitação
  1. derivar os testes de aceitação
  1. executar os testes de aceitação
  1. negociar os resultados
  1. aceitar ou rejeitar o sistema

Unitário x aceitação

Tipo O que verifica?
Unitário Se uma unidade isolada funciona corretamente
Aceitação Se o sistema está adequado para uso do ponto de vista do cliente

Exemplo:

  • teste unitário valida calcular_frete(200.0) == 0.0
  • teste de aceitação valida que o checkout mostra frete R$ 0,00 no uso real

Aceitação não é sempre tudo ou nada

Na prática, pode haver aceitação condicional.

Isso acontece quando:

  • o cliente quer começar a implantação logo
  • já treinou equipe ou comprou infraestrutura
  • considera que o custo de não usar o sistema é maior que o custo dos problemas restantes

Fechamento

Guarde esta ideia central:

  • teste unitário ajuda a validar partes pequenas
  • TDD ajuda a construir essas partes em passos seguros
  • teste de aceitação ajuda a decidir se o sistema está pronto para uso real

Testar não é uma atividade única.

É um conjunto de verificações em níveis diferentes.

Questões de fixação - 1 / 2

  1. Explique a diferença entre teste unitário, teste de integração, teste de sistema e teste de aceitação.
  2. Por que passar em testes unitários não é suficiente para concluir que o sistema inteiro está pronto para entrega?
  3. Descreva o ciclo do TDD usando as cores vermelho, verde e azul.
  4. Por que a falha inicial de um teste novo é esperada no TDD?
  5. Proponha quatro casos de teste unitário para a regra de frete grátis em pedidos com subtotal maior ou igual a R$ 200,00.

Questões de fixação - 2 / 2

  1. Qual é a diferença entre um teste baseado em requisito e um teste de cenário?
  2. Explique por que TDD ajuda a reduzir o custo do teste de regressão.
  3. Em que situações mocks ou objetos falsos são úteis em testes unitários?
  4. Quais são os seis estágios do teste de aceitação segundo Sommerville?
  5. O que significa uma aceitação condicional de sistema?